vfs: check userland buffers before reading them.
[haiku.git] / src / kits / package / PackageInfoParser.cpp
blob3de3a7cacbf3b5341a0d411902de1acc4eb43b9c
1 /*
2 * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
3 * Copyright 2016, Andrew Lindesay <apl@lindesay.co.nz>
4 * Distributed under the terms of the MIT License.
5 */
8 #include "PackageInfoParser.h"
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdlib.h>
14 #include <algorithm>
15 #include <string>
17 #include <Url.h>
19 namespace BPackageKit {
22 BPackageInfo::ParseErrorListener::~ParseErrorListener()
27 BPackageInfo::Parser::Parser(ParseErrorListener* listener)
29 fListener(listener),
30 fPos(NULL)
35 status_t
36 BPackageInfo::Parser::Parse(const BString& packageInfoString,
37 BPackageInfo* packageInfo)
39 if (packageInfo == NULL)
40 return B_BAD_VALUE;
42 fPos = packageInfoString.String();
44 try {
45 _Parse(packageInfo);
46 } catch (const ParseError& error) {
47 if (fListener != NULL) {
48 // map error position to line and column
49 int line = 1;
50 int inLineOffset;
51 int32 offset = error.pos - packageInfoString.String();
52 int32 newlinePos = packageInfoString.FindLast('\n', offset - 1);
53 if (newlinePos < 0)
54 inLineOffset = offset;
55 else {
56 inLineOffset = offset - newlinePos - 1;
57 do {
58 line++;
59 newlinePos = packageInfoString.FindLast('\n',
60 newlinePos - 1);
61 } while (newlinePos >= 0);
64 int column = 0;
65 for (int i = 0; i < inLineOffset; i++) {
66 column++;
67 if (error.pos[i - inLineOffset] == '\t')
68 column = (column + 3) / 4 * 4;
71 fListener->OnError(error.message, line, column + 1);
73 return B_BAD_DATA;
74 } catch (const std::bad_alloc& e) {
75 if (fListener != NULL)
76 fListener->OnError("out of memory", 0, 0);
77 return B_NO_MEMORY;
80 return B_OK;
84 status_t
85 BPackageInfo::Parser::ParseVersion(const BString& versionString,
86 bool revisionIsOptional, BPackageVersion& _version)
88 fPos = versionString.String();
90 try {
91 Token token(TOKEN_STRING, fPos, versionString.Length());
92 _ParseVersionValue(token, &_version, revisionIsOptional);
93 } catch (const ParseError& error) {
94 if (fListener != NULL) {
95 int32 offset = error.pos - versionString.String();
96 fListener->OnError(error.message, 1, offset);
98 return B_BAD_DATA;
99 } catch (const std::bad_alloc& e) {
100 if (fListener != NULL)
101 fListener->OnError("out of memory", 0, 0);
102 return B_NO_MEMORY;
105 return B_OK;
109 status_t
110 BPackageInfo::Parser::ParseResolvableExpression(const BString& expressionString,
111 BPackageResolvableExpression& _expression)
113 fPos = expressionString.String();
115 try {
116 Token token(TOKEN_STRING, fPos, expressionString.Length());
117 _ParseResolvableExpression(_NextToken(), _expression, NULL);
118 } catch (const ParseError& error) {
119 if (fListener != NULL) {
120 int32 offset = error.pos - expressionString.String();
121 fListener->OnError(error.message, 1, offset);
123 return B_BAD_DATA;
124 } catch (const std::bad_alloc& e) {
125 if (fListener != NULL)
126 fListener->OnError("out of memory", 0, 0);
127 return B_NO_MEMORY;
130 return B_OK;
134 BPackageInfo::Parser::Token
135 BPackageInfo::Parser::_NextToken()
137 // Eat any whitespace, comments, or escaped new lines. Also eat ';' -- they
138 // have the same function as newlines. We remember the last encountered ';'
139 // or '\n' and return it as a token afterwards.
140 const char* itemSeparatorPos = NULL;
141 bool inComment = false;
142 while ((inComment && *fPos != '\0') || isspace(*fPos) || *fPos == ';'
143 || *fPos == '#' || *fPos == '\\') {
144 if (*fPos == '#') {
145 inComment = true;
146 } else if (!inComment && *fPos == '\\') {
147 if (fPos[1] != '\n')
148 break;
149 // ignore escaped line breaks
150 fPos++;
151 } else if (*fPos == '\n') {
152 itemSeparatorPos = fPos;
153 inComment = false;
154 } else if (!inComment && *fPos == ';')
155 itemSeparatorPos = fPos;
156 fPos++;
159 if (itemSeparatorPos != NULL) {
160 return Token(TOKEN_ITEM_SEPARATOR, itemSeparatorPos);
163 const char* tokenPos = fPos;
164 switch (*fPos) {
165 case '\0':
166 return Token(TOKEN_EOF, fPos);
168 case '{':
169 fPos++;
170 return Token(TOKEN_OPEN_BRACE, tokenPos);
172 case '}':
173 fPos++;
174 return Token(TOKEN_CLOSE_BRACE, tokenPos);
176 case '<':
177 fPos++;
178 if (*fPos == '=') {
179 fPos++;
180 return Token(TOKEN_OPERATOR_LESS_EQUAL, tokenPos, 2);
182 return Token(TOKEN_OPERATOR_LESS, tokenPos, 1);
184 case '=':
185 fPos++;
186 if (*fPos == '=') {
187 fPos++;
188 return Token(TOKEN_OPERATOR_EQUAL, tokenPos, 2);
190 return Token(TOKEN_OPERATOR_ASSIGN, tokenPos, 1);
192 case '!':
193 if (fPos[1] == '=') {
194 fPos += 2;
195 return Token(TOKEN_OPERATOR_NOT_EQUAL, tokenPos, 2);
197 break;
199 case '>':
200 fPos++;
201 if (*fPos == '=') {
202 fPos++;
203 return Token(TOKEN_OPERATOR_GREATER_EQUAL, tokenPos, 2);
205 return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1);
207 default:
209 std::string string;
210 char quoteChar = '\0';
212 for (; *fPos != '\0'; fPos++) {
213 char c = *fPos;
214 if (quoteChar != '\0') {
215 // within a quoted string segment
216 if (c == quoteChar) {
217 quoteChar = '\0';
218 continue;
221 if (c == '\\') {
222 // next char is escaped
223 c = *++fPos;
224 if (c == '\0') {
225 throw ParseError("unterminated quoted-string",
226 tokenPos);
229 if (c == 'n')
230 c = '\n';
231 else if (c == 't')
232 c = '\t';
235 string += c;
236 } else {
237 // unquoted string segment
238 switch (c) {
239 case '"':
240 case '\'':
241 // quoted string start
242 quoteChar = c;
243 continue;
245 case '{':
246 case '}':
247 case '<':
248 case '=':
249 case '!':
250 case '>':
251 // a separator character -- this ends the string
252 break;
254 case '\\':
255 // next char is escaped
256 c = *++fPos;
257 if (c == '\0') {
258 throw ParseError("'\\' at end of string",
259 tokenPos);
261 string += c;
262 continue;
264 default:
265 if (isspace(c))
266 break;
267 string += c;
268 continue;
271 break;
275 return Token(TOKEN_STRING, tokenPos, fPos - tokenPos,
276 string.c_str());
280 BString error = BString("unknown token '") << *fPos << "' encountered";
281 throw ParseError(error.String(), fPos);
285 void
286 BPackageInfo::Parser::_RewindTo(const Token& token)
288 fPos = token.pos;
292 void
293 BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos)
295 Token string = _NextToken();
296 if (string.type != TOKEN_STRING)
297 throw ParseError("expected string", string.pos);
299 *value = string.text;
300 if (_tokenPos != NULL)
301 *_tokenPos = string.pos;
305 void
306 BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value)
308 Token arch = _NextToken();
309 if (arch.type == TOKEN_STRING) {
310 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
311 if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) {
312 *value = (BPackageArchitecture)i;
313 return;
318 BString error("architecture must be one of: [");
319 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
320 if (i > 0)
321 error << ",";
322 error << BPackageInfo::kArchitectureNames[i];
324 error << "]";
325 throw ParseError(error, arch.pos);
329 void
330 BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
331 bool revisionIsOptional)
333 Token word = _NextToken();
334 _ParseVersionValue(word, value, revisionIsOptional);
338 /*static*/ void
339 BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value,
340 bool revisionIsOptional)
342 if (word.type != TOKEN_STRING)
343 throw ParseError("expected string (a version)", word.pos);
345 // get the revision number
346 uint32 revision = 0;
347 int32 dashPos = word.text.FindLast('-');
348 if (dashPos >= 0) {
349 char* end;
350 long long number = strtoll(word.text.String() + dashPos + 1, &end,
352 if (*end != '\0' || number < 0 || number > UINT_MAX) {
353 throw ParseError("revision must be a number > 0 and < UINT_MAX",
354 word.pos + dashPos + 1);
357 revision = (uint32)number;
358 word.text.Truncate(dashPos);
361 if (revision == 0 && !revisionIsOptional) {
362 throw ParseError("expected revision number (-<number> suffix)",
363 word.pos + word.text.Length());
366 // get the pre-release string
367 BString preRelease;
368 int32 tildePos = word.text.FindLast('~');
369 if (tildePos >= 0) {
370 word.text.CopyInto(preRelease, tildePos + 1,
371 word.text.Length() - tildePos - 1);
372 word.text.Truncate(tildePos);
374 if (preRelease.IsEmpty()) {
375 throw ParseError("invalid empty pre-release string",
376 word.pos + tildePos + 1);
379 int32 errorPos;
380 if (!_IsAlphaNumUnderscore(preRelease, ".", &errorPos)) {
381 throw ParseError("invalid character in pre-release string",
382 word.pos + tildePos + 1 + errorPos);
386 // get major, minor, and micro strings
387 BString major;
388 BString minor;
389 BString micro;
390 int32 firstDotPos = word.text.FindFirst('.');
391 if (firstDotPos < 0)
392 major = word.text;
393 else {
394 word.text.CopyInto(major, 0, firstDotPos);
395 int32 secondDotPos = word.text.FindFirst('.', firstDotPos + 1);
396 if (secondDotPos == firstDotPos + 1)
397 throw ParseError("expected minor version", word.pos + secondDotPos);
399 if (secondDotPos < 0) {
400 word.text.CopyInto(minor, firstDotPos + 1, word.text.Length());
401 } else {
402 word.text.CopyInto(minor, firstDotPos + 1,
403 secondDotPos - (firstDotPos + 1));
404 word.text.CopyInto(micro, secondDotPos + 1, word.text.Length());
406 int32 errorPos;
407 if (!_IsAlphaNumUnderscore(micro, ".", &errorPos)) {
408 throw ParseError("invalid character in micro version string",
409 word.pos + secondDotPos + 1 + errorPos);
413 int32 errorPos;
414 if (!_IsAlphaNumUnderscore(minor, "", &errorPos)) {
415 throw ParseError("invalid character in minor version string",
416 word.pos + firstDotPos + 1 + errorPos);
420 int32 errorPos;
421 if (!_IsAlphaNumUnderscore(major, "", &errorPos)) {
422 throw ParseError("invalid character in major version string",
423 word.pos + errorPos);
426 value->SetTo(major, minor, micro, preRelease, revision);
430 void
431 BPackageInfo::Parser::_ParseResolvableExpression(const Token& token,
432 BPackageResolvableExpression& _value, BString* _basePackage)
434 if (token.type != TOKEN_STRING) {
435 throw ParseError("expected word (a resolvable name)",
436 token.pos);
439 int32 errorPos;
440 if (!_IsValidResolvableName(token.text, &errorPos)) {
441 throw ParseError("invalid character in resolvable name",
442 token.pos + errorPos);
445 BPackageVersion version;
446 Token op = _NextToken();
447 BPackageResolvableOperator resolvableOperator;
448 if (op.type == TOKEN_OPERATOR_LESS
449 || op.type == TOKEN_OPERATOR_LESS_EQUAL
450 || op.type == TOKEN_OPERATOR_EQUAL
451 || op.type == TOKEN_OPERATOR_NOT_EQUAL
452 || op.type == TOKEN_OPERATOR_GREATER_EQUAL
453 || op.type == TOKEN_OPERATOR_GREATER) {
454 _ParseVersionValue(&version, true);
456 if (_basePackage != NULL) {
457 Token base = _NextToken();
458 if (base.type == TOKEN_STRING && base.text == "base") {
459 if (!_basePackage->IsEmpty()) {
460 throw ParseError("multiple packages marked as base package",
461 token.pos);
464 *_basePackage = token.text;
465 } else
466 _RewindTo(base);
469 resolvableOperator = (BPackageResolvableOperator)
470 (op.type - TOKEN_OPERATOR_LESS);
471 } else if (op.type == TOKEN_ITEM_SEPARATOR
472 || op.type == TOKEN_CLOSE_BRACE || op.type == TOKEN_EOF) {
473 _RewindTo(op);
474 resolvableOperator = B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT;
475 } else {
476 throw ParseError(
477 "expected '<', '<=', '==', '!=', '>=', '>', comma or '}'",
478 op.pos);
481 _value.SetTo(token.text, resolvableOperator, version);
485 void
486 BPackageInfo::Parser::_ParseList(ListElementParser& elementParser,
487 bool allowSingleNonListElement)
489 Token openBracket = _NextToken();
490 if (openBracket.type != TOKEN_OPEN_BRACE) {
491 if (!allowSingleNonListElement)
492 throw ParseError("expected start of list ('{')", openBracket.pos);
494 elementParser(openBracket);
495 return;
498 while (true) {
499 Token token = _NextToken();
500 if (token.type == TOKEN_CLOSE_BRACE)
501 return;
503 if (token.type == TOKEN_ITEM_SEPARATOR)
504 continue;
506 elementParser(token);
511 void
512 BPackageInfo::Parser::_ParseStringList(BStringList* value,
513 bool requireResolvableName, bool convertToLowerCase,
514 StringValidator* stringValidator)
516 struct StringParser : public ListElementParser {
517 BStringList* value;
518 bool requireResolvableName;
519 bool convertToLowerCase;
520 StringValidator* stringValidator;
522 StringParser(BStringList* value, bool requireResolvableName,
523 bool convertToLowerCase, StringValidator* stringValidator)
525 value(value),
526 requireResolvableName(requireResolvableName),
527 convertToLowerCase(convertToLowerCase),
528 stringValidator(stringValidator)
532 virtual void operator()(const Token& token)
534 if (token.type != TOKEN_STRING)
535 throw ParseError("expected string", token.pos);
537 if (requireResolvableName) {
538 int32 errorPos;
539 if (!_IsValidResolvableName(token.text, &errorPos)) {
540 throw ParseError("invalid character in resolvable name",
541 token.pos + errorPos);
545 BString element(token.text);
546 if (convertToLowerCase)
547 element.ToLower();
549 if (stringValidator != NULL)
550 stringValidator->Validate(element, token.pos);
552 value->Add(element);
554 } stringParser(value, requireResolvableName, convertToLowerCase,
555 stringValidator);
557 _ParseList(stringParser, true);
561 uint32
562 BPackageInfo::Parser::_ParseFlags()
564 struct FlagParser : public ListElementParser {
565 uint32 flags;
567 FlagParser()
569 flags(0)
573 virtual void operator()(const Token& token)
575 if (token.type != TOKEN_STRING)
576 throw ParseError("expected word (a flag)", token.pos);
578 if (token.text.ICompare("approve_license") == 0)
579 flags |= B_PACKAGE_FLAG_APPROVE_LICENSE;
580 else if (token.text.ICompare("system_package") == 0)
581 flags |= B_PACKAGE_FLAG_SYSTEM_PACKAGE;
582 else {
583 throw ParseError(
584 "expected 'approve_license' or 'system_package'",
585 token.pos);
588 } flagParser;
590 _ParseList(flagParser, true);
592 return flagParser.flags;
596 void
597 BPackageInfo::Parser::_ParseResolvableList(
598 BObjectList<BPackageResolvable>* value)
600 struct ResolvableParser : public ListElementParser {
601 Parser& parser;
602 BObjectList<BPackageResolvable>* value;
604 ResolvableParser(Parser& parser_,
605 BObjectList<BPackageResolvable>* value_)
607 parser(parser_),
608 value(value_)
612 virtual void operator()(const Token& token)
614 if (token.type != TOKEN_STRING) {
615 throw ParseError("expected word (a resolvable name)",
616 token.pos);
619 int32 errorPos;
620 if (!_IsValidResolvableName(token.text, &errorPos)) {
621 throw ParseError("invalid character in resolvable name",
622 token.pos + errorPos);
625 // parse version
626 BPackageVersion version;
627 Token op = parser._NextToken();
628 if (op.type == TOKEN_OPERATOR_ASSIGN) {
629 parser._ParseVersionValue(&version, true);
630 } else if (op.type == TOKEN_ITEM_SEPARATOR
631 || op.type == TOKEN_CLOSE_BRACE) {
632 parser._RewindTo(op);
633 } else
634 throw ParseError("expected '=', comma or '}'", op.pos);
636 // parse compatible version
637 BPackageVersion compatibleVersion;
638 Token compatible = parser._NextToken();
639 if (compatible.type == TOKEN_STRING
640 && (compatible.text == "compat"
641 || compatible.text == "compatible")) {
642 op = parser._NextToken();
643 if (op.type == TOKEN_OPERATOR_GREATER_EQUAL) {
644 parser._ParseVersionValue(&compatibleVersion, true);
645 } else
646 parser._RewindTo(compatible);
647 } else
648 parser._RewindTo(compatible);
650 value->AddItem(new BPackageResolvable(token.text, version,
651 compatibleVersion));
653 } resolvableParser(*this, value);
655 _ParseList(resolvableParser, false);
659 void
660 BPackageInfo::Parser::_ParseResolvableExprList(
661 BObjectList<BPackageResolvableExpression>* value, BString* _basePackage)
663 struct ResolvableExpressionParser : public ListElementParser {
664 Parser& parser;
665 BObjectList<BPackageResolvableExpression>* value;
666 BString* basePackage;
668 ResolvableExpressionParser(Parser& parser,
669 BObjectList<BPackageResolvableExpression>* value,
670 BString* basePackage)
672 parser(parser),
673 value(value),
674 basePackage(basePackage)
678 virtual void operator()(const Token& token)
680 BPackageResolvableExpression expression;
681 parser._ParseResolvableExpression(token, expression, basePackage);
682 value->AddItem(new BPackageResolvableExpression(expression));
684 } resolvableExpressionParser(*this, value, _basePackage);
686 _ParseList(resolvableExpressionParser, false);
690 void
691 BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
692 GlobalWritableFileInfoList* infos)
694 struct GlobalWritableFileInfoParser : public ListElementParser {
695 Parser& parser;
696 GlobalWritableFileInfoList* infos;
698 GlobalWritableFileInfoParser(Parser& parser,
699 GlobalWritableFileInfoList* infos)
701 parser(parser),
702 infos(infos)
706 virtual void operator()(const Token& token)
708 if (token.type != TOKEN_STRING) {
709 throw ParseError("expected string (a file path)",
710 token.pos);
713 BWritableFileUpdateType updateType
714 = B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
715 bool isDirectory = false;
717 Token nextToken = parser._NextToken();
718 if (nextToken.type == TOKEN_STRING
719 && nextToken.text == "directory") {
720 isDirectory = true;
721 nextToken = parser._NextToken();
724 if (nextToken.type == TOKEN_STRING) {
725 const char* const* end = kWritableFileUpdateTypes
726 + B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
727 const char* const* found = std::find(kWritableFileUpdateTypes,
728 end, nextToken.text);
729 if (found == end) {
730 throw ParseError(BString("expected an update type"),
731 nextToken.pos);
733 updateType = (BWritableFileUpdateType)(
734 found - kWritableFileUpdateTypes);
735 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
736 || nextToken.type == TOKEN_CLOSE_BRACE) {
737 parser._RewindTo(nextToken);
738 } else {
739 throw ParseError(
740 "expected 'included', semicolon, new line or '}'",
741 nextToken.pos);
744 if (!infos->AddItem(new BGlobalWritableFileInfo(token.text,
745 updateType, isDirectory))) {
746 throw std::bad_alloc();
749 } resolvableExpressionParser(*this, infos);
751 _ParseList(resolvableExpressionParser, false);
755 void
756 BPackageInfo::Parser::_ParseUserSettingsFileInfos(
757 UserSettingsFileInfoList* infos)
759 struct UserSettingsFileInfoParser : public ListElementParser {
760 Parser& parser;
761 UserSettingsFileInfoList* infos;
763 UserSettingsFileInfoParser(Parser& parser,
764 UserSettingsFileInfoList* infos)
766 parser(parser),
767 infos(infos)
771 virtual void operator()(const Token& token)
773 if (token.type != TOKEN_STRING) {
774 throw ParseError("expected string (a settings file path)",
775 token.pos);
778 BString templatePath;
779 bool isDirectory = false;
781 Token nextToken = parser._NextToken();
782 if (nextToken.type == TOKEN_STRING
783 && nextToken.text == "directory") {
784 isDirectory = true;
785 } else if (nextToken.type == TOKEN_STRING
786 && nextToken.text == "template") {
787 nextToken = parser._NextToken();
788 if (nextToken.type != TOKEN_STRING) {
789 throw ParseError(
790 "expected string (a settings template file path)",
791 nextToken.pos);
793 templatePath = nextToken.text;
794 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
795 || nextToken.type == TOKEN_CLOSE_BRACE) {
796 parser._RewindTo(nextToken);
797 } else {
798 throw ParseError(
799 "expected 'template', semicolon, new line or '}'",
800 nextToken.pos);
803 if (isDirectory
804 ? !infos->AddItem(new BUserSettingsFileInfo(token.text, true))
805 : !infos->AddItem(new BUserSettingsFileInfo(token.text,
806 templatePath))) {
807 throw std::bad_alloc();
810 } resolvableExpressionParser(*this, infos);
812 _ParseList(resolvableExpressionParser, false);
816 void
817 BPackageInfo::Parser::_ParseUsers(UserList* users)
819 struct UserParser : public ListElementParser {
820 Parser& parser;
821 UserList* users;
823 UserParser(Parser& parser, UserList* users)
825 parser(parser),
826 users(users)
830 virtual void operator()(const Token& token)
832 if (token.type != TOKEN_STRING
833 || !BUser::IsValidUserName(token.text)) {
834 throw ParseError("expected a user name", token.pos);
837 BString realName;
838 BString home;
839 BString shell;
840 BStringList groups;
842 for (;;) {
843 Token nextToken = parser._NextToken();
844 if (nextToken.type != TOKEN_STRING) {
845 parser._RewindTo(nextToken);
846 break;
849 if (nextToken.text == "real-name") {
850 nextToken = parser._NextToken();
851 if (nextToken.type != TOKEN_STRING) {
852 throw ParseError("expected string (a user real name)",
853 nextToken.pos);
855 realName = nextToken.text;
856 } else if (nextToken.text == "home") {
857 nextToken = parser._NextToken();
858 if (nextToken.type != TOKEN_STRING) {
859 throw ParseError("expected string (a home path)",
860 nextToken.pos);
862 home = nextToken.text;
863 } else if (nextToken.text == "shell") {
864 nextToken = parser._NextToken();
865 if (nextToken.type != TOKEN_STRING) {
866 throw ParseError("expected string (a shell path)",
867 nextToken.pos);
869 shell = nextToken.text;
870 } else if (nextToken.text == "groups") {
871 for (;;) {
872 nextToken = parser._NextToken();
873 if (nextToken.type == TOKEN_STRING
874 && BUser::IsValidUserName(nextToken.text)) {
875 if (!groups.Add(nextToken.text))
876 throw std::bad_alloc();
877 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
878 || nextToken.type == TOKEN_CLOSE_BRACE) {
879 parser._RewindTo(nextToken);
880 break;
881 } else {
882 throw ParseError("expected a group name",
883 nextToken.pos);
886 break;
887 } else {
888 throw ParseError(
889 "expected 'real-name', 'home', 'shell', or 'groups'",
890 nextToken.pos);
894 BString templatePath;
896 Token nextToken = parser._NextToken();
897 if (nextToken.type == TOKEN_STRING
898 && nextToken.text == "template") {
899 nextToken = parser._NextToken();
900 if (nextToken.type != TOKEN_STRING) {
901 throw ParseError(
902 "expected string (a settings template file path)",
903 nextToken.pos);
905 templatePath = nextToken.text;
906 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
907 || nextToken.type == TOKEN_CLOSE_BRACE) {
908 parser._RewindTo(nextToken);
909 } else {
910 throw ParseError(
911 "expected 'template', semicolon, new line or '}'",
912 nextToken.pos);
915 if (!users->AddItem(new BUser(token.text, realName, home, shell,
916 groups))) {
917 throw std::bad_alloc();
920 } resolvableExpressionParser(*this, users);
922 _ParseList(resolvableExpressionParser, false);
926 void
927 BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo)
929 bool seen[B_PACKAGE_INFO_ENUM_COUNT];
930 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; ++i)
931 seen[i] = false;
933 const char* const* names = BPackageInfo::kElementNames;
935 while (Token t = _NextToken()) {
936 if (t.type == TOKEN_ITEM_SEPARATOR)
937 continue;
939 if (t.type != TOKEN_STRING)
940 throw ParseError("expected string (a variable name)", t.pos);
942 BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT;
943 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) {
944 if (names[i] != NULL && t.text.ICompare(names[i]) == 0) {
945 attribute = (BPackageInfoAttributeID)i;
946 break;
950 if (attribute == B_PACKAGE_INFO_ENUM_COUNT) {
951 BString error = BString("unknown attribute \"") << t.text << '"';
952 throw ParseError(error, t.pos);
955 if (seen[attribute]) {
956 BString error = BString(names[attribute]) << " already seen!";
957 throw ParseError(error, t.pos);
960 switch (attribute) {
961 case B_PACKAGE_INFO_NAME:
963 BString name;
964 const char* namePos;
965 _ParseStringValue(&name, &namePos);
967 int32 errorPos;
968 if (!_IsValidResolvableName(name, &errorPos)) {
969 throw ParseError("invalid character in package name",
970 namePos + errorPos);
973 packageInfo->SetName(name);
974 break;
977 case B_PACKAGE_INFO_SUMMARY:
979 BString summary;
980 _ParseStringValue(&summary);
981 if (summary.FindFirst('\n') >= 0)
982 throw ParseError("the summary contains linebreaks", t.pos);
983 packageInfo->SetSummary(summary);
984 break;
987 case B_PACKAGE_INFO_DESCRIPTION:
988 _ParseStringValue(&packageInfo->fDescription);
989 break;
991 case B_PACKAGE_INFO_VENDOR:
992 _ParseStringValue(&packageInfo->fVendor);
993 break;
995 case B_PACKAGE_INFO_PACKAGER:
996 _ParseStringValue(&packageInfo->fPackager);
997 break;
999 case B_PACKAGE_INFO_BASE_PACKAGE:
1000 _ParseStringValue(&packageInfo->fBasePackage);
1001 break;
1003 case B_PACKAGE_INFO_ARCHITECTURE:
1004 _ParseArchitectureValue(&packageInfo->fArchitecture);
1005 break;
1007 case B_PACKAGE_INFO_VERSION:
1008 _ParseVersionValue(&packageInfo->fVersion, false);
1009 break;
1011 case B_PACKAGE_INFO_COPYRIGHTS:
1012 _ParseStringList(&packageInfo->fCopyrightList);
1013 break;
1015 case B_PACKAGE_INFO_LICENSES:
1016 _ParseStringList(&packageInfo->fLicenseList);
1017 break;
1019 case B_PACKAGE_INFO_URLS:
1021 UrlStringValidator stringValidator;
1022 _ParseStringList(&packageInfo->fURLList,
1023 false, false, &stringValidator);
1025 break;
1027 case B_PACKAGE_INFO_SOURCE_URLS:
1029 UrlStringValidator stringValidator;
1030 _ParseStringList(&packageInfo->fSourceURLList,
1031 false, false, &stringValidator);
1033 break;
1035 case B_PACKAGE_INFO_GLOBAL_WRITABLE_FILES:
1036 _ParseGlobalWritableFileInfos(
1037 &packageInfo->fGlobalWritableFileInfos);
1038 break;
1040 case B_PACKAGE_INFO_USER_SETTINGS_FILES:
1041 _ParseUserSettingsFileInfos(
1042 &packageInfo->fUserSettingsFileInfos);
1043 break;
1045 case B_PACKAGE_INFO_USERS:
1046 _ParseUsers(&packageInfo->fUsers);
1047 break;
1049 case B_PACKAGE_INFO_GROUPS:
1050 _ParseStringList(&packageInfo->fGroups);
1051 break;
1053 case B_PACKAGE_INFO_POST_INSTALL_SCRIPTS:
1054 _ParseStringList(&packageInfo->fPostInstallScripts);
1055 break;
1057 case B_PACKAGE_INFO_PROVIDES:
1058 _ParseResolvableList(&packageInfo->fProvidesList);
1059 break;
1061 case B_PACKAGE_INFO_REQUIRES:
1062 packageInfo->fBasePackage.Truncate(0);
1063 _ParseResolvableExprList(&packageInfo->fRequiresList,
1064 &packageInfo->fBasePackage);
1065 break;
1067 case B_PACKAGE_INFO_SUPPLEMENTS:
1068 _ParseResolvableExprList(&packageInfo->fSupplementsList);
1069 break;
1071 case B_PACKAGE_INFO_CONFLICTS:
1072 _ParseResolvableExprList(&packageInfo->fConflictsList);
1073 break;
1075 case B_PACKAGE_INFO_FRESHENS:
1076 _ParseResolvableExprList(&packageInfo->fFreshensList);
1077 break;
1079 case B_PACKAGE_INFO_REPLACES:
1080 _ParseStringList(&packageInfo->fReplacesList, true);
1081 break;
1083 case B_PACKAGE_INFO_FLAGS:
1084 packageInfo->SetFlags(_ParseFlags());
1085 break;
1087 default:
1088 // can never get here
1089 break;
1092 seen[attribute] = true;
1095 // everything up to and including 'provides' is mandatory
1096 for (int i = 0; i <= B_PACKAGE_INFO_PROVIDES; ++i) {
1097 if (!seen[i]) {
1098 BString error = BString(names[i]) << " is not being set anywhere!";
1099 throw ParseError(error, fPos);
1105 /*static*/ inline bool
1106 BPackageInfo::Parser::_IsAlphaNumUnderscore(const BString& string,
1107 const char* additionalChars, int32* _errorPos)
1109 return _IsAlphaNumUnderscore(string.String(),
1110 string.String() + string.Length(), additionalChars, _errorPos);
1114 /*static*/ inline bool
1115 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* string,
1116 const char* additionalChars, int32* _errorPos)
1118 return _IsAlphaNumUnderscore(string, string + strlen(string),
1119 additionalChars, _errorPos);
1123 /*static*/ bool
1124 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* start, const char* end,
1125 const char* additionalChars, int32* _errorPos)
1127 for (const char* c = start; c < end; c++) {
1128 if (!isalnum(*c) && *c != '_' && strchr(additionalChars, *c) == NULL) {
1129 if (_errorPos != NULL)
1130 *_errorPos = c - start;
1131 return false;
1135 return true;
1139 /*static*/ bool
1140 BPackageInfo::Parser::_IsValidResolvableName(const char* string,
1141 int32* _errorPos)
1143 for (const char* c = string; *c != '\0'; c++) {
1144 switch (*c) {
1145 case '-':
1146 case '/':
1147 case '<':
1148 case '>':
1149 case '=':
1150 case '!':
1151 break;
1152 default:
1153 if (!isspace(*c))
1154 continue;
1155 break;
1158 if (_errorPos != NULL)
1159 *_errorPos = c - string;
1160 return false;
1162 return true;
1165 void
1166 BPackageInfo::Parser::UrlStringValidator::Validate(const BString& urlString,
1167 const char* pos)
1169 BUrl url(urlString);
1171 if (!url.IsValid())
1172 throw ParseError("invalid url", pos);
1176 } // namespace BPackageKit