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 22 BPackageInfo::ParseErrorListener::~ParseErrorListener() 23 { 24 } 25 26 27 BPackageInfo::Parser::Parser(ParseErrorListener* listener) 28 : 29 fListener(listener), 30 fPos(NULL) 31 { 32 } 33 34 35 status_t 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 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 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 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 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 311 BPackageInfo::Parser::_RewindTo(const Token& token) 312 { 313 fPos = token.pos; 314 } 315 316 317 void 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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