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 */
6
7
8 #include "PackageInfoParser.h"
9
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdlib.h>
13
14 #include <algorithm>
15 #include <string>
16
17 #include <Url.h>
18
19 namespace BPackageKit {
20
21
~ParseErrorListener()22 BPackageInfo::ParseErrorListener::~ParseErrorListener()
23 {
24 }
25
26
Parser(ParseErrorListener * listener)27 BPackageInfo::Parser::Parser(ParseErrorListener* listener)
28 :
29 fListener(listener),
30 fPos(NULL)
31 {
32 }
33
34
35 status_t
Parse(const BString & packageInfoString,BPackageInfo * packageInfo)36 BPackageInfo::Parser::Parse(const BString& packageInfoString,
37 BPackageInfo* packageInfo)
38 {
39 if (packageInfo == NULL)
40 return B_BAD_VALUE;
41
42 fPos = packageInfoString.String();
43
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);
62 }
63
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;
69 }
70
71 fListener->OnError(error.message, line, column + 1);
72 }
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;
78 }
79
80 return B_OK;
81 }
82
83
84 status_t
ParseVersion(const BString & versionString,bool revisionIsOptional,BPackageVersion & _version)85 BPackageInfo::Parser::ParseVersion(const BString& versionString,
86 bool revisionIsOptional, BPackageVersion& _version)
87 {
88 fPos = versionString.String();
89
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);
97 }
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;
103 }
104
105 return B_OK;
106 }
107
108
109 status_t
ParseResolvable(const BString & expressionString,BPackageResolvable & _expression)110 BPackageInfo::Parser::ParseResolvable(const BString& expressionString,
111 BPackageResolvable& _expression)
112 {
113 fPos = expressionString.String();
114
115 try {
116 Token token(TOKEN_STRING, fPos, expressionString.Length());
117 _ParseResolvable(_NextToken(), _expression);
118 } catch (const ParseError& error) {
119 if (fListener != NULL) {
120 int32 offset = error.pos - expressionString.String();
121 fListener->OnError(error.message, 1, offset);
122 }
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;
128 }
129
130 return B_OK;
131 }
132
133
134 status_t
ParseResolvableExpression(const BString & expressionString,BPackageResolvableExpression & _expression)135 BPackageInfo::Parser::ParseResolvableExpression(const BString& expressionString,
136 BPackageResolvableExpression& _expression)
137 {
138 fPos = expressionString.String();
139
140 try {
141 Token token(TOKEN_STRING, fPos, expressionString.Length());
142 _ParseResolvableExpression(_NextToken(), _expression, NULL);
143 } catch (const ParseError& error) {
144 if (fListener != NULL) {
145 int32 offset = error.pos - expressionString.String();
146 fListener->OnError(error.message, 1, offset);
147 }
148 return B_BAD_DATA;
149 } catch (const std::bad_alloc& e) {
150 if (fListener != NULL)
151 fListener->OnError("out of memory", 0, 0);
152 return B_NO_MEMORY;
153 }
154
155 return B_OK;
156 }
157
158
159 BPackageInfo::Parser::Token
_NextToken()160 BPackageInfo::Parser::_NextToken()
161 {
162 // Eat any whitespace, comments, or escaped new lines. Also eat ';' -- they
163 // have the same function as newlines. We remember the last encountered ';'
164 // or '\n' and return it as a token afterwards.
165 const char* itemSeparatorPos = NULL;
166 bool inComment = false;
167 while ((inComment && *fPos != '\0') || isspace(*fPos) || *fPos == ';'
168 || *fPos == '#' || *fPos == '\\') {
169 if (*fPos == '#') {
170 inComment = true;
171 } else if (!inComment && *fPos == '\\') {
172 if (fPos[1] != '\n')
173 break;
174 // ignore escaped line breaks
175 fPos++;
176 } else if (*fPos == '\n') {
177 itemSeparatorPos = fPos;
178 inComment = false;
179 } else if (!inComment && *fPos == ';')
180 itemSeparatorPos = fPos;
181 fPos++;
182 }
183
184 if (itemSeparatorPos != NULL) {
185 return Token(TOKEN_ITEM_SEPARATOR, itemSeparatorPos);
186 }
187
188 const char* tokenPos = fPos;
189 switch (*fPos) {
190 case '\0':
191 return Token(TOKEN_EOF, fPos);
192
193 case '{':
194 fPos++;
195 return Token(TOKEN_OPEN_BRACE, tokenPos);
196
197 case '}':
198 fPos++;
199 return Token(TOKEN_CLOSE_BRACE, tokenPos);
200
201 case '<':
202 fPos++;
203 if (*fPos == '=') {
204 fPos++;
205 return Token(TOKEN_OPERATOR_LESS_EQUAL, tokenPos, 2);
206 }
207 return Token(TOKEN_OPERATOR_LESS, tokenPos, 1);
208
209 case '=':
210 fPos++;
211 if (*fPos == '=') {
212 fPos++;
213 return Token(TOKEN_OPERATOR_EQUAL, tokenPos, 2);
214 }
215 return Token(TOKEN_OPERATOR_ASSIGN, tokenPos, 1);
216
217 case '!':
218 if (fPos[1] == '=') {
219 fPos += 2;
220 return Token(TOKEN_OPERATOR_NOT_EQUAL, tokenPos, 2);
221 }
222 break;
223
224 case '>':
225 fPos++;
226 if (*fPos == '=') {
227 fPos++;
228 return Token(TOKEN_OPERATOR_GREATER_EQUAL, tokenPos, 2);
229 }
230 return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1);
231
232 default:
233 {
234 std::string string;
235 char quoteChar = '\0';
236
237 for (; *fPos != '\0'; fPos++) {
238 char c = *fPos;
239 if (quoteChar != '\0') {
240 // within a quoted string segment
241 if (c == quoteChar) {
242 quoteChar = '\0';
243 continue;
244 }
245
246 if (c == '\\') {
247 // next char is escaped
248 c = *++fPos;
249 if (c == '\0') {
250 throw ParseError("unterminated quoted-string",
251 tokenPos);
252 }
253
254 if (c == 'n')
255 c = '\n';
256 else if (c == 't')
257 c = '\t';
258 }
259
260 string += c;
261 } else {
262 // unquoted string segment
263 switch (c) {
264 case '"':
265 case '\'':
266 // quoted string start
267 quoteChar = c;
268 continue;
269
270 case '{':
271 case '}':
272 case '<':
273 case '=':
274 case '!':
275 case '>':
276 // a separator character -- this ends the string
277 break;
278
279 case '\\':
280 // next char is escaped
281 c = *++fPos;
282 if (c == '\0') {
283 throw ParseError("'\\' at end of string",
284 tokenPos);
285 }
286 string += c;
287 continue;
288
289 default:
290 if (isspace(c))
291 break;
292 string += c;
293 continue;
294 }
295
296 break;
297 }
298 }
299
300 return Token(TOKEN_STRING, tokenPos, fPos - tokenPos,
301 string.c_str());
302 }
303 }
304
305 BString error = BString("unknown token '") << *fPos << "' encountered";
306 throw ParseError(error.String(), fPos);
307 }
308
309
310 void
_RewindTo(const Token & token)311 BPackageInfo::Parser::_RewindTo(const Token& token)
312 {
313 fPos = token.pos;
314 }
315
316
317 void
_ParseStringValue(BString * value,const char ** _tokenPos)318 BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos)
319 {
320 Token string = _NextToken();
321 if (string.type != TOKEN_STRING)
322 throw ParseError("expected string", string.pos);
323
324 *value = string.text;
325 if (_tokenPos != NULL)
326 *_tokenPos = string.pos;
327 }
328
329
330 void
_ParseArchitectureValue(BPackageArchitecture * value)331 BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value)
332 {
333 Token arch = _NextToken();
334 if (arch.type == TOKEN_STRING) {
335 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
336 if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) {
337 *value = (BPackageArchitecture)i;
338 return;
339 }
340 }
341 }
342
343 BString error("architecture must be one of: [");
344 for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
345 if (i > 0)
346 error << ",";
347 error << BPackageInfo::kArchitectureNames[i];
348 }
349 error << "]";
350 throw ParseError(error, arch.pos);
351 }
352
353
354 void
_ParseVersionValue(BPackageVersion * value,bool revisionIsOptional)355 BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
356 bool revisionIsOptional)
357 {
358 Token word = _NextToken();
359 _ParseVersionValue(word, value, revisionIsOptional);
360 }
361
362
363 /*static*/ void
_ParseVersionValue(Token & word,BPackageVersion * value,bool revisionIsOptional)364 BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value,
365 bool revisionIsOptional)
366 {
367 if (word.type != TOKEN_STRING)
368 throw ParseError("expected string (a version)", word.pos);
369
370 // get the revision number
371 uint32 revision = 0;
372 int32 dashPos = word.text.FindLast('-');
373 if (dashPos >= 0) {
374 char* end;
375 long long number = strtoll(word.text.String() + dashPos + 1, &end,
376 0);
377 if (*end != '\0' || number < 0 || number > UINT_MAX) {
378 throw ParseError("revision must be a number > 0 and < UINT_MAX",
379 word.pos + dashPos + 1);
380 }
381
382 revision = (uint32)number;
383 word.text.Truncate(dashPos);
384 }
385
386 if (revision == 0 && !revisionIsOptional) {
387 throw ParseError("expected revision number (-<number> suffix)",
388 word.pos + word.text.Length());
389 }
390
391 // get the pre-release string
392 BString preRelease;
393 int32 tildePos = word.text.FindLast('~');
394 if (tildePos >= 0) {
395 word.text.CopyInto(preRelease, tildePos + 1,
396 word.text.Length() - tildePos - 1);
397 word.text.Truncate(tildePos);
398
399 if (preRelease.IsEmpty()) {
400 throw ParseError("invalid empty pre-release string",
401 word.pos + tildePos + 1);
402 }
403
404 int32 errorPos;
405 if (!_IsAlphaNumUnderscore(preRelease, ".", &errorPos)) {
406 throw ParseError("invalid character in pre-release string",
407 word.pos + tildePos + 1 + errorPos);
408 }
409 }
410
411 // get major, minor, and micro strings
412 BString major;
413 BString minor;
414 BString micro;
415 int32 firstDotPos = word.text.FindFirst('.');
416 if (firstDotPos < 0)
417 major = word.text;
418 else {
419 word.text.CopyInto(major, 0, firstDotPos);
420 int32 secondDotPos = word.text.FindFirst('.', firstDotPos + 1);
421 if (secondDotPos == firstDotPos + 1)
422 throw ParseError("expected minor version", word.pos + secondDotPos);
423
424 if (secondDotPos < 0) {
425 word.text.CopyInto(minor, firstDotPos + 1, word.text.Length());
426 } else {
427 word.text.CopyInto(minor, firstDotPos + 1,
428 secondDotPos - (firstDotPos + 1));
429 word.text.CopyInto(micro, secondDotPos + 1, word.text.Length());
430
431 int32 errorPos;
432 if (!_IsAlphaNumUnderscore(micro, ".", &errorPos)) {
433 throw ParseError("invalid character in micro version string",
434 word.pos + secondDotPos + 1 + errorPos);
435 }
436 }
437
438 int32 errorPos;
439 if (!_IsAlphaNumUnderscore(minor, "", &errorPos)) {
440 throw ParseError("invalid character in minor version string",
441 word.pos + firstDotPos + 1 + errorPos);
442 }
443 }
444
445 int32 errorPos;
446 if (!_IsAlphaNumUnderscore(major, "", &errorPos)) {
447 throw ParseError("invalid character in major version string",
448 word.pos + errorPos);
449 }
450
451 value->SetTo(major, minor, micro, preRelease, revision);
452 }
453
454
455 void
_ParseResolvable(const Token & token,BPackageResolvable & _value)456 BPackageInfo::Parser::_ParseResolvable(const Token& token,
457 BPackageResolvable& _value)
458 {
459 if (token.type != TOKEN_STRING) {
460 throw ParseError("expected word (a resolvable name)",
461 token.pos);
462 }
463
464 int32 errorPos;
465 if (!_IsValidResolvableName(token.text, &errorPos)) {
466 throw ParseError("invalid character in resolvable name",
467 token.pos + errorPos);
468 }
469
470 // parse version
471 BPackageVersion version;
472 Token op = _NextToken();
473 if (op.type == TOKEN_OPERATOR_ASSIGN) {
474 _ParseVersionValue(&version, true);
475 } else if (op.type == TOKEN_ITEM_SEPARATOR
476 || op.type == TOKEN_CLOSE_BRACE || op.type == TOKEN_EOF) {
477 _RewindTo(op);
478 } else
479 throw ParseError("expected '=', comma or '}'", op.pos);
480
481 // parse compatible version
482 BPackageVersion compatibleVersion;
483 Token compatible = _NextToken();
484 if (compatible.type == TOKEN_STRING
485 && (compatible.text == "compat"
486 || compatible.text == "compatible")) {
487 op = _NextToken();
488 if (op.type == TOKEN_OPERATOR_GREATER_EQUAL) {
489 _ParseVersionValue(&compatibleVersion, true);
490 } else
491 _RewindTo(compatible);
492 } else
493 _RewindTo(compatible);
494
495 _value.SetTo(token.text, version, compatibleVersion);
496 }
497
498
499 void
_ParseResolvableExpression(const Token & token,BPackageResolvableExpression & _value,BString * _basePackage)500 BPackageInfo::Parser::_ParseResolvableExpression(const Token& token,
501 BPackageResolvableExpression& _value, BString* _basePackage)
502 {
503 if (token.type != TOKEN_STRING) {
504 throw ParseError("expected word (a resolvable name)",
505 token.pos);
506 }
507
508 int32 errorPos;
509 if (!_IsValidResolvableName(token.text, &errorPos)) {
510 throw ParseError("invalid character in resolvable name",
511 token.pos + errorPos);
512 }
513
514 BPackageVersion version;
515 Token op = _NextToken();
516 BPackageResolvableOperator resolvableOperator;
517 if (op.type == TOKEN_OPERATOR_LESS
518 || op.type == TOKEN_OPERATOR_LESS_EQUAL
519 || op.type == TOKEN_OPERATOR_EQUAL
520 || op.type == TOKEN_OPERATOR_NOT_EQUAL
521 || op.type == TOKEN_OPERATOR_GREATER_EQUAL
522 || op.type == TOKEN_OPERATOR_GREATER) {
523 _ParseVersionValue(&version, true);
524
525 if (_basePackage != NULL) {
526 Token base = _NextToken();
527 if (base.type == TOKEN_STRING && base.text == "base") {
528 if (!_basePackage->IsEmpty()) {
529 throw ParseError("multiple packages marked as base package",
530 token.pos);
531 }
532
533 *_basePackage = token.text;
534 } else
535 _RewindTo(base);
536 }
537
538 resolvableOperator = (BPackageResolvableOperator)
539 (op.type - TOKEN_OPERATOR_LESS);
540 } else if (op.type == TOKEN_ITEM_SEPARATOR
541 || op.type == TOKEN_CLOSE_BRACE || op.type == TOKEN_EOF) {
542 _RewindTo(op);
543 resolvableOperator = B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT;
544 } else {
545 throw ParseError(
546 "expected '<', '<=', '==', '!=', '>=', '>', comma or '}'",
547 op.pos);
548 }
549
550 _value.SetTo(token.text, resolvableOperator, version);
551 }
552
553
554 void
_ParseList(ListElementParser & elementParser,bool allowSingleNonListElement)555 BPackageInfo::Parser::_ParseList(ListElementParser& elementParser,
556 bool allowSingleNonListElement)
557 {
558 Token openBracket = _NextToken();
559 if (openBracket.type != TOKEN_OPEN_BRACE) {
560 if (!allowSingleNonListElement)
561 throw ParseError("expected start of list ('{')", openBracket.pos);
562
563 elementParser(openBracket);
564 return;
565 }
566
567 while (true) {
568 Token token = _NextToken();
569 if (token.type == TOKEN_CLOSE_BRACE)
570 return;
571
572 if (token.type == TOKEN_ITEM_SEPARATOR)
573 continue;
574
575 elementParser(token);
576 }
577 }
578
579
580 void
_ParseStringList(BStringList * value,bool requireResolvableName,bool convertToLowerCase,StringValidator * stringValidator)581 BPackageInfo::Parser::_ParseStringList(BStringList* value,
582 bool requireResolvableName, bool convertToLowerCase,
583 StringValidator* stringValidator)
584 {
585 struct StringParser : public ListElementParser {
586 BStringList* value;
587 bool requireResolvableName;
588 bool convertToLowerCase;
589 StringValidator* stringValidator;
590
591 StringParser(BStringList* value, bool requireResolvableName,
592 bool convertToLowerCase, StringValidator* stringValidator)
593 :
594 value(value),
595 requireResolvableName(requireResolvableName),
596 convertToLowerCase(convertToLowerCase),
597 stringValidator(stringValidator)
598 {
599 }
600
601 virtual void operator()(const Token& token)
602 {
603 if (token.type != TOKEN_STRING)
604 throw ParseError("expected string", token.pos);
605
606 if (requireResolvableName) {
607 int32 errorPos;
608 if (!_IsValidResolvableName(token.text, &errorPos)) {
609 throw ParseError("invalid character in resolvable name",
610 token.pos + errorPos);
611 }
612 }
613
614 BString element(token.text);
615 if (convertToLowerCase)
616 element.ToLower();
617
618 if (stringValidator != NULL)
619 stringValidator->Validate(element, token.pos);
620
621 value->Add(element);
622 }
623 } stringParser(value, requireResolvableName, convertToLowerCase,
624 stringValidator);
625
626 _ParseList(stringParser, true);
627 }
628
629
630 uint32
_ParseFlags()631 BPackageInfo::Parser::_ParseFlags()
632 {
633 struct FlagParser : public ListElementParser {
634 uint32 flags;
635
636 FlagParser()
637 :
638 flags(0)
639 {
640 }
641
642 virtual void operator()(const Token& token)
643 {
644 if (token.type != TOKEN_STRING)
645 throw ParseError("expected word (a flag)", token.pos);
646
647 if (token.text.ICompare("approve_license") == 0)
648 flags |= B_PACKAGE_FLAG_APPROVE_LICENSE;
649 else if (token.text.ICompare("system_package") == 0)
650 flags |= B_PACKAGE_FLAG_SYSTEM_PACKAGE;
651 else {
652 throw ParseError(
653 "expected 'approve_license' or 'system_package'",
654 token.pos);
655 }
656 }
657 } flagParser;
658
659 _ParseList(flagParser, true);
660
661 return flagParser.flags;
662 }
663
664
665 void
_ParseResolvableList(BObjectList<BPackageResolvable> * value)666 BPackageInfo::Parser::_ParseResolvableList(
667 BObjectList<BPackageResolvable>* value)
668 {
669 struct ResolvableParser : public ListElementParser {
670 Parser& parser;
671 BObjectList<BPackageResolvable>* value;
672
673 ResolvableParser(Parser& parser_,
674 BObjectList<BPackageResolvable>* value_)
675 :
676 parser(parser_),
677 value(value_)
678 {
679 }
680
681 virtual void operator()(const Token& token)
682 {
683 BPackageResolvable expression;
684 parser._ParseResolvable(token, expression);
685 value->AddItem(new BPackageResolvable(expression));
686 }
687 } resolvableParser(*this, value);
688
689 _ParseList(resolvableParser, false);
690 }
691
692
693 void
_ParseResolvableExprList(BObjectList<BPackageResolvableExpression> * value,BString * _basePackage)694 BPackageInfo::Parser::_ParseResolvableExprList(
695 BObjectList<BPackageResolvableExpression>* value, BString* _basePackage)
696 {
697 struct ResolvableExpressionParser : public ListElementParser {
698 Parser& parser;
699 BObjectList<BPackageResolvableExpression>* value;
700 BString* basePackage;
701
702 ResolvableExpressionParser(Parser& parser,
703 BObjectList<BPackageResolvableExpression>* value,
704 BString* basePackage)
705 :
706 parser(parser),
707 value(value),
708 basePackage(basePackage)
709 {
710 }
711
712 virtual void operator()(const Token& token)
713 {
714 BPackageResolvableExpression expression;
715 parser._ParseResolvableExpression(token, expression, basePackage);
716 value->AddItem(new BPackageResolvableExpression(expression));
717 }
718 } resolvableExpressionParser(*this, value, _basePackage);
719
720 _ParseList(resolvableExpressionParser, false);
721 }
722
723
724 void
_ParseGlobalWritableFileInfos(GlobalWritableFileInfoList * infos)725 BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
726 GlobalWritableFileInfoList* infos)
727 {
728 struct GlobalWritableFileInfoParser : public ListElementParser {
729 Parser& parser;
730 GlobalWritableFileInfoList* infos;
731
732 GlobalWritableFileInfoParser(Parser& parser,
733 GlobalWritableFileInfoList* infos)
734 :
735 parser(parser),
736 infos(infos)
737 {
738 }
739
740 virtual void operator()(const Token& token)
741 {
742 if (token.type != TOKEN_STRING) {
743 throw ParseError("expected string (a file path)",
744 token.pos);
745 }
746
747 BWritableFileUpdateType updateType
748 = B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
749 bool isDirectory = false;
750
751 Token nextToken = parser._NextToken();
752 if (nextToken.type == TOKEN_STRING
753 && nextToken.text == "directory") {
754 isDirectory = true;
755 nextToken = parser._NextToken();
756 }
757
758 if (nextToken.type == TOKEN_STRING) {
759 const char* const* end = kWritableFileUpdateTypes
760 + B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
761 const char* const* found = std::find(kWritableFileUpdateTypes,
762 end, nextToken.text);
763 if (found == end) {
764 throw ParseError(BString("expected an update type"),
765 nextToken.pos);
766 }
767 updateType = (BWritableFileUpdateType)(
768 found - kWritableFileUpdateTypes);
769 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
770 || nextToken.type == TOKEN_CLOSE_BRACE) {
771 parser._RewindTo(nextToken);
772 } else {
773 throw ParseError(
774 "expected 'included', semicolon, new line or '}'",
775 nextToken.pos);
776 }
777
778 if (!infos->AddItem(new BGlobalWritableFileInfo(token.text,
779 updateType, isDirectory))) {
780 throw std::bad_alloc();
781 }
782 }
783 } resolvableExpressionParser(*this, infos);
784
785 _ParseList(resolvableExpressionParser, false);
786 }
787
788
789 void
_ParseUserSettingsFileInfos(UserSettingsFileInfoList * infos)790 BPackageInfo::Parser::_ParseUserSettingsFileInfos(
791 UserSettingsFileInfoList* infos)
792 {
793 struct UserSettingsFileInfoParser : public ListElementParser {
794 Parser& parser;
795 UserSettingsFileInfoList* infos;
796
797 UserSettingsFileInfoParser(Parser& parser,
798 UserSettingsFileInfoList* infos)
799 :
800 parser(parser),
801 infos(infos)
802 {
803 }
804
805 virtual void operator()(const Token& token)
806 {
807 if (token.type != TOKEN_STRING) {
808 throw ParseError("expected string (a settings file path)",
809 token.pos);
810 }
811
812 BString templatePath;
813 bool isDirectory = false;
814
815 Token nextToken = parser._NextToken();
816 if (nextToken.type == TOKEN_STRING
817 && nextToken.text == "directory") {
818 isDirectory = true;
819 } else if (nextToken.type == TOKEN_STRING
820 && nextToken.text == "template") {
821 nextToken = parser._NextToken();
822 if (nextToken.type != TOKEN_STRING) {
823 throw ParseError(
824 "expected string (a settings template file path)",
825 nextToken.pos);
826 }
827 templatePath = nextToken.text;
828 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
829 || nextToken.type == TOKEN_CLOSE_BRACE) {
830 parser._RewindTo(nextToken);
831 } else {
832 throw ParseError(
833 "expected 'template', semicolon, new line or '}'",
834 nextToken.pos);
835 }
836
837 if (isDirectory
838 ? !infos->AddItem(new BUserSettingsFileInfo(token.text, true))
839 : !infos->AddItem(new BUserSettingsFileInfo(token.text,
840 templatePath))) {
841 throw std::bad_alloc();
842 }
843 }
844 } resolvableExpressionParser(*this, infos);
845
846 _ParseList(resolvableExpressionParser, false);
847 }
848
849
850 void
_ParseUsers(UserList * users)851 BPackageInfo::Parser::_ParseUsers(UserList* users)
852 {
853 struct UserParser : public ListElementParser {
854 Parser& parser;
855 UserList* users;
856
857 UserParser(Parser& parser, UserList* users)
858 :
859 parser(parser),
860 users(users)
861 {
862 }
863
864 virtual void operator()(const Token& token)
865 {
866 if (token.type != TOKEN_STRING
867 || !BUser::IsValidUserName(token.text)) {
868 throw ParseError("expected a user name", token.pos);
869 }
870
871 BString realName;
872 BString home;
873 BString shell;
874 BStringList groups;
875
876 for (;;) {
877 Token nextToken = parser._NextToken();
878 if (nextToken.type != TOKEN_STRING) {
879 parser._RewindTo(nextToken);
880 break;
881 }
882
883 if (nextToken.text == "real-name") {
884 nextToken = parser._NextToken();
885 if (nextToken.type != TOKEN_STRING) {
886 throw ParseError("expected string (a user real name)",
887 nextToken.pos);
888 }
889 realName = nextToken.text;
890 } else if (nextToken.text == "home") {
891 nextToken = parser._NextToken();
892 if (nextToken.type != TOKEN_STRING) {
893 throw ParseError("expected string (a home path)",
894 nextToken.pos);
895 }
896 home = nextToken.text;
897 } else if (nextToken.text == "shell") {
898 nextToken = parser._NextToken();
899 if (nextToken.type != TOKEN_STRING) {
900 throw ParseError("expected string (a shell path)",
901 nextToken.pos);
902 }
903 shell = nextToken.text;
904 } else if (nextToken.text == "groups") {
905 for (;;) {
906 nextToken = parser._NextToken();
907 if (nextToken.type == TOKEN_STRING
908 && BUser::IsValidUserName(nextToken.text)) {
909 if (!groups.Add(nextToken.text))
910 throw std::bad_alloc();
911 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
912 || nextToken.type == TOKEN_CLOSE_BRACE) {
913 parser._RewindTo(nextToken);
914 break;
915 } else {
916 throw ParseError("expected a group name",
917 nextToken.pos);
918 }
919 }
920 break;
921 } else {
922 throw ParseError(
923 "expected 'real-name', 'home', 'shell', or 'groups'",
924 nextToken.pos);
925 }
926 }
927
928 BString templatePath;
929
930 Token nextToken = parser._NextToken();
931 if (nextToken.type == TOKEN_STRING
932 && nextToken.text == "template") {
933 nextToken = parser._NextToken();
934 if (nextToken.type != TOKEN_STRING) {
935 throw ParseError(
936 "expected string (a settings template file path)",
937 nextToken.pos);
938 }
939 templatePath = nextToken.text;
940 } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
941 || nextToken.type == TOKEN_CLOSE_BRACE) {
942 parser._RewindTo(nextToken);
943 } else {
944 throw ParseError(
945 "expected 'template', semicolon, new line or '}'",
946 nextToken.pos);
947 }
948
949 if (!users->AddItem(new BUser(token.text, realName, home, shell,
950 groups))) {
951 throw std::bad_alloc();
952 }
953 }
954 } resolvableExpressionParser(*this, users);
955
956 _ParseList(resolvableExpressionParser, false);
957 }
958
959
960 void
_Parse(BPackageInfo * packageInfo)961 BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo)
962 {
963 bool seen[B_PACKAGE_INFO_ENUM_COUNT];
964 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; ++i)
965 seen[i] = false;
966
967 const char* const* names = BPackageInfo::kElementNames;
968
969 while (Token t = _NextToken()) {
970 if (t.type == TOKEN_ITEM_SEPARATOR)
971 continue;
972
973 if (t.type != TOKEN_STRING)
974 throw ParseError("expected string (a variable name)", t.pos);
975
976 BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT;
977 for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) {
978 if (names[i] != NULL && t.text.ICompare(names[i]) == 0) {
979 attribute = (BPackageInfoAttributeID)i;
980 break;
981 }
982 }
983
984 if (attribute == B_PACKAGE_INFO_ENUM_COUNT) {
985 BString error = BString("unknown attribute \"") << t.text << '"';
986 throw ParseError(error, t.pos);
987 }
988
989 if (seen[attribute]) {
990 BString error = BString(names[attribute]) << " already seen!";
991 throw ParseError(error, t.pos);
992 }
993
994 switch (attribute) {
995 case B_PACKAGE_INFO_NAME:
996 {
997 BString name;
998 const char* namePos;
999 _ParseStringValue(&name, &namePos);
1000
1001 int32 errorPos;
1002 if (!_IsValidResolvableName(name, &errorPos)) {
1003 throw ParseError("invalid character in package name",
1004 namePos + errorPos);
1005 }
1006
1007 packageInfo->SetName(name);
1008 break;
1009 }
1010
1011 case B_PACKAGE_INFO_SUMMARY:
1012 {
1013 BString summary;
1014 _ParseStringValue(&summary);
1015 if (summary.FindFirst('\n') >= 0)
1016 throw ParseError("the summary contains linebreaks", t.pos);
1017 packageInfo->SetSummary(summary);
1018 break;
1019 }
1020
1021 case B_PACKAGE_INFO_DESCRIPTION:
1022 _ParseStringValue(&packageInfo->fDescription);
1023 break;
1024
1025 case B_PACKAGE_INFO_VENDOR:
1026 _ParseStringValue(&packageInfo->fVendor);
1027 break;
1028
1029 case B_PACKAGE_INFO_PACKAGER:
1030 _ParseStringValue(&packageInfo->fPackager);
1031 break;
1032
1033 case B_PACKAGE_INFO_BASE_PACKAGE:
1034 _ParseStringValue(&packageInfo->fBasePackage);
1035 break;
1036
1037 case B_PACKAGE_INFO_ARCHITECTURE:
1038 _ParseArchitectureValue(&packageInfo->fArchitecture);
1039 break;
1040
1041 case B_PACKAGE_INFO_VERSION:
1042 _ParseVersionValue(&packageInfo->fVersion, false);
1043 break;
1044
1045 case B_PACKAGE_INFO_COPYRIGHTS:
1046 _ParseStringList(&packageInfo->fCopyrightList);
1047 break;
1048
1049 case B_PACKAGE_INFO_LICENSES:
1050 _ParseStringList(&packageInfo->fLicenseList);
1051 break;
1052
1053 case B_PACKAGE_INFO_URLS:
1054 {
1055 UrlStringValidator stringValidator;
1056 _ParseStringList(&packageInfo->fURLList,
1057 false, false, &stringValidator);
1058 }
1059 break;
1060
1061 case B_PACKAGE_INFO_SOURCE_URLS:
1062 {
1063 UrlStringValidator stringValidator;
1064 _ParseStringList(&packageInfo->fSourceURLList,
1065 false, false, &stringValidator);
1066 }
1067 break;
1068
1069 case B_PACKAGE_INFO_GLOBAL_WRITABLE_FILES:
1070 _ParseGlobalWritableFileInfos(
1071 &packageInfo->fGlobalWritableFileInfos);
1072 break;
1073
1074 case B_PACKAGE_INFO_USER_SETTINGS_FILES:
1075 _ParseUserSettingsFileInfos(
1076 &packageInfo->fUserSettingsFileInfos);
1077 break;
1078
1079 case B_PACKAGE_INFO_USERS:
1080 _ParseUsers(&packageInfo->fUsers);
1081 break;
1082
1083 case B_PACKAGE_INFO_GROUPS:
1084 _ParseStringList(&packageInfo->fGroups);
1085 break;
1086
1087 case B_PACKAGE_INFO_POST_INSTALL_SCRIPTS:
1088 _ParseStringList(&packageInfo->fPostInstallScripts);
1089 break;
1090
1091 case B_PACKAGE_INFO_PRE_UNINSTALL_SCRIPTS:
1092 _ParseStringList(&packageInfo->fPreUninstallScripts);
1093 break;
1094
1095 case B_PACKAGE_INFO_PROVIDES:
1096 _ParseResolvableList(&packageInfo->fProvidesList);
1097 break;
1098
1099 case B_PACKAGE_INFO_REQUIRES:
1100 packageInfo->fBasePackage.Truncate(0);
1101 _ParseResolvableExprList(&packageInfo->fRequiresList,
1102 &packageInfo->fBasePackage);
1103 break;
1104
1105 case B_PACKAGE_INFO_SUPPLEMENTS:
1106 _ParseResolvableExprList(&packageInfo->fSupplementsList);
1107 break;
1108
1109 case B_PACKAGE_INFO_CONFLICTS:
1110 _ParseResolvableExprList(&packageInfo->fConflictsList);
1111 break;
1112
1113 case B_PACKAGE_INFO_FRESHENS:
1114 _ParseResolvableExprList(&packageInfo->fFreshensList);
1115 break;
1116
1117 case B_PACKAGE_INFO_REPLACES:
1118 _ParseStringList(&packageInfo->fReplacesList, true);
1119 break;
1120
1121 case B_PACKAGE_INFO_FLAGS:
1122 packageInfo->SetFlags(_ParseFlags());
1123 break;
1124
1125 default:
1126 // can never get here
1127 break;
1128 }
1129
1130 seen[attribute] = true;
1131 }
1132
1133 // everything up to and including 'provides' is mandatory
1134 for (int i = 0; i <= B_PACKAGE_INFO_PROVIDES; ++i) {
1135 if (!seen[i]) {
1136 BString error = BString(names[i]) << " is not being set anywhere!";
1137 throw ParseError(error, fPos);
1138 }
1139 }
1140 }
1141
1142
1143 /*static*/ inline bool
_IsAlphaNumUnderscore(const BString & string,const char * additionalChars,int32 * _errorPos)1144 BPackageInfo::Parser::_IsAlphaNumUnderscore(const BString& string,
1145 const char* additionalChars, int32* _errorPos)
1146 {
1147 return _IsAlphaNumUnderscore(string.String(),
1148 string.String() + string.Length(), additionalChars, _errorPos);
1149 }
1150
1151
1152 /*static*/ inline bool
_IsAlphaNumUnderscore(const char * string,const char * additionalChars,int32 * _errorPos)1153 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* string,
1154 const char* additionalChars, int32* _errorPos)
1155 {
1156 return _IsAlphaNumUnderscore(string, string + strlen(string),
1157 additionalChars, _errorPos);
1158 }
1159
1160
1161 /*static*/ bool
_IsAlphaNumUnderscore(const char * start,const char * end,const char * additionalChars,int32 * _errorPos)1162 BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* start, const char* end,
1163 const char* additionalChars, int32* _errorPos)
1164 {
1165 for (const char* c = start; c < end; c++) {
1166 if (!isalnum(*c) && *c != '_' && strchr(additionalChars, *c) == NULL) {
1167 if (_errorPos != NULL)
1168 *_errorPos = c - start;
1169 return false;
1170 }
1171 }
1172
1173 return true;
1174 }
1175
1176
1177 /*static*/ bool
_IsValidResolvableName(const char * string,int32 * _errorPos)1178 BPackageInfo::Parser::_IsValidResolvableName(const char* string,
1179 int32* _errorPos)
1180 {
1181 for (const char* c = string; *c != '\0'; c++) {
1182 switch (*c) {
1183 case '-':
1184 case '/':
1185 case '<':
1186 case '>':
1187 case '=':
1188 case '!':
1189 break;
1190 default:
1191 if (!isspace(*c))
1192 continue;
1193 break;
1194 }
1195
1196 if (_errorPos != NULL)
1197 *_errorPos = c - string;
1198 return false;
1199 }
1200 return true;
1201 }
1202
1203 void
Validate(const BString & urlString,const char * pos)1204 BPackageInfo::Parser::UrlStringValidator::Validate(const BString& urlString,
1205 const char* pos)
1206 {
1207 BUrl url(urlString);
1208
1209 if (!url.IsValid())
1210 throw ParseError("invalid url", pos);
1211 }
1212
1213
1214 } // namespace BPackageKit
1215