Files
linguist/samples/C++/PackageInfoParser.cpp
2016-06-28 01:10:55 +05:30

1179 lines
28 KiB
C++

/*
* Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
* Copyright 2016, Andrew Lindesay <apl@lindesay.co.nz>
* Distributed under the terms of the MIT License.
*
* Source - https://github.com/haiku/haiku/blob/73e180c9b965aaeb034055453e153bc3adf47917/src/kits/package/PackageInfoParser.cpp
*/
#include "PackageInfoParser.h"
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <algorithm>
#include <string>
#include <Url.h>
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 inLineOffset;
int32 offset = error.pos - packageInfoString.String();
int32 newlinePos = packageInfoString.FindLast('\n', offset - 1);
if (newlinePos < 0)
inLineOffset = offset;
else {
inLineOffset = offset - newlinePos - 1;
do {
line++;
newlinePos = packageInfoString.FindLast('\n',
newlinePos - 1);
} while (newlinePos >= 0);
}
int column = 0;
for (int i = 0; i < inLineOffset; i++) {
column++;
if (error.pos[i - inLineOffset] == '\t')
column = (column + 3) / 4 * 4;
}
fListener->OnError(error.message, line, column + 1);
}
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_STRING, 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;
}
status_t
BPackageInfo::Parser::ParseResolvableExpression(const BString& expressionString,
BPackageResolvableExpression& _expression)
{
fPos = expressionString.String();
try {
Token token(TOKEN_STRING, fPos, expressionString.Length());
_ParseResolvableExpression(_NextToken(), _expression, NULL);
} catch (const ParseError& error) {
if (fListener != NULL) {
int32 offset = error.pos - expressionString.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, comments, or escaped new lines. 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 == '#' || *fPos == '\\') {
if (*fPos == '#') {
inComment = true;
} else if (!inComment && *fPos == '\\') {
if (fPos[1] != '\n')
break;
// ignore escaped line breaks
fPos++;
} 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);
default:
{
std::string string;
char quoteChar = '\0';
for (; *fPos != '\0'; fPos++) {
char c = *fPos;
if (quoteChar != '\0') {
// within a quoted string segment
if (c == quoteChar) {
quoteChar = '\0';
continue;
}
if (c == '\\') {
// next char is escaped
c = *++fPos;
if (c == '\0') {
throw ParseError("unterminated quoted-string",
tokenPos);
}
if (c == 'n')
c = '\n';
else if (c == 't')
c = '\t';
}
string += c;
} else {
// unquoted string segment
switch (c) {
case '"':
case '\'':
// quoted string start
quoteChar = c;
continue;
case '{':
case '}':
case '<':
case '=':
case '!':
case '>':
// a separator character -- this ends the string
break;
case '\\':
// next char is escaped
c = *++fPos;
if (c == '\0') {
throw ParseError("'\\' at end of string",
tokenPos);
}
string += c;
continue;
default:
if (isspace(c))
break;
string += c;
continue;
}
break;
}
}
return Token(TOKEN_STRING, tokenPos, fPos - tokenPos,
string.c_str());
}
}
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_STRING)
throw ParseError("expected string", string.pos);
*value = string.text;
if (_tokenPos != NULL)
*_tokenPos = string.pos;
}
void
BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value)
{
Token arch = _NextToken();
if (arch.type == TOKEN_STRING) {
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_STRING)
throw ParseError("expected string (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 (-<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::_ParseResolvableExpression(const Token& token,
BPackageResolvableExpression& _value, BString* _basePackage)
{
if (token.type != TOKEN_STRING) {
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 = _NextToken();
BPackageResolvableOperator resolvableOperator;
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) {
_ParseVersionValue(&version, true);
if (_basePackage != NULL) {
Token base = _NextToken();
if (base.type == TOKEN_STRING && base.text == "base") {
if (!_basePackage->IsEmpty()) {
throw ParseError("multiple packages marked as base package",
token.pos);
}
*_basePackage = token.text;
} else
_RewindTo(base);
}
resolvableOperator = (BPackageResolvableOperator)
(op.type - TOKEN_OPERATOR_LESS);
} else if (op.type == TOKEN_ITEM_SEPARATOR
|| op.type == TOKEN_CLOSE_BRACE || op.type == TOKEN_EOF) {
_RewindTo(op);
resolvableOperator = B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT;
} else {
throw ParseError(
"expected '<', '<=', '==', '!=', '>=', '>', comma or '}'",
op.pos);
}
_value.SetTo(token.text, resolvableOperator, version);
}
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 requireResolvableName, bool convertToLowerCase,
StringValidator* stringValidator)
{
struct StringParser : public ListElementParser {
BStringList* value;
bool requireResolvableName;
bool convertToLowerCase;
StringValidator* stringValidator;
StringParser(BStringList* value, bool requireResolvableName,
bool convertToLowerCase, StringValidator* stringValidator)
:
value(value),
requireResolvableName(requireResolvableName),
convertToLowerCase(convertToLowerCase),
stringValidator(stringValidator)
{
}
virtual void operator()(const Token& token)
{
if (token.type != TOKEN_STRING)
throw ParseError("expected string", token.pos);
if (requireResolvableName) {
int32 errorPos;
if (!_IsValidResolvableName(token.text, &errorPos)) {
throw ParseError("invalid character in resolvable name",
token.pos + errorPos);
}
}
BString element(token.text);
if (convertToLowerCase)
element.ToLower();
if (stringValidator != NULL)
stringValidator->Validate(element, token.pos);
value->Add(element);
}
} stringParser(value, requireResolvableName, convertToLowerCase,
stringValidator);
_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_STRING)
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<BPackageResolvable>* value)
{
struct ResolvableParser : public ListElementParser {
Parser& parser;
BObjectList<BPackageResolvable>* value;
ResolvableParser(Parser& parser_,
BObjectList<BPackageResolvable>* value_)
:
parser(parser_),
value(value_)
{
}
virtual void operator()(const Token& token)
{
if (token.type != TOKEN_STRING) {
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_STRING
&& (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<BPackageResolvableExpression>* value, BString* _basePackage)
{
struct ResolvableExpressionParser : public ListElementParser {
Parser& parser;
BObjectList<BPackageResolvableExpression>* value;
BString* basePackage;
ResolvableExpressionParser(Parser& parser,
BObjectList<BPackageResolvableExpression>* value,
BString* basePackage)
:
parser(parser),
value(value),
basePackage(basePackage)
{
}
virtual void operator()(const Token& token)
{
BPackageResolvableExpression expression;
parser._ParseResolvableExpression(token, expression, basePackage);
value->AddItem(new BPackageResolvableExpression(expression));
}
} resolvableExpressionParser(*this, value, _basePackage);
_ParseList(resolvableExpressionParser, false);
}
void
BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
GlobalWritableFileInfoList* infos)
{
struct GlobalWritableFileInfoParser : public ListElementParser {
Parser& parser;
GlobalWritableFileInfoList* infos;
GlobalWritableFileInfoParser(Parser& parser,
GlobalWritableFileInfoList* infos)
:
parser(parser),
infos(infos)
{
}
virtual void operator()(const Token& token)
{
if (token.type != TOKEN_STRING) {
throw ParseError("expected string (a file path)",
token.pos);
}
BWritableFileUpdateType updateType
= B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
bool isDirectory = false;
Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_STRING
&& nextToken.text == "directory") {
isDirectory = true;
nextToken = parser._NextToken();
}
if (nextToken.type == TOKEN_STRING) {
const char* const* end = kWritableFileUpdateTypes
+ B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
const char* const* found = std::find(kWritableFileUpdateTypes,
end, nextToken.text);
if (found == end) {
throw ParseError(BString("expected an update type"),
nextToken.pos);
}
updateType = (BWritableFileUpdateType)(
found - kWritableFileUpdateTypes);
} 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 BGlobalWritableFileInfo(token.text,
updateType, isDirectory))) {
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_STRING) {
throw ParseError("expected string (a settings file path)",
token.pos);
}
BString templatePath;
bool isDirectory = false;
Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_STRING
&& nextToken.text == "directory") {
isDirectory = true;
} else if (nextToken.type == TOKEN_STRING
&& nextToken.text == "template") {
nextToken = parser._NextToken();
if (nextToken.type != TOKEN_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 (isDirectory
? !infos->AddItem(new BUserSettingsFileInfo(token.text, true))
: !infos->AddItem(new BUserSettingsFileInfo(token.text,
templatePath))) {
throw std::bad_alloc();
}
}
} resolvableExpressionParser(*this, infos);
_ParseList(resolvableExpressionParser, false);
}
void
BPackageInfo::Parser::_ParseUsers(UserList* users)
{
struct UserParser : public ListElementParser {
Parser& parser;
UserList* users;
UserParser(Parser& parser, UserList* users)
:
parser(parser),
users(users)
{
}
virtual void operator()(const Token& token)
{
if (token.type != TOKEN_STRING
|| !BUser::IsValidUserName(token.text)) {
throw ParseError("expected a user name", token.pos);
}
BString realName;
BString home;
BString shell;
BStringList groups;
for (;;) {
Token nextToken = parser._NextToken();
if (nextToken.type != TOKEN_STRING) {
parser._RewindTo(nextToken);
break;
}
if (nextToken.text == "real-name") {
nextToken = parser._NextToken();
if (nextToken.type != TOKEN_STRING) {
throw ParseError("expected string (a user real name)",
nextToken.pos);
}
realName = nextToken.text;
} else if (nextToken.text == "home") {
nextToken = parser._NextToken();
if (nextToken.type != TOKEN_STRING) {
throw ParseError("expected string (a home path)",
nextToken.pos);
}
home = nextToken.text;
} else if (nextToken.text == "shell") {
nextToken = parser._NextToken();
if (nextToken.type != TOKEN_STRING) {
throw ParseError("expected string (a shell path)",
nextToken.pos);
}
shell = nextToken.text;
} else if (nextToken.text == "groups") {
for (;;) {
nextToken = parser._NextToken();
if (nextToken.type == TOKEN_STRING
&& BUser::IsValidUserName(nextToken.text)) {
if (!groups.Add(nextToken.text))
throw std::bad_alloc();
} else if (nextToken.type == TOKEN_ITEM_SEPARATOR
|| nextToken.type == TOKEN_CLOSE_BRACE) {
parser._RewindTo(nextToken);
break;
} else {
throw ParseError("expected a group name",
nextToken.pos);
}
}
break;
} else {
throw ParseError(
"expected 'real-name', 'home', 'shell', or 'groups'",
nextToken.pos);
}
}
BString templatePath;
Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_STRING
&& nextToken.text == "template") {
nextToken = parser._NextToken();
if (nextToken.type != TOKEN_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 (!users->AddItem(new BUser(token.text, realName, home, shell,
groups))) {
throw std::bad_alloc();
}
}
} resolvableExpressionParser(*this, users);
_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_STRING)
throw ParseError("expected string (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:
{
UrlStringValidator stringValidator;
_ParseStringList(&packageInfo->fURLList,
false, false, &stringValidator);
}
break;
case B_PACKAGE_INFO_SOURCE_URLS:
{
UrlStringValidator stringValidator;
_ParseStringList(&packageInfo->fSourceURLList,
false, false, &stringValidator);
}
break;
case B_PACKAGE_INFO_GLOBAL_WRITABLE_FILES:
_ParseGlobalWritableFileInfos(
&packageInfo->fGlobalWritableFileInfos);
break;
case B_PACKAGE_INFO_USER_SETTINGS_FILES:
_ParseUserSettingsFileInfos(
&packageInfo->fUserSettingsFileInfos);
break;
case B_PACKAGE_INFO_USERS:
_ParseUsers(&packageInfo->fUsers);
break;
case B_PACKAGE_INFO_GROUPS:
_ParseStringList(&packageInfo->fGroups);
break;
case B_PACKAGE_INFO_POST_INSTALL_SCRIPTS:
_ParseStringList(&packageInfo->fPostInstallScripts);
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, 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;
}
void
BPackageInfo::Parser::UrlStringValidator::Validate(const BString& urlString,
const char* pos)
{
BUrl url(urlString);
if (!url.IsValid())
throw ParseError("invalid url", pos);
}
} // namespace BPackageKit