From 6da0ce6ff206b501faa6972d44225c5d2e7469dc Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 23 May 2013 23:16:21 +0200 Subject: [PATCH] Move package info parser to its own file --- src/build/libpackage/Jamfile | 1 + src/kits/package/Jamfile | 1 + src/kits/package/PackageInfo.cpp | 1070 +----------------------- src/kits/package/PackageInfoParser.cpp | 926 ++++++++++++++++++++ src/kits/package/PackageInfoParser.h | 173 ++++ 5 files changed, 1103 insertions(+), 1068 deletions(-) create mode 100644 src/kits/package/PackageInfoParser.cpp create mode 100644 src/kits/package/PackageInfoParser.h diff --git a/src/build/libpackage/Jamfile b/src/build/libpackage/Jamfile index 1851ee6222..d0b83cb11b 100644 --- a/src/build/libpackage/Jamfile +++ b/src/build/libpackage/Jamfile @@ -92,6 +92,7 @@ BuildPlatformSharedLibrary libpackage_build.so JobQueue.cpp PackageInfo.cpp PackageInfoContentHandler.cpp + PackageInfoParser.cpp PackageInfoSet.cpp PackageResolvable.cpp PackageResolvableExpression.cpp diff --git a/src/kits/package/Jamfile b/src/kits/package/Jamfile index ee058a1336..358b0f2607 100644 --- a/src/kits/package/Jamfile +++ b/src/kits/package/Jamfile @@ -84,6 +84,7 @@ SharedLibrary libpackage.so JobQueue.cpp PackageInfo.cpp PackageInfoContentHandler.cpp + PackageInfoParser.cpp PackageInfoSet.cpp PackageResolvable.cpp PackageResolvableExpression.cpp diff --git a/src/kits/package/PackageInfo.cpp b/src/kits/package/PackageInfo.cpp index 34e14eeb2e..fdcef99678 100644 --- a/src/kits/package/PackageInfo.cpp +++ b/src/kits/package/PackageInfo.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -23,1077 +22,12 @@ #include #include +#include "PackageInfoParser.h" + namespace BPackageKit { -namespace { - - -enum TokenType { - TOKEN_WORD, - TOKEN_QUOTED_STRING, - TOKEN_OPERATOR_ASSIGN, - TOKEN_OPERATOR_LESS, - TOKEN_OPERATOR_LESS_EQUAL, - TOKEN_OPERATOR_EQUAL, - TOKEN_OPERATOR_NOT_EQUAL, - TOKEN_OPERATOR_GREATER_EQUAL, - TOKEN_OPERATOR_GREATER, - TOKEN_OPEN_BRACE, - TOKEN_CLOSE_BRACE, - TOKEN_ITEM_SEPARATOR, - // - TOKEN_EOF, -}; - - -struct ParseError { - BString message; - const char* pos; - - ParseError(const BString& _message, const char* _pos) - : message(_message), pos(_pos) - { - } -}; - - -} // anonymous namespace - - -// #pragma mark - Parser - - -/* - * Parses a ".PackageInfo" file and fills a BPackageInfo object with the - * package info elements found. - */ -class BPackageInfo::Parser { -public: - Parser(ParseErrorListener* listener = NULL); - - status_t Parse(const BString& packageInfoString, - BPackageInfo* packageInfo); - - status_t ParseVersion(const BString& versionString, - bool revisionIsOptional, - BPackageVersion& _version); - -private: - struct Token; - struct ListElementParser; - friend struct ListElementParser; - - Token _NextToken(); - void _RewindTo(const Token& token); - - void _ParseStringValue(BString* value, - const char** _tokenPos = NULL); - uint32 _ParseFlags(); - void _ParseArchitectureValue( - BPackageArchitecture* value); - void _ParseVersionValue(BPackageVersion* value, - bool revisionIsOptional); - static void _ParseVersionValue(Token& word, - BPackageVersion* value, - bool revisionIsOptional); - void _ParseList(ListElementParser& elementParser, - bool allowSingleNonListElement); - void _ParseStringList(BStringList* value, - bool allowQuotedStrings = true, - bool convertToLowerCase = false); - void _ParseResolvableList( - BObjectList* value); - void _ParseResolvableExprList( - BObjectList* - value, - BString* _basePackage = NULL); - void _ParseGlobalSettingsFileInfos( - GlobalSettingsFileInfoList* infos); - void _ParseUserSettingsFileInfos( - UserSettingsFileInfoList* infos); - - void _Parse(BPackageInfo* packageInfo); - - static bool _IsAlphaNumUnderscore(const BString& string, - const char* additionalChars, - int32* _errorPos); - static bool _IsAlphaNumUnderscore(const char* string, - const char* additionalChars, - int32* _errorPos); - static bool _IsAlphaNumUnderscore(const char* start, - const char* end, - const char* additionalChars, - int32* _errorPos); - static bool _IsValidResolvableName(const char* string, - int32* _errorPos); - -private: - ParseErrorListener* fListener; - const char* fPos; -}; - - -struct BPackageInfo::Parser::Token { - TokenType type; - BString text; - const char* pos; - - Token(TokenType _type, const char* _pos, int length = 0) - : type(_type), pos(_pos) - { - if (length != 0) { - text.SetTo(pos, length); - - if (type == TOKEN_QUOTED_STRING) { - // unescape value of quoted string - char* value = text.LockBuffer(length); - if (value == NULL) - return; - int index = 0; - int newIndex = 0; - bool lastWasEscape = false; - while (char c = value[index++]) { - if (lastWasEscape) { - lastWasEscape = false; - // map \n to newline and \t to tab - if (c == 'n') - c = '\n'; - else if (c == 't') - c = '\t'; - } else if (c == '\\') { - lastWasEscape = true; - continue; - } - value[newIndex++] = c; - } - value[newIndex] = '\0'; - text.UnlockBuffer(newIndex); - } - } - } - - operator bool() const - { - return type != TOKEN_EOF; - } -}; - - -struct BPackageInfo::Parser::ListElementParser { - virtual void operator()(const Token& token) = 0; -}; - - -BPackageInfo::ParseErrorListener::~ParseErrorListener() -{ -} - - -BPackageInfo::Parser::Parser(ParseErrorListener* listener) - : - fListener(listener), - fPos(NULL) -{ -} - - -status_t -BPackageInfo::Parser::Parse(const BString& packageInfoString, - BPackageInfo* packageInfo) -{ - if (packageInfo == NULL) - return B_BAD_VALUE; - - fPos = packageInfoString.String(); - - try { - _Parse(packageInfo); - } catch (const ParseError& error) { - if (fListener != NULL) { - // map error position to line and column - int line = 1; - int column; - int32 offset = error.pos - packageInfoString.String(); - int32 newlinePos = packageInfoString.FindLast('\n', offset - 1); - if (newlinePos < 0) - column = offset; - else { - column = offset - newlinePos; - do { - line++; - newlinePos = packageInfoString.FindLast('\n', - newlinePos - 1); - } while (newlinePos >= 0); - } - fListener->OnError(error.message, line, column); - } - return B_BAD_DATA; - } catch (const std::bad_alloc& e) { - if (fListener != NULL) - fListener->OnError("out of memory", 0, 0); - return B_NO_MEMORY; - } - - return B_OK; -} - - -status_t -BPackageInfo::Parser::ParseVersion(const BString& versionString, - bool revisionIsOptional, BPackageVersion& _version) -{ - fPos = versionString.String(); - - try { - Token token(TOKEN_WORD, fPos, versionString.Length()); - _ParseVersionValue(token, &_version, revisionIsOptional); - } catch (const ParseError& error) { - if (fListener != NULL) { - int32 offset = error.pos - versionString.String(); - fListener->OnError(error.message, 1, offset); - } - return B_BAD_DATA; - } catch (const std::bad_alloc& e) { - if (fListener != NULL) - fListener->OnError("out of memory", 0, 0); - return B_NO_MEMORY; - } - - return B_OK; -} - - -BPackageInfo::Parser::Token -BPackageInfo::Parser::_NextToken() -{ - // Eat any whitespace or comments. Also eat ';' -- they have the same - // function as newlines. We remember the last encountered ';' or '\n' and - // return it as a token afterwards. - const char* itemSeparatorPos = NULL; - bool inComment = false; - while ((inComment && *fPos != '\0') || isspace(*fPos) || *fPos == ';' - || *fPos == '#') { - if (*fPos == '#') { - inComment = true; - } else if (*fPos == '\n') { - itemSeparatorPos = fPos; - inComment = false; - } else if (!inComment && *fPos == ';') - itemSeparatorPos = fPos; - fPos++; - } - - if (itemSeparatorPos != NULL) { - return Token(TOKEN_ITEM_SEPARATOR, itemSeparatorPos); - } - - const char* tokenPos = fPos; - switch (*fPos) { - case '\0': - return Token(TOKEN_EOF, fPos); - - case '{': - fPos++; - return Token(TOKEN_OPEN_BRACE, tokenPos); - - case '}': - fPos++; - return Token(TOKEN_CLOSE_BRACE, tokenPos); - - case '<': - fPos++; - if (*fPos == '=') { - fPos++; - return Token(TOKEN_OPERATOR_LESS_EQUAL, tokenPos, 2); - } - return Token(TOKEN_OPERATOR_LESS, tokenPos, 1); - - case '=': - fPos++; - if (*fPos == '=') { - fPos++; - return Token(TOKEN_OPERATOR_EQUAL, tokenPos, 2); - } - return Token(TOKEN_OPERATOR_ASSIGN, tokenPos, 1); - - case '!': - if (fPos[1] == '=') { - fPos += 2; - return Token(TOKEN_OPERATOR_NOT_EQUAL, tokenPos, 2); - } - break; - - case '>': - fPos++; - if (*fPos == '=') { - fPos++; - return Token(TOKEN_OPERATOR_GREATER_EQUAL, tokenPos, 2); - } - return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1); - - case '"': - case '\'': - { - char quoteChar = *fPos; - fPos++; - const char* start = fPos; - // anything until the next quote is part of the value - bool lastWasEscape = false; - while ((*fPos != quoteChar || lastWasEscape) && *fPos != '\0') { - if (lastWasEscape) - lastWasEscape = false; - else if (*fPos == '\\') - lastWasEscape = true; - fPos++; - } - if (*fPos != quoteChar) - throw ParseError("unterminated quoted-string", tokenPos); - const char* end = fPos++; - return Token(TOKEN_QUOTED_STRING, start, end - start); - } - - default: - { - const char* start = fPos; - while (isalnum(*fPos) || *fPos == '.' || *fPos == '-' - || *fPos == '_' || *fPos == ':' || *fPos == '+' - || *fPos == '~') { - fPos++; - } - if (fPos == start) - break; - return Token(TOKEN_WORD, start, fPos - start); - } - } - - BString error = BString("unknown token '") << *fPos << "' encountered"; - throw ParseError(error.String(), fPos); -} - - -void -BPackageInfo::Parser::_RewindTo(const Token& token) -{ - fPos = token.pos; -} - - -void -BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos) -{ - Token string = _NextToken(); - if (string.type != TOKEN_QUOTED_STRING && string.type != TOKEN_WORD) - throw ParseError("expected quoted-string or word", string.pos); - - *value = string.text; - if (_tokenPos != NULL) - *_tokenPos = string.pos; -} - - -void -BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value) -{ - Token arch = _NextToken(); - if (arch.type == TOKEN_WORD) { - for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) { - if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) { - *value = (BPackageArchitecture)i; - return; - } - } - } - - BString error("architecture must be one of: ["); - for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) { - if (i > 0) - error << ","; - error << BPackageInfo::kArchitectureNames[i]; - } - error << "]"; - throw ParseError(error, arch.pos); -} - - -void -BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value, - bool revisionIsOptional) -{ - Token word = _NextToken(); - _ParseVersionValue(word, value, revisionIsOptional); -} - - -/*static*/ void -BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value, - bool revisionIsOptional) -{ - if (word.type != TOKEN_WORD) - throw ParseError("expected word (a version)", word.pos); - - // get the revision number - uint32 revision = 0; - int32 dashPos = word.text.FindLast('-'); - if (dashPos >= 0) { - char* end; - long long number = strtoll(word.text.String() + dashPos + 1, &end, - 0); - if (*end != '\0' || number < 0 || number > UINT_MAX) { - throw ParseError("revision must be a number > 0 and < UINT_MAX", - word.pos + dashPos + 1); - } - - revision = (uint32)number; - word.text.Truncate(dashPos); - } - - if (revision == 0 && !revisionIsOptional) { - throw ParseError("expected revision number (- suffix)", - word.pos + word.text.Length()); - } - - // get the pre-release string - BString preRelease; - int32 tildePos = word.text.FindLast('~'); - if (tildePos >= 0) { - word.text.CopyInto(preRelease, tildePos + 1, - word.text.Length() - tildePos - 1); - word.text.Truncate(tildePos); - - if (preRelease.IsEmpty()) { - throw ParseError("invalid empty pre-release string", - word.pos + tildePos + 1); - } - - int32 errorPos; - if (!_IsAlphaNumUnderscore(preRelease, ".", &errorPos)) { - throw ParseError("invalid character in pre-release string", - word.pos + tildePos + 1 + errorPos); - } - } - - // get major, minor, and micro strings - BString major; - BString minor; - BString micro; - int32 firstDotPos = word.text.FindFirst('.'); - if (firstDotPos < 0) - major = word.text; - else { - word.text.CopyInto(major, 0, firstDotPos); - int32 secondDotPos = word.text.FindFirst('.', firstDotPos + 1); - if (secondDotPos == firstDotPos + 1) - throw ParseError("expected minor version", word.pos + secondDotPos); - - if (secondDotPos < 0) { - word.text.CopyInto(minor, firstDotPos + 1, word.text.Length()); - } else { - word.text.CopyInto(minor, firstDotPos + 1, - secondDotPos - (firstDotPos + 1)); - word.text.CopyInto(micro, secondDotPos + 1, word.text.Length()); - - int32 errorPos; - if (!_IsAlphaNumUnderscore(micro, ".", &errorPos)) { - throw ParseError("invalid character in micro version string", - word.pos + secondDotPos + 1 + errorPos); - } - } - - int32 errorPos; - if (!_IsAlphaNumUnderscore(minor, "", &errorPos)) { - throw ParseError("invalid character in minor version string", - word.pos + firstDotPos + 1 + errorPos); - } - } - - int32 errorPos; - if (!_IsAlphaNumUnderscore(major, "", &errorPos)) { - throw ParseError("invalid character in major version string", - word.pos + errorPos); - } - - value->SetTo(major, minor, micro, preRelease, revision); -} - - -void -BPackageInfo::Parser::_ParseList(ListElementParser& elementParser, - bool allowSingleNonListElement) -{ - Token openBracket = _NextToken(); - if (openBracket.type != TOKEN_OPEN_BRACE) { - if (!allowSingleNonListElement) - throw ParseError("expected start of list ('{')", openBracket.pos); - - elementParser(openBracket); - return; - } - - while (true) { - Token token = _NextToken(); - if (token.type == TOKEN_CLOSE_BRACE) - return; - - if (token.type == TOKEN_ITEM_SEPARATOR) - continue; - - elementParser(token); - } -} - - -void -BPackageInfo::Parser::_ParseStringList(BStringList* value, - bool allowQuotedStrings, bool convertToLowerCase) -{ - struct StringParser : public ListElementParser { - BStringList* value; - bool allowQuotedStrings; - bool convertToLowerCase; - - StringParser(BStringList* value, bool allowQuotedStrings, - bool convertToLowerCase) - : - value(value), - allowQuotedStrings(allowQuotedStrings), - convertToLowerCase(convertToLowerCase) - { - } - - virtual void operator()(const Token& token) - { - if (allowQuotedStrings) { - if (token.type != TOKEN_QUOTED_STRING - && token.type != TOKEN_WORD) { - throw ParseError("expected quoted-string or word", - token.pos); - } - } else { - if (token.type != TOKEN_WORD) - throw ParseError("expected word", token.pos); - } - - BString element(token.text); - if (convertToLowerCase) - element.ToLower(); - - value->Add(element); - } - } stringParser(value, allowQuotedStrings, convertToLowerCase); - - _ParseList(stringParser, true); -} - - -uint32 -BPackageInfo::Parser::_ParseFlags() -{ - struct FlagParser : public ListElementParser { - uint32 flags; - - FlagParser() - : - flags(0) - { - } - - virtual void operator()(const Token& token) - { - if (token.type != TOKEN_WORD) - throw ParseError("expected word (a flag)", token.pos); - - if (token.text.ICompare("approve_license") == 0) - flags |= B_PACKAGE_FLAG_APPROVE_LICENSE; - else if (token.text.ICompare("system_package") == 0) - flags |= B_PACKAGE_FLAG_SYSTEM_PACKAGE; - else { - throw ParseError( - "expected 'approve_license' or 'system_package'", - token.pos); - } - } - } flagParser; - - _ParseList(flagParser, true); - - return flagParser.flags; -} - - -void -BPackageInfo::Parser::_ParseResolvableList( - BObjectList* value) -{ - struct ResolvableParser : public ListElementParser { - Parser& parser; - BObjectList* value; - - ResolvableParser(Parser& parser_, - BObjectList* value_) - : - parser(parser_), - value(value_) - { - } - - virtual void operator()(const Token& token) - { - if (token.type != TOKEN_WORD) { - throw ParseError("expected word (a resolvable name)", - token.pos); - } - - int32 errorPos; - if (!_IsValidResolvableName(token.text, &errorPos)) { - throw ParseError("invalid character in resolvable name", - token.pos + errorPos); - } - - // parse version - BPackageVersion version; - Token op = parser._NextToken(); - if (op.type == TOKEN_OPERATOR_ASSIGN) { - parser._ParseVersionValue(&version, true); - } else if (op.type == TOKEN_ITEM_SEPARATOR - || op.type == TOKEN_CLOSE_BRACE) { - parser._RewindTo(op); - } else - throw ParseError("expected '=', comma or '}'", op.pos); - - // parse compatible version - BPackageVersion compatibleVersion; - Token compatible = parser._NextToken(); - if (compatible.type == TOKEN_WORD - && (compatible.text == "compat" - || compatible.text == "compatible")) { - op = parser._NextToken(); - if (op.type == TOKEN_OPERATOR_GREATER_EQUAL) { - parser._ParseVersionValue(&compatibleVersion, true); - } else - parser._RewindTo(compatible); - } else - parser._RewindTo(compatible); - - value->AddItem(new BPackageResolvable(token.text, version, - compatibleVersion)); - } - } resolvableParser(*this, value); - - _ParseList(resolvableParser, false); -} - - -void -BPackageInfo::Parser::_ParseResolvableExprList( - BObjectList* value, BString* _basePackage) -{ - struct ResolvableExpressionParser : public ListElementParser { - Parser& parser; - BObjectList* value; - BString* basePackage; - - ResolvableExpressionParser(Parser& parser, - BObjectList* value, - BString* basePackage) - : - parser(parser), - value(value), - basePackage(basePackage) - { - } - - virtual void operator()(const Token& token) - { - if (token.type != TOKEN_WORD) { - throw ParseError("expected word (a resolvable name)", - token.pos); - } - - int32 errorPos; - if (!_IsValidResolvableName(token.text, &errorPos)) { - throw ParseError("invalid character in resolvable name", - token.pos + errorPos); - } - - BPackageVersion version; - Token op = parser._NextToken(); - if (op.type == TOKEN_OPERATOR_LESS - || op.type == TOKEN_OPERATOR_LESS_EQUAL - || op.type == TOKEN_OPERATOR_EQUAL - || op.type == TOKEN_OPERATOR_NOT_EQUAL - || op.type == TOKEN_OPERATOR_GREATER_EQUAL - || op.type == TOKEN_OPERATOR_GREATER) { - parser._ParseVersionValue(&version, true); - - if (basePackage != NULL) { - Token base = parser._NextToken(); - if (base.type == TOKEN_WORD && base.text == "base") { - if (!basePackage->IsEmpty()) { - throw ParseError( - "multiple packages marked as base package", - token.pos); - } - - *basePackage = token.text; - } else - parser._RewindTo(base); - } - } else if (op.type == TOKEN_ITEM_SEPARATOR - || op.type == TOKEN_CLOSE_BRACE) { - parser._RewindTo(op); - } else { - throw ParseError( - "expected '<', '<=', '==', '!=', '>=', '>', comma or '}'", - op.pos); - } - - BPackageResolvableOperator resolvableOperator - = (BPackageResolvableOperator)(op.type - TOKEN_OPERATOR_LESS); - - value->AddItem(new BPackageResolvableExpression(token.text, - resolvableOperator, version)); - } - } resolvableExpressionParser(*this, value, _basePackage); - - _ParseList(resolvableExpressionParser, false); -} - - -void -BPackageInfo::Parser::_ParseGlobalSettingsFileInfos( - GlobalSettingsFileInfoList* infos) -{ - struct GlobalSettingsFileInfoParser : public ListElementParser { - Parser& parser; - GlobalSettingsFileInfoList* infos; - - GlobalSettingsFileInfoParser(Parser& parser, - GlobalSettingsFileInfoList* infos) - : - parser(parser), - infos(infos) - { - } - - virtual void operator()(const Token& token) - { - if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { - throw ParseError("expected string (a settings file path)", - token.pos); - } - - BSettingsFileUpdateType updateType - = B_SETTINGS_FILE_UPDATE_TYPE_ENUM_COUNT; - - Token nextToken = parser._NextToken(); - if (nextToken.type == TOKEN_WORD) { - const char* const* end = kSettingsFileUpdateTypes - + B_SETTINGS_FILE_UPDATE_TYPE_ENUM_COUNT; - const char* const* found = std::find(kSettingsFileUpdateTypes, - end, nextToken.text); - if (found == end) { - throw ParseError(BString("expected an update type"), - nextToken.pos); - } - updateType = (BSettingsFileUpdateType)( - found - kSettingsFileUpdateTypes); - } else if (nextToken.type == TOKEN_ITEM_SEPARATOR - || nextToken.type == TOKEN_CLOSE_BRACE) { - parser._RewindTo(nextToken); - } else { - throw ParseError( - "expected 'included', semicolon, new line or '}'", - nextToken.pos); - } - - if (!infos->AddItem(new BGlobalSettingsFileInfo(token.text, - updateType))) { - throw std::bad_alloc(); - } - } - } resolvableExpressionParser(*this, infos); - - _ParseList(resolvableExpressionParser, false); -} - - -void -BPackageInfo::Parser::_ParseUserSettingsFileInfos( - UserSettingsFileInfoList* infos) -{ - struct UserSettingsFileInfoParser : public ListElementParser { - Parser& parser; - UserSettingsFileInfoList* infos; - - UserSettingsFileInfoParser(Parser& parser, - UserSettingsFileInfoList* infos) - : - parser(parser), - infos(infos) - { - } - - virtual void operator()(const Token& token) - { - if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { - throw ParseError("expected string (a settings file path)", - token.pos); - } - - BString templatePath; - - Token nextToken = parser._NextToken(); - if (nextToken.type == TOKEN_WORD && nextToken.text == "template") { - nextToken = parser._NextToken(); - if (nextToken.type != TOKEN_WORD - && nextToken.type != TOKEN_QUOTED_STRING) { - throw ParseError( - "expected string (a settings template file path)", - nextToken.pos); - } - templatePath = nextToken.text; - } else if (nextToken.type == TOKEN_ITEM_SEPARATOR - || nextToken.type == TOKEN_CLOSE_BRACE) { - parser._RewindTo(nextToken); - } else { - throw ParseError( - "expected 'template', semicolon, new line or '}'", - nextToken.pos); - } - - if (!infos->AddItem(new BUserSettingsFileInfo(token.text, - templatePath))) { - throw std::bad_alloc(); - } - } - } resolvableExpressionParser(*this, infos); - - _ParseList(resolvableExpressionParser, false); -} - - -void -BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo) -{ - bool seen[B_PACKAGE_INFO_ENUM_COUNT]; - for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; ++i) - seen[i] = false; - - const char* const* names = BPackageInfo::kElementNames; - - while (Token t = _NextToken()) { - if (t.type == TOKEN_ITEM_SEPARATOR) - continue; - - if (t.type != TOKEN_WORD) - throw ParseError("expected word (a variable name)", t.pos); - - BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT; - for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) { - if (names[i] != NULL && t.text.ICompare(names[i]) == 0) { - attribute = (BPackageInfoAttributeID)i; - break; - } - } - - if (attribute == B_PACKAGE_INFO_ENUM_COUNT) { - BString error = BString("unknown attribute \"") << t.text << '"'; - throw ParseError(error, t.pos); - } - - if (seen[attribute]) { - BString error = BString(names[attribute]) << " already seen!"; - throw ParseError(error, t.pos); - } - - switch (attribute) { - case B_PACKAGE_INFO_NAME: - { - BString name; - const char* namePos; - _ParseStringValue(&name, &namePos); - - int32 errorPos; - if (!_IsValidResolvableName(name, &errorPos)) { - throw ParseError("invalid character in package name", - namePos + errorPos); - } - - packageInfo->SetName(name); - break; - } - - case B_PACKAGE_INFO_SUMMARY: - { - BString summary; - _ParseStringValue(&summary); - if (summary.FindFirst('\n') >= 0) - throw ParseError("the summary contains linebreaks", t.pos); - packageInfo->SetSummary(summary); - break; - } - - case B_PACKAGE_INFO_DESCRIPTION: - _ParseStringValue(&packageInfo->fDescription); - break; - - case B_PACKAGE_INFO_VENDOR: - _ParseStringValue(&packageInfo->fVendor); - break; - - case B_PACKAGE_INFO_PACKAGER: - _ParseStringValue(&packageInfo->fPackager); - break; - - case B_PACKAGE_INFO_BASE_PACKAGE: - _ParseStringValue(&packageInfo->fBasePackage); - break; - - case B_PACKAGE_INFO_ARCHITECTURE: - _ParseArchitectureValue(&packageInfo->fArchitecture); - break; - - case B_PACKAGE_INFO_VERSION: - _ParseVersionValue(&packageInfo->fVersion, false); - break; - - case B_PACKAGE_INFO_COPYRIGHTS: - _ParseStringList(&packageInfo->fCopyrightList); - break; - - case B_PACKAGE_INFO_LICENSES: - _ParseStringList(&packageInfo->fLicenseList); - break; - - case B_PACKAGE_INFO_URLS: - _ParseStringList(&packageInfo->fURLList); - break; - - case B_PACKAGE_INFO_SOURCE_URLS: - _ParseStringList(&packageInfo->fSourceURLList); - break; - - case B_PACKAGE_INFO_GLOBAL_SETTINGS_FILES: - _ParseGlobalSettingsFileInfos( - &packageInfo->fGlobalSettingsFileInfos); - break; - - case B_PACKAGE_INFO_USER_SETTINGS_FILES: - _ParseUserSettingsFileInfos( - &packageInfo->fUserSettingsFileInfos); - break; - - case B_PACKAGE_INFO_PROVIDES: - _ParseResolvableList(&packageInfo->fProvidesList); - break; - - case B_PACKAGE_INFO_REQUIRES: - packageInfo->fBasePackage.Truncate(0); - _ParseResolvableExprList(&packageInfo->fRequiresList, - &packageInfo->fBasePackage); - break; - - case B_PACKAGE_INFO_SUPPLEMENTS: - _ParseResolvableExprList(&packageInfo->fSupplementsList); - break; - - case B_PACKAGE_INFO_CONFLICTS: - _ParseResolvableExprList(&packageInfo->fConflictsList); - break; - - case B_PACKAGE_INFO_FRESHENS: - _ParseResolvableExprList(&packageInfo->fFreshensList); - break; - - case B_PACKAGE_INFO_REPLACES: - _ParseStringList(&packageInfo->fReplacesList, false, true); - break; - - case B_PACKAGE_INFO_FLAGS: - packageInfo->SetFlags(_ParseFlags()); - break; - - default: - // can never get here - break; - } - - seen[attribute] = true; - } - - // everything up to and including 'provides' is mandatory - for (int i = 0; i <= B_PACKAGE_INFO_PROVIDES; ++i) { - if (!seen[i]) { - BString error = BString(names[i]) << " is not being set anywhere!"; - throw ParseError(error, fPos); - } - } -} - - -/*static*/ inline bool -BPackageInfo::Parser::_IsAlphaNumUnderscore(const BString& string, - const char* additionalChars, int32* _errorPos) -{ - return _IsAlphaNumUnderscore(string.String(), - string.String() + string.Length(), additionalChars, _errorPos); -} - - -/*static*/ inline bool -BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* string, - const char* additionalChars, int32* _errorPos) -{ - return _IsAlphaNumUnderscore(string, string + strlen(string), - additionalChars, _errorPos); -} - - -/*static*/ bool -BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* start, const char* end, - const char* additionalChars, int32* _errorPos) -{ - for (const char* c = start; c < end; c++) { - if (!isalnum(*c) && *c != '_' && strchr(additionalChars, *c) == NULL) { - if (_errorPos != NULL) - *_errorPos = c - start; - return false; - } - } - - return true; -} - - -/*static*/ bool -BPackageInfo::Parser::_IsValidResolvableName(const char* string, - int32* _errorPos) -{ - for (const char* c = string; *c != '\0'; c++) { - switch (*c) { - case '-': - case '/': - case '<': - case '>': - case '=': - case '!': - break; - default: - if (!isspace(*c)) - continue; - break; - } - - if (_errorPos != NULL) - *_errorPos = c - string; - return false; - } - return true; -} - - const char* const BPackageInfo::kElementNames[B_PACKAGE_INFO_ENUM_COUNT] = { "name", "summary", diff --git a/src/kits/package/PackageInfoParser.cpp b/src/kits/package/PackageInfoParser.cpp new file mode 100644 index 0000000000..b2fbd444f6 --- /dev/null +++ b/src/kits/package/PackageInfoParser.cpp @@ -0,0 +1,926 @@ +/* + * Copyright 2011, Oliver Tappe + * Distributed under the terms of the MIT License. + */ + + +#include "PackageInfoParser.h" + +#include +#include + +#include + + +namespace BPackageKit { + + +BPackageInfo::ParseErrorListener::~ParseErrorListener() +{ +} + + +BPackageInfo::Parser::Parser(ParseErrorListener* listener) + : + fListener(listener), + fPos(NULL) +{ +} + + +status_t +BPackageInfo::Parser::Parse(const BString& packageInfoString, + BPackageInfo* packageInfo) +{ + if (packageInfo == NULL) + return B_BAD_VALUE; + + fPos = packageInfoString.String(); + + try { + _Parse(packageInfo); + } catch (const ParseError& error) { + if (fListener != NULL) { + // map error position to line and column + int line = 1; + int column; + int32 offset = error.pos - packageInfoString.String(); + int32 newlinePos = packageInfoString.FindLast('\n', offset - 1); + if (newlinePos < 0) + column = offset; + else { + column = offset - newlinePos; + do { + line++; + newlinePos = packageInfoString.FindLast('\n', + newlinePos - 1); + } while (newlinePos >= 0); + } + fListener->OnError(error.message, line, column); + } + return B_BAD_DATA; + } catch (const std::bad_alloc& e) { + if (fListener != NULL) + fListener->OnError("out of memory", 0, 0); + return B_NO_MEMORY; + } + + return B_OK; +} + + +status_t +BPackageInfo::Parser::ParseVersion(const BString& versionString, + bool revisionIsOptional, BPackageVersion& _version) +{ + fPos = versionString.String(); + + try { + Token token(TOKEN_WORD, fPos, versionString.Length()); + _ParseVersionValue(token, &_version, revisionIsOptional); + } catch (const ParseError& error) { + if (fListener != NULL) { + int32 offset = error.pos - versionString.String(); + fListener->OnError(error.message, 1, offset); + } + return B_BAD_DATA; + } catch (const std::bad_alloc& e) { + if (fListener != NULL) + fListener->OnError("out of memory", 0, 0); + return B_NO_MEMORY; + } + + return B_OK; +} + + +BPackageInfo::Parser::Token +BPackageInfo::Parser::_NextToken() +{ + // Eat any whitespace or comments. Also eat ';' -- they have the same + // function as newlines. We remember the last encountered ';' or '\n' and + // return it as a token afterwards. + const char* itemSeparatorPos = NULL; + bool inComment = false; + while ((inComment && *fPos != '\0') || isspace(*fPos) || *fPos == ';' + || *fPos == '#') { + if (*fPos == '#') { + inComment = true; + } else if (*fPos == '\n') { + itemSeparatorPos = fPos; + inComment = false; + } else if (!inComment && *fPos == ';') + itemSeparatorPos = fPos; + fPos++; + } + + if (itemSeparatorPos != NULL) { + return Token(TOKEN_ITEM_SEPARATOR, itemSeparatorPos); + } + + const char* tokenPos = fPos; + switch (*fPos) { + case '\0': + return Token(TOKEN_EOF, fPos); + + case '{': + fPos++; + return Token(TOKEN_OPEN_BRACE, tokenPos); + + case '}': + fPos++; + return Token(TOKEN_CLOSE_BRACE, tokenPos); + + case '<': + fPos++; + if (*fPos == '=') { + fPos++; + return Token(TOKEN_OPERATOR_LESS_EQUAL, tokenPos, 2); + } + return Token(TOKEN_OPERATOR_LESS, tokenPos, 1); + + case '=': + fPos++; + if (*fPos == '=') { + fPos++; + return Token(TOKEN_OPERATOR_EQUAL, tokenPos, 2); + } + return Token(TOKEN_OPERATOR_ASSIGN, tokenPos, 1); + + case '!': + if (fPos[1] == '=') { + fPos += 2; + return Token(TOKEN_OPERATOR_NOT_EQUAL, tokenPos, 2); + } + break; + + case '>': + fPos++; + if (*fPos == '=') { + fPos++; + return Token(TOKEN_OPERATOR_GREATER_EQUAL, tokenPos, 2); + } + return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1); + + case '"': + case '\'': + { + char quoteChar = *fPos; + fPos++; + const char* start = fPos; + // anything until the next quote is part of the value + bool lastWasEscape = false; + while ((*fPos != quoteChar || lastWasEscape) && *fPos != '\0') { + if (lastWasEscape) + lastWasEscape = false; + else if (*fPos == '\\') + lastWasEscape = true; + fPos++; + } + if (*fPos != quoteChar) + throw ParseError("unterminated quoted-string", tokenPos); + const char* end = fPos++; + return Token(TOKEN_QUOTED_STRING, start, end - start); + } + + default: + { + const char* start = fPos; + while (isalnum(*fPos) || *fPos == '.' || *fPos == '-' + || *fPos == '_' || *fPos == ':' || *fPos == '+' + || *fPos == '~') { + fPos++; + } + if (fPos == start) + break; + return Token(TOKEN_WORD, start, fPos - start); + } + } + + BString error = BString("unknown token '") << *fPos << "' encountered"; + throw ParseError(error.String(), fPos); +} + + +void +BPackageInfo::Parser::_RewindTo(const Token& token) +{ + fPos = token.pos; +} + + +void +BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos) +{ + Token string = _NextToken(); + if (string.type != TOKEN_QUOTED_STRING && string.type != TOKEN_WORD) + throw ParseError("expected quoted-string or word", string.pos); + + *value = string.text; + if (_tokenPos != NULL) + *_tokenPos = string.pos; +} + + +void +BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value) +{ + Token arch = _NextToken(); + if (arch.type == TOKEN_WORD) { + for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) { + if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) { + *value = (BPackageArchitecture)i; + return; + } + } + } + + BString error("architecture must be one of: ["); + for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) { + if (i > 0) + error << ","; + error << BPackageInfo::kArchitectureNames[i]; + } + error << "]"; + throw ParseError(error, arch.pos); +} + + +void +BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value, + bool revisionIsOptional) +{ + Token word = _NextToken(); + _ParseVersionValue(word, value, revisionIsOptional); +} + + +/*static*/ void +BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value, + bool revisionIsOptional) +{ + if (word.type != TOKEN_WORD) + throw ParseError("expected word (a version)", word.pos); + + // get the revision number + uint32 revision = 0; + int32 dashPos = word.text.FindLast('-'); + if (dashPos >= 0) { + char* end; + long long number = strtoll(word.text.String() + dashPos + 1, &end, + 0); + if (*end != '\0' || number < 0 || number > UINT_MAX) { + throw ParseError("revision must be a number > 0 and < UINT_MAX", + word.pos + dashPos + 1); + } + + revision = (uint32)number; + word.text.Truncate(dashPos); + } + + if (revision == 0 && !revisionIsOptional) { + throw ParseError("expected revision number (- suffix)", + word.pos + word.text.Length()); + } + + // get the pre-release string + BString preRelease; + int32 tildePos = word.text.FindLast('~'); + if (tildePos >= 0) { + word.text.CopyInto(preRelease, tildePos + 1, + word.text.Length() - tildePos - 1); + word.text.Truncate(tildePos); + + if (preRelease.IsEmpty()) { + throw ParseError("invalid empty pre-release string", + word.pos + tildePos + 1); + } + + int32 errorPos; + if (!_IsAlphaNumUnderscore(preRelease, ".", &errorPos)) { + throw ParseError("invalid character in pre-release string", + word.pos + tildePos + 1 + errorPos); + } + } + + // get major, minor, and micro strings + BString major; + BString minor; + BString micro; + int32 firstDotPos = word.text.FindFirst('.'); + if (firstDotPos < 0) + major = word.text; + else { + word.text.CopyInto(major, 0, firstDotPos); + int32 secondDotPos = word.text.FindFirst('.', firstDotPos + 1); + if (secondDotPos == firstDotPos + 1) + throw ParseError("expected minor version", word.pos + secondDotPos); + + if (secondDotPos < 0) { + word.text.CopyInto(minor, firstDotPos + 1, word.text.Length()); + } else { + word.text.CopyInto(minor, firstDotPos + 1, + secondDotPos - (firstDotPos + 1)); + word.text.CopyInto(micro, secondDotPos + 1, word.text.Length()); + + int32 errorPos; + if (!_IsAlphaNumUnderscore(micro, ".", &errorPos)) { + throw ParseError("invalid character in micro version string", + word.pos + secondDotPos + 1 + errorPos); + } + } + + int32 errorPos; + if (!_IsAlphaNumUnderscore(minor, "", &errorPos)) { + throw ParseError("invalid character in minor version string", + word.pos + firstDotPos + 1 + errorPos); + } + } + + int32 errorPos; + if (!_IsAlphaNumUnderscore(major, "", &errorPos)) { + throw ParseError("invalid character in major version string", + word.pos + errorPos); + } + + value->SetTo(major, minor, micro, preRelease, revision); +} + + +void +BPackageInfo::Parser::_ParseList(ListElementParser& elementParser, + bool allowSingleNonListElement) +{ + Token openBracket = _NextToken(); + if (openBracket.type != TOKEN_OPEN_BRACE) { + if (!allowSingleNonListElement) + throw ParseError("expected start of list ('{')", openBracket.pos); + + elementParser(openBracket); + return; + } + + while (true) { + Token token = _NextToken(); + if (token.type == TOKEN_CLOSE_BRACE) + return; + + if (token.type == TOKEN_ITEM_SEPARATOR) + continue; + + elementParser(token); + } +} + + +void +BPackageInfo::Parser::_ParseStringList(BStringList* value, + bool allowQuotedStrings, bool convertToLowerCase) +{ + struct StringParser : public ListElementParser { + BStringList* value; + bool allowQuotedStrings; + bool convertToLowerCase; + + StringParser(BStringList* value, bool allowQuotedStrings, + bool convertToLowerCase) + : + value(value), + allowQuotedStrings(allowQuotedStrings), + convertToLowerCase(convertToLowerCase) + { + } + + virtual void operator()(const Token& token) + { + if (allowQuotedStrings) { + if (token.type != TOKEN_QUOTED_STRING + && token.type != TOKEN_WORD) { + throw ParseError("expected quoted-string or word", + token.pos); + } + } else { + if (token.type != TOKEN_WORD) + throw ParseError("expected word", token.pos); + } + + BString element(token.text); + if (convertToLowerCase) + element.ToLower(); + + value->Add(element); + } + } stringParser(value, allowQuotedStrings, convertToLowerCase); + + _ParseList(stringParser, true); +} + + +uint32 +BPackageInfo::Parser::_ParseFlags() +{ + struct FlagParser : public ListElementParser { + uint32 flags; + + FlagParser() + : + flags(0) + { + } + + virtual void operator()(const Token& token) + { + if (token.type != TOKEN_WORD) + throw ParseError("expected word (a flag)", token.pos); + + if (token.text.ICompare("approve_license") == 0) + flags |= B_PACKAGE_FLAG_APPROVE_LICENSE; + else if (token.text.ICompare("system_package") == 0) + flags |= B_PACKAGE_FLAG_SYSTEM_PACKAGE; + else { + throw ParseError( + "expected 'approve_license' or 'system_package'", + token.pos); + } + } + } flagParser; + + _ParseList(flagParser, true); + + return flagParser.flags; +} + + +void +BPackageInfo::Parser::_ParseResolvableList( + BObjectList* value) +{ + struct ResolvableParser : public ListElementParser { + Parser& parser; + BObjectList* value; + + ResolvableParser(Parser& parser_, + BObjectList* value_) + : + parser(parser_), + value(value_) + { + } + + virtual void operator()(const Token& token) + { + if (token.type != TOKEN_WORD) { + throw ParseError("expected word (a resolvable name)", + token.pos); + } + + int32 errorPos; + if (!_IsValidResolvableName(token.text, &errorPos)) { + throw ParseError("invalid character in resolvable name", + token.pos + errorPos); + } + + // parse version + BPackageVersion version; + Token op = parser._NextToken(); + if (op.type == TOKEN_OPERATOR_ASSIGN) { + parser._ParseVersionValue(&version, true); + } else if (op.type == TOKEN_ITEM_SEPARATOR + || op.type == TOKEN_CLOSE_BRACE) { + parser._RewindTo(op); + } else + throw ParseError("expected '=', comma or '}'", op.pos); + + // parse compatible version + BPackageVersion compatibleVersion; + Token compatible = parser._NextToken(); + if (compatible.type == TOKEN_WORD + && (compatible.text == "compat" + || compatible.text == "compatible")) { + op = parser._NextToken(); + if (op.type == TOKEN_OPERATOR_GREATER_EQUAL) { + parser._ParseVersionValue(&compatibleVersion, true); + } else + parser._RewindTo(compatible); + } else + parser._RewindTo(compatible); + + value->AddItem(new BPackageResolvable(token.text, version, + compatibleVersion)); + } + } resolvableParser(*this, value); + + _ParseList(resolvableParser, false); +} + + +void +BPackageInfo::Parser::_ParseResolvableExprList( + BObjectList* value, BString* _basePackage) +{ + struct ResolvableExpressionParser : public ListElementParser { + Parser& parser; + BObjectList* value; + BString* basePackage; + + ResolvableExpressionParser(Parser& parser, + BObjectList* value, + BString* basePackage) + : + parser(parser), + value(value), + basePackage(basePackage) + { + } + + virtual void operator()(const Token& token) + { + if (token.type != TOKEN_WORD) { + throw ParseError("expected word (a resolvable name)", + token.pos); + } + + int32 errorPos; + if (!_IsValidResolvableName(token.text, &errorPos)) { + throw ParseError("invalid character in resolvable name", + token.pos + errorPos); + } + + BPackageVersion version; + Token op = parser._NextToken(); + if (op.type == TOKEN_OPERATOR_LESS + || op.type == TOKEN_OPERATOR_LESS_EQUAL + || op.type == TOKEN_OPERATOR_EQUAL + || op.type == TOKEN_OPERATOR_NOT_EQUAL + || op.type == TOKEN_OPERATOR_GREATER_EQUAL + || op.type == TOKEN_OPERATOR_GREATER) { + parser._ParseVersionValue(&version, true); + + if (basePackage != NULL) { + Token base = parser._NextToken(); + if (base.type == TOKEN_WORD && base.text == "base") { + if (!basePackage->IsEmpty()) { + throw ParseError( + "multiple packages marked as base package", + token.pos); + } + + *basePackage = token.text; + } else + parser._RewindTo(base); + } + } else if (op.type == TOKEN_ITEM_SEPARATOR + || op.type == TOKEN_CLOSE_BRACE) { + parser._RewindTo(op); + } else { + throw ParseError( + "expected '<', '<=', '==', '!=', '>=', '>', comma or '}'", + op.pos); + } + + BPackageResolvableOperator resolvableOperator + = (BPackageResolvableOperator)(op.type - TOKEN_OPERATOR_LESS); + + value->AddItem(new BPackageResolvableExpression(token.text, + resolvableOperator, version)); + } + } resolvableExpressionParser(*this, value, _basePackage); + + _ParseList(resolvableExpressionParser, false); +} + + +void +BPackageInfo::Parser::_ParseGlobalSettingsFileInfos( + GlobalSettingsFileInfoList* infos) +{ + struct GlobalSettingsFileInfoParser : public ListElementParser { + Parser& parser; + GlobalSettingsFileInfoList* infos; + + GlobalSettingsFileInfoParser(Parser& parser, + GlobalSettingsFileInfoList* infos) + : + parser(parser), + infos(infos) + { + } + + virtual void operator()(const Token& token) + { + if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { + throw ParseError("expected string (a settings file path)", + token.pos); + } + + BSettingsFileUpdateType updateType + = B_SETTINGS_FILE_UPDATE_TYPE_ENUM_COUNT; + + Token nextToken = parser._NextToken(); + if (nextToken.type == TOKEN_WORD) { + const char* const* end = kSettingsFileUpdateTypes + + B_SETTINGS_FILE_UPDATE_TYPE_ENUM_COUNT; + const char* const* found = std::find(kSettingsFileUpdateTypes, + end, nextToken.text); + if (found == end) { + throw ParseError(BString("expected an update type"), + nextToken.pos); + } + updateType = (BSettingsFileUpdateType)( + found - kSettingsFileUpdateTypes); + } else if (nextToken.type == TOKEN_ITEM_SEPARATOR + || nextToken.type == TOKEN_CLOSE_BRACE) { + parser._RewindTo(nextToken); + } else { + throw ParseError( + "expected 'included', semicolon, new line or '}'", + nextToken.pos); + } + + if (!infos->AddItem(new BGlobalSettingsFileInfo(token.text, + updateType))) { + throw std::bad_alloc(); + } + } + } resolvableExpressionParser(*this, infos); + + _ParseList(resolvableExpressionParser, false); +} + + +void +BPackageInfo::Parser::_ParseUserSettingsFileInfos( + UserSettingsFileInfoList* infos) +{ + struct UserSettingsFileInfoParser : public ListElementParser { + Parser& parser; + UserSettingsFileInfoList* infos; + + UserSettingsFileInfoParser(Parser& parser, + UserSettingsFileInfoList* infos) + : + parser(parser), + infos(infos) + { + } + + virtual void operator()(const Token& token) + { + if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { + throw ParseError("expected string (a settings file path)", + token.pos); + } + + BString templatePath; + + Token nextToken = parser._NextToken(); + if (nextToken.type == TOKEN_WORD && nextToken.text == "template") { + nextToken = parser._NextToken(); + if (nextToken.type != TOKEN_WORD + && nextToken.type != TOKEN_QUOTED_STRING) { + throw ParseError( + "expected string (a settings template file path)", + nextToken.pos); + } + templatePath = nextToken.text; + } else if (nextToken.type == TOKEN_ITEM_SEPARATOR + || nextToken.type == TOKEN_CLOSE_BRACE) { + parser._RewindTo(nextToken); + } else { + throw ParseError( + "expected 'template', semicolon, new line or '}'", + nextToken.pos); + } + + if (!infos->AddItem(new BUserSettingsFileInfo(token.text, + templatePath))) { + throw std::bad_alloc(); + } + } + } resolvableExpressionParser(*this, infos); + + _ParseList(resolvableExpressionParser, false); +} + + +void +BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo) +{ + bool seen[B_PACKAGE_INFO_ENUM_COUNT]; + for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; ++i) + seen[i] = false; + + const char* const* names = BPackageInfo::kElementNames; + + while (Token t = _NextToken()) { + if (t.type == TOKEN_ITEM_SEPARATOR) + continue; + + if (t.type != TOKEN_WORD) + throw ParseError("expected word (a variable name)", t.pos); + + BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT; + for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) { + if (names[i] != NULL && t.text.ICompare(names[i]) == 0) { + attribute = (BPackageInfoAttributeID)i; + break; + } + } + + if (attribute == B_PACKAGE_INFO_ENUM_COUNT) { + BString error = BString("unknown attribute \"") << t.text << '"'; + throw ParseError(error, t.pos); + } + + if (seen[attribute]) { + BString error = BString(names[attribute]) << " already seen!"; + throw ParseError(error, t.pos); + } + + switch (attribute) { + case B_PACKAGE_INFO_NAME: + { + BString name; + const char* namePos; + _ParseStringValue(&name, &namePos); + + int32 errorPos; + if (!_IsValidResolvableName(name, &errorPos)) { + throw ParseError("invalid character in package name", + namePos + errorPos); + } + + packageInfo->SetName(name); + break; + } + + case B_PACKAGE_INFO_SUMMARY: + { + BString summary; + _ParseStringValue(&summary); + if (summary.FindFirst('\n') >= 0) + throw ParseError("the summary contains linebreaks", t.pos); + packageInfo->SetSummary(summary); + break; + } + + case B_PACKAGE_INFO_DESCRIPTION: + _ParseStringValue(&packageInfo->fDescription); + break; + + case B_PACKAGE_INFO_VENDOR: + _ParseStringValue(&packageInfo->fVendor); + break; + + case B_PACKAGE_INFO_PACKAGER: + _ParseStringValue(&packageInfo->fPackager); + break; + + case B_PACKAGE_INFO_BASE_PACKAGE: + _ParseStringValue(&packageInfo->fBasePackage); + break; + + case B_PACKAGE_INFO_ARCHITECTURE: + _ParseArchitectureValue(&packageInfo->fArchitecture); + break; + + case B_PACKAGE_INFO_VERSION: + _ParseVersionValue(&packageInfo->fVersion, false); + break; + + case B_PACKAGE_INFO_COPYRIGHTS: + _ParseStringList(&packageInfo->fCopyrightList); + break; + + case B_PACKAGE_INFO_LICENSES: + _ParseStringList(&packageInfo->fLicenseList); + break; + + case B_PACKAGE_INFO_URLS: + _ParseStringList(&packageInfo->fURLList); + break; + + case B_PACKAGE_INFO_SOURCE_URLS: + _ParseStringList(&packageInfo->fSourceURLList); + break; + + case B_PACKAGE_INFO_GLOBAL_SETTINGS_FILES: + _ParseGlobalSettingsFileInfos( + &packageInfo->fGlobalSettingsFileInfos); + break; + + case B_PACKAGE_INFO_USER_SETTINGS_FILES: + _ParseUserSettingsFileInfos( + &packageInfo->fUserSettingsFileInfos); + break; + + case B_PACKAGE_INFO_PROVIDES: + _ParseResolvableList(&packageInfo->fProvidesList); + break; + + case B_PACKAGE_INFO_REQUIRES: + packageInfo->fBasePackage.Truncate(0); + _ParseResolvableExprList(&packageInfo->fRequiresList, + &packageInfo->fBasePackage); + break; + + case B_PACKAGE_INFO_SUPPLEMENTS: + _ParseResolvableExprList(&packageInfo->fSupplementsList); + break; + + case B_PACKAGE_INFO_CONFLICTS: + _ParseResolvableExprList(&packageInfo->fConflictsList); + break; + + case B_PACKAGE_INFO_FRESHENS: + _ParseResolvableExprList(&packageInfo->fFreshensList); + break; + + case B_PACKAGE_INFO_REPLACES: + _ParseStringList(&packageInfo->fReplacesList, false, true); + break; + + case B_PACKAGE_INFO_FLAGS: + packageInfo->SetFlags(_ParseFlags()); + break; + + default: + // can never get here + break; + } + + seen[attribute] = true; + } + + // everything up to and including 'provides' is mandatory + for (int i = 0; i <= B_PACKAGE_INFO_PROVIDES; ++i) { + if (!seen[i]) { + BString error = BString(names[i]) << " is not being set anywhere!"; + throw ParseError(error, fPos); + } + } +} + + +/*static*/ inline bool +BPackageInfo::Parser::_IsAlphaNumUnderscore(const BString& string, + const char* additionalChars, int32* _errorPos) +{ + return _IsAlphaNumUnderscore(string.String(), + string.String() + string.Length(), additionalChars, _errorPos); +} + + +/*static*/ inline bool +BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* string, + const char* additionalChars, int32* _errorPos) +{ + return _IsAlphaNumUnderscore(string, string + strlen(string), + additionalChars, _errorPos); +} + + +/*static*/ bool +BPackageInfo::Parser::_IsAlphaNumUnderscore(const char* start, const char* end, + const char* additionalChars, int32* _errorPos) +{ + for (const char* c = start; c < end; c++) { + if (!isalnum(*c) && *c != '_' && strchr(additionalChars, *c) == NULL) { + if (_errorPos != NULL) + *_errorPos = c - start; + return false; + } + } + + return true; +} + + +/*static*/ bool +BPackageInfo::Parser::_IsValidResolvableName(const char* string, + int32* _errorPos) +{ + for (const char* c = string; *c != '\0'; c++) { + switch (*c) { + case '-': + case '/': + case '<': + case '>': + case '=': + case '!': + break; + default: + if (!isspace(*c)) + continue; + break; + } + + if (_errorPos != NULL) + *_errorPos = c - string; + return false; + } + return true; +} + + +} // namespace BPackageKit diff --git a/src/kits/package/PackageInfoParser.h b/src/kits/package/PackageInfoParser.h new file mode 100644 index 0000000000..beb547c105 --- /dev/null +++ b/src/kits/package/PackageInfoParser.h @@ -0,0 +1,173 @@ +/* + * Copyright 2011, Oliver Tappe + * Distributed under the terms of the MIT License. + */ +#ifndef PACKAGE_INFO_PARSER_H +#define PACKAGE_INFO_PARSER_H + + +#include + + +namespace BPackageKit { + + +/* + * Parses a ".PackageInfo" file and fills a BPackageInfo object with the + * package info elements found. + */ +class BPackageInfo::Parser { +public: + Parser(ParseErrorListener* listener = NULL); + + status_t Parse(const BString& packageInfoString, + BPackageInfo* packageInfo); + + status_t ParseVersion(const BString& versionString, + bool revisionIsOptional, + BPackageVersion& _version); + +private: + struct ParseError; + struct Token; + struct ListElementParser; + friend struct ListElementParser; + + enum TokenType { + TOKEN_WORD, + TOKEN_QUOTED_STRING, + TOKEN_OPERATOR_ASSIGN, + TOKEN_OPERATOR_LESS, + TOKEN_OPERATOR_LESS_EQUAL, + TOKEN_OPERATOR_EQUAL, + TOKEN_OPERATOR_NOT_EQUAL, + TOKEN_OPERATOR_GREATER_EQUAL, + TOKEN_OPERATOR_GREATER, + TOKEN_OPEN_BRACE, + TOKEN_CLOSE_BRACE, + TOKEN_ITEM_SEPARATOR, + // + TOKEN_EOF, + }; + +private: + Token _NextToken(); + void _RewindTo(const Token& token); + + void _ParseStringValue(BString* value, + const char** _tokenPos = NULL); + uint32 _ParseFlags(); + void _ParseArchitectureValue( + BPackageArchitecture* value); + void _ParseVersionValue(BPackageVersion* value, + bool revisionIsOptional); + static void _ParseVersionValue(Token& word, + BPackageVersion* value, + bool revisionIsOptional); + void _ParseList(ListElementParser& elementParser, + bool allowSingleNonListElement); + void _ParseStringList(BStringList* value, + bool allowQuotedStrings = true, + bool convertToLowerCase = false); + void _ParseResolvableList( + BObjectList* value); + void _ParseResolvableExprList( + BObjectList* + value, + BString* _basePackage = NULL); + void _ParseGlobalSettingsFileInfos( + GlobalSettingsFileInfoList* infos); + void _ParseUserSettingsFileInfos( + UserSettingsFileInfoList* infos); + + void _Parse(BPackageInfo* packageInfo); + + static bool _IsAlphaNumUnderscore(const BString& string, + const char* additionalChars, + int32* _errorPos); + static bool _IsAlphaNumUnderscore(const char* string, + const char* additionalChars, + int32* _errorPos); + static bool _IsAlphaNumUnderscore(const char* start, + const char* end, + const char* additionalChars, + int32* _errorPos); + static bool _IsValidResolvableName(const char* string, + int32* _errorPos); + +private: + ParseErrorListener* fListener; + const char* fPos; +}; + + +struct BPackageInfo::Parser::ParseError { + BString message; + const char* pos; + + ParseError(const BString& _message, const char* _pos) + : message(_message), pos(_pos) + { + } +}; + + +struct BPackageInfo::Parser::Token { + TokenType type; + BString text; + const char* pos; + + Token(TokenType _type, const char* _pos, int length = 0) + : type(_type), pos(_pos) + { + if (length != 0) { + text.SetTo(pos, length); + + if (type == TOKEN_QUOTED_STRING) { + // unescape value of quoted string + char* value = text.LockBuffer(length); + if (value == NULL) + return; + int index = 0; + int newIndex = 0; + bool lastWasEscape = false; + while (char c = value[index++]) { + if (lastWasEscape) { + lastWasEscape = false; + // map \n to newline and \t to tab + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } else if (c == '\\') { + lastWasEscape = true; + continue; + } + value[newIndex++] = c; + } + value[newIndex] = '\0'; + text.UnlockBuffer(newIndex); + } + } + } + + operator bool() const + { + return type != TOKEN_EOF; + } +}; + + +struct BPackageInfo::Parser::ListElementParser { + virtual ~ListElementParser() + { + } + + virtual void operator()(const Token& token) = 0; +}; + + +} // namespace BPackageKit + + +#endif // PACKAGE_INFO_PARSER_H