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