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