BPicture: Fix archive constructor.
[haiku.git] / src / kits / package / PackageInfoParser.cpp
blobf684815ec44f16ae202dfc26add80abeb26d3c43
1 /*
2 * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
3 * Distributed under the terms of the MIT License.
4 */
7 #include "PackageInfoParser.h"
9 #include <ctype.h>
10 #include <stdint.h>
11 #include <stdlib.h>
13 #include <algorithm>
14 #include <string>
17 namespace BPackageKit {
20 BPackageInfo::ParseErrorListener::~ParseErrorListener()
25 BPackageInfo::Parser::Parser(ParseErrorListener* listener)
27 fListener(listener),
28 fPos(NULL)
33 status_t
34 BPackageInfo::Parser::Parse(const BString& packageInfoString,
35 BPackageInfo* packageInfo)
37 if (packageInfo == NULL)
38 return B_BAD_VALUE;
40 fPos = packageInfoString.String();
42 try {
43 _Parse(packageInfo);
44 } catch (const ParseError& error) {
45 if (fListener != NULL) {
46 // map error position to line and column
47 int line = 1;
48 int inLineOffset;
49 int32 offset = error.pos - packageInfoString.String();
50 int32 newlinePos = packageInfoString.FindLast('\n', offset - 1);
51 if (newlinePos < 0)
52 inLineOffset = offset;
53 else {
54 inLineOffset = offset - newlinePos - 1;
55 do {
56 line++;
57 newlinePos = packageInfoString.FindLast('\n',
58 newlinePos - 1);
59 } while (newlinePos >= 0);
62 int column = 0;
63 for (int i = 0; i < inLineOffset; i++) {
64 column++;
65 if (error.pos[i - inLineOffset] == '\t')
66 column = (column + 3) / 4 * 4;
69 fListener->OnError(error.message, line, column + 1);
71 return B_BAD_DATA;
72 } catch (const std::bad_alloc& e) {
73 if (fListener != NULL)
74 fListener->OnError("out of memory", 0, 0);
75 return B_NO_MEMORY;
78 return B_OK;
82 status_t
83 BPackageInfo::Parser::ParseVersion(const BString& versionString,
84 bool revisionIsOptional, BPackageVersion& _version)
86 fPos = versionString.String();
88 try {
89 Token token(TOKEN_STRING, fPos, versionString.Length());
90 _ParseVersionValue(token, &_version, revisionIsOptional);
91 } catch (const ParseError& error) {
92 if (fListener != NULL) {
93 int32 offset = error.pos - versionString.String();
94 fListener->OnError(error.message, 1, offset);
96 return B_BAD_DATA;
97 } catch (const std::bad_alloc& e) {
98 if (fListener != NULL)
99 fListener->OnError("out of memory", 0, 0);
100 return B_NO_MEMORY;
103 return B_OK;
107 status_t
108 BPackageInfo::Parser::ParseResolvableExpression(const BString& expressionString,
109 BPackageResolvableExpression& _expression)
111 fPos = expressionString.String();
113 try {
114 Token token(TOKEN_STRING, fPos, expressionString.Length());
115 _ParseResolvableExpression(_NextToken(), _expression, NULL);
116 } catch (const ParseError& error) {
117 if (fListener != NULL) {
118 int32 offset = error.pos - expressionString.String();
119 fListener->OnError(error.message, 1, offset);
121 return B_BAD_DATA;
122 } catch (const std::bad_alloc& e) {
123 if (fListener != NULL)
124 fListener->OnError("out of memory", 0, 0);
125 return B_NO_MEMORY;
128 return B_OK;
132 BPackageInfo::Parser::Token
133 BPackageInfo::Parser::_NextToken()
135 // Eat any whitespace, comments, or escaped new lines. Also eat ';' -- they
136 // have the same function as newlines. We remember the last encountered ';'
137 // or '\n' and return it as a token afterwards.
138 const char* itemSeparatorPos = NULL;
139 bool inComment = false;
140 while ((inComment && *fPos != '\0') || isspace(*fPos) || *fPos == ';'
141 || *fPos == '#' || *fPos == '\\') {
142 if (*fPos == '#') {
143 inComment = true;
144 } else if (!inComment && *fPos == '\\') {
145 if (fPos[1] != '\n')
146 break;
147 // ignore escaped line breaks
148 fPos++;
149 } else if (*fPos == '\n') {
150 itemSeparatorPos = fPos;
151 inComment = false;
152 } else if (!inComment && *fPos == ';')
153 itemSeparatorPos = fPos;
154 fPos++;
157 if (itemSeparatorPos != NULL) {
158 return Token(TOKEN_ITEM_SEPARATOR, itemSeparatorPos);
161 const char* tokenPos = fPos;
162 switch (*fPos) {
163 case '\0':
164 return Token(TOKEN_EOF, fPos);
166 case '{':
167 fPos++;
168 return Token(TOKEN_OPEN_BRACE, tokenPos);
170 case '}':
171 fPos++;
172 return Token(TOKEN_CLOSE_BRACE, tokenPos);
174 case '<':
175 fPos++;
176 if (*fPos == '=') {
177 fPos++;
178 return Token(TOKEN_OPERATOR_LESS_EQUAL, tokenPos, 2);
180 return Token(TOKEN_OPERATOR_LESS, tokenPos, 1);
182 case '=':
183 fPos++;
184 if (*fPos == '=') {
185 fPos++;
186 return Token(TOKEN_OPERATOR_EQUAL, tokenPos, 2);
188 return Token(TOKEN_OPERATOR_ASSIGN, tokenPos, 1);
190 case '!':
191 if (fPos[1] == '=') {
192 fPos += 2;
193 return Token(TOKEN_OPERATOR_NOT_EQUAL, tokenPos, 2);
195 break;
197 case '>':
198 fPos++;
199 if (*fPos == '=') {
200 fPos++;
201 return Token(TOKEN_OPERATOR_GREATER_EQUAL, tokenPos, 2);
203 return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1);
205 default:
207 std::string string;
208 char quoteChar = '\0';
210 for (; *fPos != '\0'; fPos++) {
211 char c = *fPos;
212 if (quoteChar != '\0') {
213 // within a quoted string segment
214 if (c == quoteChar) {
215 quoteChar = '\0';
216 continue;
219 if (c == '\\') {
220 // next char is escaped
221 c = *++fPos;
222 if (c == '\0') {
223 throw ParseError("unterminated quoted-string",
224 tokenPos);
227 if (c == 'n')
228 c = '\n';
229 else if (c == 't')
230 c = '\t';
233 string += c;
234 } else {
235 // unquoted string segment
236 switch (c) {
237 case '"':
238 case '\'':
239 // quoted string start
240 quoteChar = c;
241 continue;
243 case '{':
244 case '}':
245 case '<':
246 case '=':
247 case '!':
248 case '>':
249 // a separator character -- this ends the string
250 break;
252 case '\\':
253 // next char is escaped
254 c = *++fPos;
255 if (c == '\0') {
256 throw ParseError("'\\' at end of string",
257 tokenPos);
259 string += c;
260 continue;
262 default:
263 if (isspace(c))
264 break;
265 string += c;
266 continue;
269 break;
273 return Token(TOKEN_STRING, tokenPos, fPos - tokenPos,
274 string.c_str());
278 BString error = BString("unknown token '") << *fPos << "' encountered";
279 throw ParseError(error.String(), fPos);
283 void
284 BPackageInfo::Parser::_RewindTo(const Token& token)
286 fPos = token.pos;
290 void
291 BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos)
293 Token string = _NextToken();
294 if (string.type != TOKEN_STRING)
295 throw ParseError("expected string", string.pos);
297 *value = string.text;
298 if (_tokenPos != NULL)
299 *_tokenPos = string.pos;
303 void
304 BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value)
306 Token arch = _NextToken();
307 if (arch.type == TOKEN_STRING) {
308 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
309 if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) {
310 *value = (BPackageArchitecture)i;
311 return;
316 BString error("architecture must be one of: [");
317 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
318 if (i > 0)
319 error << ",";
320 error << BPackageInfo::kArchitectureNames[i];
322 error << "]";
323 throw ParseError(error, arch.pos);
327 void
328 BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
329 bool revisionIsOptional)
331 Token word = _NextToken();
332 _ParseVersionValue(word, value, revisionIsOptional);
336 /*static*/ void
337 BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value,
338 bool revisionIsOptional)
340 if (word.type != TOKEN_STRING)
341 throw ParseError("expected string (a version)", word.pos);
343 // get the revision number
344 uint32 revision = 0;
345 int32 dashPos = word.text.FindLast('-');
346 if (dashPos >= 0) {
347 char* end;
348 long long number = strtoll(word.text.String() + dashPos + 1, &end,
350 if (*end != '\0' || number < 0 || number > UINT_MAX) {
351 throw ParseError("revision must be a number > 0 and < UINT_MAX",
352 word.pos + dashPos + 1);
355 revision = (uint32)number;
356 word.text.Truncate(dashPos);
359 if (revision == 0 && !revisionIsOptional) {
360 throw ParseError("expected revision number (-<number> suffix)",
361 word.pos + word.text.Length());
364 // get the pre-release string
365 BString preRelease;
366 int32 tildePos = word.text.FindLast('~');
367 if (tildePos >= 0) {
368 word.text.CopyInto(preRelease, tildePos + 1,
369 word.text.Length() - tildePos - 1);
370 word.text.Truncate(tildePos);
372 if (preRelease.IsEmpty()) {
373 throw ParseError("invalid empty pre-release string",
374 word.pos + tildePos + 1);
377 int32 errorPos;
378 if (!_IsAlphaNumUnderscore(preRelease, ".", &errorPos)) {
379 throw ParseError("invalid character in pre-release string",
380 word.pos + tildePos + 1 + errorPos);
384 // get major, minor, and micro strings
385 BString major;
386 BString minor;
387 BString micro;
388 int32 firstDotPos = word.text.FindFirst('.');
389 if (firstDotPos < 0)
390 major = word.text;
391 else {
392 word.text.CopyInto(major, 0, firstDotPos);
393 int32 secondDotPos = word.text.FindFirst('.', firstDotPos + 1);
394 if (secondDotPos == firstDotPos + 1)
395 throw ParseError("expected minor version", word.pos + secondDotPos);
397 if (secondDotPos < 0) {
398 word.text.CopyInto(minor, firstDotPos + 1, word.text.Length());
399 } else {
400 word.text.CopyInto(minor, firstDotPos + 1,
401 secondDotPos - (firstDotPos + 1));
402 word.text.CopyInto(micro, secondDotPos + 1, word.text.Length());
404 int32 errorPos;
405 if (!_IsAlphaNumUnderscore(micro, ".", &errorPos)) {
406 throw ParseError("invalid character in micro version string",
407 word.pos + secondDotPos + 1 + errorPos);
411 int32 errorPos;
412 if (!_IsAlphaNumUnderscore(minor, "", &errorPos)) {
413 throw ParseError("invalid character in minor version string",
414 word.pos + firstDotPos + 1 + errorPos);
418 int32 errorPos;
419 if (!_IsAlphaNumUnderscore(major, "", &errorPos)) {
420 throw ParseError("invalid character in major version string",
421 word.pos + errorPos);
424 value->SetTo(major, minor, micro, preRelease, revision);
428 void
429 BPackageInfo::Parser::_ParseResolvableExpression(const Token& token,
430 BPackageResolvableExpression& _value, BString* _basePackage)
432 if (token.type != TOKEN_STRING) {
433 throw ParseError("expected word (a resolvable name)",
434 token.pos);
437 int32 errorPos;
438 if (!_IsValidResolvableName(token.text, &errorPos)) {
439 throw ParseError("invalid character in resolvable name",
440 token.pos + errorPos);
443 BPackageVersion version;
444 Token op = _NextToken();
445 BPackageResolvableOperator resolvableOperator;
446 if (op.type == TOKEN_OPERATOR_LESS
447 || op.type == TOKEN_OPERATOR_LESS_EQUAL
448 || op.type == TOKEN_OPERATOR_EQUAL
449 || op.type == TOKEN_OPERATOR_NOT_EQUAL
450 || op.type == TOKEN_OPERATOR_GREATER_EQUAL
451 || op.type == TOKEN_OPERATOR_GREATER) {
452 _ParseVersionValue(&version, true);
454 if (_basePackage != NULL) {
455 Token base = _NextToken();
456 if (base.type == TOKEN_STRING && base.text == "base") {
457 if (!_basePackage->IsEmpty()) {
458 throw ParseError("multiple packages marked as base package",
459 token.pos);
462 *_basePackage = token.text;
463 } else
464 _RewindTo(base);
467 resolvableOperator = (BPackageResolvableOperator)
468 (op.type - TOKEN_OPERATOR_LESS);
469 } else if (op.type == TOKEN_ITEM_SEPARATOR
470 || op.type == TOKEN_CLOSE_BRACE || op.type == TOKEN_EOF) {
471 _RewindTo(op);
472 resolvableOperator = B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT;
473 } else {
474 throw ParseError(
475 "expected '<', '<=', '==', '!=', '>=', '>', comma or '}'",
476 op.pos);
479 _value.SetTo(token.text, resolvableOperator, version);
483 void
484 BPackageInfo::Parser::_ParseList(ListElementParser& elementParser,
485 bool allowSingleNonListElement)
487 Token openBracket = _NextToken();
488 if (openBracket.type != TOKEN_OPEN_BRACE) {
489 if (!allowSingleNonListElement)
490 throw ParseError("expected start of list ('{')", openBracket.pos);
492 elementParser(openBracket);
493 return;
496 while (true) {
497 Token token = _NextToken();
498 if (token.type == TOKEN_CLOSE_BRACE)
499 return;
501 if (token.type == TOKEN_ITEM_SEPARATOR)
502 continue;
504 elementParser(token);
509 void
510 BPackageInfo::Parser::_ParseStringList(BStringList* value,
511 bool requireResolvableName, bool convertToLowerCase)
513 struct StringParser : public ListElementParser {
514 BStringList* value;
515 bool requireResolvableName;
516 bool convertToLowerCase;
518 StringParser(BStringList* value, bool requireResolvableName,
519 bool convertToLowerCase)
521 value(value),
522 requireResolvableName(requireResolvableName),
523 convertToLowerCase(convertToLowerCase)
527 virtual void operator()(const Token& token)
529 if (token.type != TOKEN_STRING)
530 throw ParseError("expected string", token.pos);
532 if (requireResolvableName) {
533 int32 errorPos;
534 if (!_IsValidResolvableName(token.text, &errorPos)) {
535 throw ParseError("invalid character in resolvable name",
536 token.pos + errorPos);
540 BString element(token.text);
541 if (convertToLowerCase)
542 element.ToLower();
544 value->Add(element);
546 } stringParser(value, requireResolvableName, convertToLowerCase);
548 _ParseList(stringParser, true);
552 uint32
553 BPackageInfo::Parser::_ParseFlags()
555 struct FlagParser : public ListElementParser {
556 uint32 flags;
558 FlagParser()
560 flags(0)
564 virtual void operator()(const Token& token)
566 if (token.type != TOKEN_STRING)
567 throw ParseError("expected word (a flag)", token.pos);
569 if (token.text.ICompare("approve_license") == 0)
570 flags |= B_PACKAGE_FLAG_APPROVE_LICENSE;
571 else if (token.text.ICompare("system_package") == 0)
572 flags |= B_PACKAGE_FLAG_SYSTEM_PACKAGE;
573 else {
574 throw ParseError(
575 "expected 'approve_license' or 'system_package'",
576 token.pos);
579 } flagParser;
581 _ParseList(flagParser, true);
583 return flagParser.flags;
587 void
588 BPackageInfo::Parser::_ParseResolvableList(
589 BObjectList<BPackageResolvable>* value)
591 struct ResolvableParser : public ListElementParser {
592 Parser& parser;
593 BObjectList<BPackageResolvable>* value;
595 ResolvableParser(Parser& parser_,
596 BObjectList<BPackageResolvable>* value_)
598 parser(parser_),
599 value(value_)
603 virtual void operator()(const Token& token)
605 if (token.type != TOKEN_STRING) {
606 throw ParseError("expected word (a resolvable name)",
607 token.pos);
610 int32 errorPos;
611 if (!_IsValidResolvableName(token.text, &errorPos)) {
612 throw ParseError("invalid character in resolvable name",
613 token.pos + errorPos);
616 // parse version
617 BPackageVersion version;
618 Token op = parser._NextToken();
619 if (op.type == TOKEN_OPERATOR_ASSIGN) {
620 parser._ParseVersionValue(&version, true);
621 } else if (op.type == TOKEN_ITEM_SEPARATOR
622 || op.type == TOKEN_CLOSE_BRACE) {
623 parser._RewindTo(op);
624 } else
625 throw ParseError("expected '=', comma or '}'", op.pos);
627 // parse compatible version
628 BPackageVersion compatibleVersion;
629 Token compatible = parser._NextToken();
630 if (compatible.type == TOKEN_STRING
631 && (compatible.text == "compat"
632 || compatible.text == "compatible")) {
633 op = parser._NextToken();
634 if (op.type == TOKEN_OPERATOR_GREATER_EQUAL) {
635 parser._ParseVersionValue(&compatibleVersion, true);
636 } else
637 parser._RewindTo(compatible);
638 } else
639 parser._RewindTo(compatible);
641 value->AddItem(new BPackageResolvable(token.text, version,
642 compatibleVersion));
644 } resolvableParser(*this, value);
646 _ParseList(resolvableParser, false);
650 void
651 BPackageInfo::Parser::_ParseResolvableExprList(
652 BObjectList<BPackageResolvableExpression>* value, BString* _basePackage)
654 struct ResolvableExpressionParser : public ListElementParser {
655 Parser& parser;
656 BObjectList<BPackageResolvableExpression>* value;
657 BString* basePackage;
659 ResolvableExpressionParser(Parser& parser,
660 BObjectList<BPackageResolvableExpression>* value,
661 BString* basePackage)
663 parser(parser),
664 value(value),
665 basePackage(basePackage)
669 virtual void operator()(const Token& token)
671 BPackageResolvableExpression expression;
672 parser._ParseResolvableExpression(token, expression, basePackage);
673 value->AddItem(new BPackageResolvableExpression(expression));
675 } resolvableExpressionParser(*this, value, _basePackage);
677 _ParseList(resolvableExpressionParser, false);
681 void
682 BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
683 GlobalWritableFileInfoList* infos)
685 struct GlobalWritableFileInfoParser : public ListElementParser {
686 Parser& parser;
687 GlobalWritableFileInfoList* infos;
689 GlobalWritableFileInfoParser(Parser& parser,
690 GlobalWritableFileInfoList* infos)
692 parser(parser),
693 infos(infos)
697 virtual void operator()(const Token& token)
699 if (token.type != TOKEN_STRING) {
700 throw ParseError("expected string (a file path)",
701 token.pos);
704 BWritableFileUpdateType updateType
705 = B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
706 bool isDirectory = false;
708 Token nextToken = parser._NextToken();
709 if (nextToken.type == TOKEN_STRING
710 && nextToken.text == "directory") {
711 isDirectory = true;
712 nextToken = parser._NextToken();
715 if (nextToken.type == TOKEN_STRING) {
716 const char* const* end = kWritableFileUpdateTypes
717 + B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
718 const char* const* found = std::find(kWritableFileUpdateTypes,
719 end, nextToken.text);
720 if (found == end) {
721 throw ParseError(BString("expected an update type"),
722 nextToken.pos);
724 updateType = (BWritableFileUpdateType)(
725 found - kWritableFileUpdateTypes);
726 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
727 || nextToken.type == TOKEN_CLOSE_BRACE) {
728 parser._RewindTo(nextToken);
729 } else {
730 throw ParseError(
731 "expected 'included', semicolon, new line or '}'",
732 nextToken.pos);
735 if (!infos->AddItem(new BGlobalWritableFileInfo(token.text,
736 updateType, isDirectory))) {
737 throw std::bad_alloc();
740 } resolvableExpressionParser(*this, infos);
742 _ParseList(resolvableExpressionParser, false);
746 void
747 BPackageInfo::Parser::_ParseUserSettingsFileInfos(
748 UserSettingsFileInfoList* infos)
750 struct UserSettingsFileInfoParser : public ListElementParser {
751 Parser& parser;
752 UserSettingsFileInfoList* infos;
754 UserSettingsFileInfoParser(Parser& parser,
755 UserSettingsFileInfoList* infos)
757 parser(parser),
758 infos(infos)
762 virtual void operator()(const Token& token)
764 if (token.type != TOKEN_STRING) {
765 throw ParseError("expected string (a settings file path)",
766 token.pos);
769 BString templatePath;
770 bool isDirectory = false;
772 Token nextToken = parser._NextToken();
773 if (nextToken.type == TOKEN_STRING
774 && nextToken.text == "directory") {
775 isDirectory = true;
776 } else if (nextToken.type == TOKEN_STRING
777 && nextToken.text == "template") {
778 nextToken = parser._NextToken();
779 if (nextToken.type != TOKEN_STRING) {
780 throw ParseError(
781 "expected string (a settings template file path)",
782 nextToken.pos);
784 templatePath = nextToken.text;
785 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
786 || nextToken.type == TOKEN_CLOSE_BRACE) {
787 parser._RewindTo(nextToken);
788 } else {
789 throw ParseError(
790 "expected 'template', semicolon, new line or '}'",
791 nextToken.pos);
794 if (isDirectory
795 ? !infos->AddItem(new BUserSettingsFileInfo(token.text, true))
796 : !infos->AddItem(new BUserSettingsFileInfo(token.text,
797 templatePath))) {
798 throw std::bad_alloc();
801 } resolvableExpressionParser(*this, infos);
803 _ParseList(resolvableExpressionParser, false);
807 void
808 BPackageInfo::Parser::_ParseUsers(UserList* users)
810 struct UserParser : public ListElementParser {
811 Parser& parser;
812 UserList* users;
814 UserParser(Parser& parser, UserList* users)
816 parser(parser),
817 users(users)
821 virtual void operator()(const Token& token)
823 if (token.type != TOKEN_STRING
824 || !BUser::IsValidUserName(token.text)) {
825 throw ParseError("expected a user name", token.pos);
828 BString realName;
829 BString home;
830 BString shell;
831 BStringList groups;
833 for (;;) {
834 Token nextToken = parser._NextToken();
835 if (nextToken.type != TOKEN_STRING) {
836 parser._RewindTo(nextToken);
837 break;
840 if (nextToken.text == "real-name") {
841 nextToken = parser._NextToken();
842 if (nextToken.type != TOKEN_STRING) {
843 throw ParseError("expected string (a user real name)",
844 nextToken.pos);
846 realName = nextToken.text;
847 } else if (nextToken.text == "home") {
848 nextToken = parser._NextToken();
849 if (nextToken.type != TOKEN_STRING) {
850 throw ParseError("expected string (a home path)",
851 nextToken.pos);
853 home = nextToken.text;
854 } else if (nextToken.text == "shell") {
855 nextToken = parser._NextToken();
856 if (nextToken.type != TOKEN_STRING) {
857 throw ParseError("expected string (a shell path)",
858 nextToken.pos);
860 shell = nextToken.text;
861 } else if (nextToken.text == "groups") {
862 for (;;) {
863 nextToken = parser._NextToken();
864 if (nextToken.type == TOKEN_STRING
865 && BUser::IsValidUserName(nextToken.text)) {
866 if (!groups.Add(nextToken.text))
867 throw std::bad_alloc();
868 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
869 || nextToken.type == TOKEN_CLOSE_BRACE) {
870 parser._RewindTo(nextToken);
871 break;
872 } else {
873 throw ParseError("expected a group name",
874 nextToken.pos);
877 break;
878 } else {
879 throw ParseError(
880 "expected 'real-name', 'home', 'shell', or 'groups'",
881 nextToken.pos);
885 BString templatePath;
887 Token nextToken = parser._NextToken();
888 if (nextToken.type == TOKEN_STRING
889 && nextToken.text == "template") {
890 nextToken = parser._NextToken();
891 if (nextToken.type != TOKEN_STRING) {
892 throw ParseError(
893 "expected string (a settings template file path)",
894 nextToken.pos);
896 templatePath = nextToken.text;
897 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
898 || nextToken.type == TOKEN_CLOSE_BRACE) {
899 parser._RewindTo(nextToken);
900 } else {
901 throw ParseError(
902 "expected 'template', semicolon, new line or '}'",
903 nextToken.pos);
906 if (!users->AddItem(new BUser(token.text, realName, home, shell,
907 groups))) {
908 throw std::bad_alloc();
911 } resolvableExpressionParser(*this, users);
913 _ParseList(resolvableExpressionParser, false);
917 void
918 BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo)
920 bool seen[B_PACKAGE_INFO_ENUM_COUNT];
921 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; ++i)
922 seen[i] = false;
924 const char* const* names = BPackageInfo::kElementNames;
926 while (Token t = _NextToken()) {
927 if (t.type == TOKEN_ITEM_SEPARATOR)
928 continue;
930 if (t.type != TOKEN_STRING)
931 throw ParseError("expected string (a variable name)", t.pos);
933 BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT;
934 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) {
935 if (names[i] != NULL && t.text.ICompare(names[i]) == 0) {
936 attribute = (BPackageInfoAttributeID)i;
937 break;
941 if (attribute == B_PACKAGE_INFO_ENUM_COUNT) {
942 BString error = BString("unknown attribute \"") << t.text << '"';
943 throw ParseError(error, t.pos);
946 if (seen[attribute]) {
947 BString error = BString(names[attribute]) << " already seen!";
948 throw ParseError(error, t.pos);
951 switch (attribute) {
952 case B_PACKAGE_INFO_NAME:
954 BString name;
955 const char* namePos;
956 _ParseStringValue(&name, &namePos);
958 int32 errorPos;
959 if (!_IsValidResolvableName(name, &errorPos)) {
960 throw ParseError("invalid character in package name",
961 namePos + errorPos);
964 packageInfo->SetName(name);
965 break;
968 case B_PACKAGE_INFO_SUMMARY:
970 BString summary;
971 _ParseStringValue(&summary);
972 if (summary.FindFirst('\n') >= 0)
973 throw ParseError("the summary contains linebreaks", t.pos);
974 packageInfo->SetSummary(summary);
975 break;
978 case B_PACKAGE_INFO_DESCRIPTION:
979 _ParseStringValue(&packageInfo->fDescription);
980 break;
982 case B_PACKAGE_INFO_VENDOR:
983 _ParseStringValue(&packageInfo->fVendor);
984 break;
986 case B_PACKAGE_INFO_PACKAGER:
987 _ParseStringValue(&packageInfo->fPackager);
988 break;
990 case B_PACKAGE_INFO_BASE_PACKAGE:
991 _ParseStringValue(&packageInfo->fBasePackage);
992 break;
994 case B_PACKAGE_INFO_ARCHITECTURE:
995 _ParseArchitectureValue(&packageInfo->fArchitecture);
996 break;
998 case B_PACKAGE_INFO_VERSION:
999 _ParseVersionValue(&packageInfo->fVersion, false);
1000 break;
1002 case B_PACKAGE_INFO_COPYRIGHTS:
1003 _ParseStringList(&packageInfo->fCopyrightList);
1004 break;
1006 case B_PACKAGE_INFO_LICENSES:
1007 _ParseStringList(&packageInfo->fLicenseList);
1008 break;
1010 case B_PACKAGE_INFO_URLS:
1011 _ParseStringList(&packageInfo->fURLList);
1012 break;
1014 case B_PACKAGE_INFO_SOURCE_URLS:
1015 _ParseStringList(&packageInfo->fSourceURLList);
1016 break;
1018 case B_PACKAGE_INFO_GLOBAL_WRITABLE_FILES:
1019 _ParseGlobalWritableFileInfos(
1020 &packageInfo->fGlobalWritableFileInfos);
1021 break;
1023 case B_PACKAGE_INFO_USER_SETTINGS_FILES:
1024 _ParseUserSettingsFileInfos(
1025 &packageInfo->fUserSettingsFileInfos);
1026 break;
1028 case B_PACKAGE_INFO_USERS:
1029 _ParseUsers(&packageInfo->fUsers);
1030 break;
1032 case B_PACKAGE_INFO_GROUPS:
1033 _ParseStringList(&packageInfo->fGroups);
1034 break;
1036 case B_PACKAGE_INFO_POST_INSTALL_SCRIPTS:
1037 _ParseStringList(&packageInfo->fPostInstallScripts);
1038 break;
1040 case B_PACKAGE_INFO_PROVIDES:
1041 _ParseResolvableList(&packageInfo->fProvidesList);
1042 break;
1044 case B_PACKAGE_INFO_REQUIRES:
1045 packageInfo->fBasePackage.Truncate(0);
1046 _ParseResolvableExprList(&packageInfo->fRequiresList,
1047 &packageInfo->fBasePackage);
1048 break;
1050 case B_PACKAGE_INFO_SUPPLEMENTS:
1051 _ParseResolvableExprList(&packageInfo->fSupplementsList);
1052 break;
1054 case B_PACKAGE_INFO_CONFLICTS:
1055 _ParseResolvableExprList(&packageInfo->fConflictsList);
1056 break;
1058 case B_PACKAGE_INFO_FRESHENS:
1059 _ParseResolvableExprList(&packageInfo->fFreshensList);
1060 break;
1062 case B_PACKAGE_INFO_REPLACES:
1063 _ParseStringList(&packageInfo->fReplacesList, true);
1064 break;
1066 case B_PACKAGE_INFO_FLAGS:
1067 packageInfo->SetFlags(_ParseFlags());
1068 break;
1070 default:
1071 // can never get here
1072 break;
1075 seen[attribute] = true;
1078 // everything up to and including 'provides' is mandatory
1079 for (int i = 0; i <= B_PACKAGE_INFO_PROVIDES; ++i) {
1080 if (!seen[i]) {
1081 BString error = BString(names[i]) << " is not being set anywhere!";
1082 throw ParseError(error, fPos);
1088 /*static*/ inline bool
1089 BPackageInfo::Parser::_IsAlphaNumUnderscore(const BString& string,
1090 const char* additionalChars, int32* _errorPos)
1092 return _IsAlphaNumUnderscore(string.String(),
1093 string.String() + string.Length(), additionalChars, _errorPos);
1097 /*static*/ inline bool
1098 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* string,
1099 const char* additionalChars, int32* _errorPos)
1101 return _IsAlphaNumUnderscore(string, string + strlen(string),
1102 additionalChars, _errorPos);
1106 /*static*/ bool
1107 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* start, const char* end,
1108 const char* additionalChars, int32* _errorPos)
1110 for (const char* c = start; c < end; c++) {
1111 if (!isalnum(*c) && *c != '_' && strchr(additionalChars, *c) == NULL) {
1112 if (_errorPos != NULL)
1113 *_errorPos = c - start;
1114 return false;
1118 return true;
1122 /*static*/ bool
1123 BPackageInfo::Parser::_IsValidResolvableName(const char* string,
1124 int32* _errorPos)
1126 for (const char* c = string; *c != '\0'; c++) {
1127 switch (*c) {
1128 case '-':
1129 case '/':
1130 case '<':
1131 case '>':
1132 case '=':
1133 case '!':
1134 break;
1135 default:
1136 if (!isspace(*c))
1137 continue;
1138 break;
1141 if (_errorPos != NULL)
1142 *_errorPos = c - string;
1143 return false;
1145 return true;
1149 } // namespace BPackageKit