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