package info parser: improve string parsing

* Instead of two string token types (TOKEN_WORD, TOKEN_QUOTED_STRING),
  there's now only one (TOKEN_STRING). Whether the string meets the
  criteria is checked where needed. In most cases the check was already
  done or not necessary anyway.
* Strings can now consist of an arbitrary sequence of quoted and
  unquoted strings and escaping is also supported in unquoted string
  segments.
* Among other things this fixes incorrect restrictions for resolvable
  names and should also make quoting paths superfluous (unless they
  contain separator characters).
This commit is contained in:
Ingo Weinhold
2013-07-09 21:42:46 +02:00
parent 276c321bcd
commit 0e9ec703dd
2 changed files with 128 additions and 113 deletions
+116 -78
View File
@@ -10,6 +10,7 @@
#include <stdint.h> #include <stdint.h>
#include <algorithm> #include <algorithm>
#include <string>
namespace BPackageKit { namespace BPackageKit {
@@ -84,7 +85,7 @@ BPackageInfo::Parser::ParseVersion(const BString& versionString,
fPos = versionString.String(); fPos = versionString.String();
try { try {
Token token(TOKEN_WORD, fPos, versionString.Length()); Token token(TOKEN_STRING, fPos, versionString.Length());
_ParseVersionValue(token, &_version, revisionIsOptional); _ParseVersionValue(token, &_version, revisionIsOptional);
} catch (const ParseError& error) { } catch (const ParseError& error) {
if (fListener != NULL) { if (fListener != NULL) {
@@ -175,38 +176,76 @@ BPackageInfo::Parser::_NextToken()
} }
return Token(TOKEN_OPERATOR_GREATER, tokenPos, 1); 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: default:
{ {
const char* start = fPos; std::string string;
while (isalnum(*fPos) || *fPos == '.' || *fPos == '-' char quoteChar = '\0';
|| *fPos == '_' || *fPos == ':' || *fPos == '+'
|| *fPos == '~') { for (; *fPos != '\0'; fPos++) {
fPos++; char c = *fPos;
if (quoteChar != '\0') {
// within a quoted string segment
if (c == quoteChar) {
quoteChar = '\0';
continue;
} }
if (fPos == start)
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; break;
return Token(TOKEN_WORD, start, fPos - start);
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());
} }
} }
@@ -226,8 +265,8 @@ void
BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos) BPackageInfo::Parser::_ParseStringValue(BString* value, const char** _tokenPos)
{ {
Token string = _NextToken(); Token string = _NextToken();
if (string.type != TOKEN_QUOTED_STRING && string.type != TOKEN_WORD) if (string.type != TOKEN_STRING)
throw ParseError("expected quoted-string or word", string.pos); throw ParseError("expected string", string.pos);
*value = string.text; *value = string.text;
if (_tokenPos != NULL) if (_tokenPos != NULL)
@@ -239,7 +278,7 @@ void
BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value) BPackageInfo::Parser::_ParseArchitectureValue(BPackageArchitecture* value)
{ {
Token arch = _NextToken(); Token arch = _NextToken();
if (arch.type == TOKEN_WORD) { if (arch.type == TOKEN_STRING) {
for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) { for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) { if (arch.text.ICompare(BPackageInfo::kArchitectureNames[i]) == 0) {
*value = (BPackageArchitecture)i; *value = (BPackageArchitecture)i;
@@ -272,8 +311,8 @@ BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value, BPackageInfo::Parser::_ParseVersionValue(Token& word, BPackageVersion* value,
bool revisionIsOptional) bool revisionIsOptional)
{ {
if (word.type != TOKEN_WORD) if (word.type != TOKEN_STRING)
throw ParseError("expected word (a version)", word.pos); throw ParseError("expected string (a version)", word.pos);
// get the revision number // get the revision number
uint32 revision = 0; uint32 revision = 0;
@@ -388,33 +427,33 @@ BPackageInfo::Parser::_ParseList(ListElementParser& elementParser,
void void
BPackageInfo::Parser::_ParseStringList(BStringList* value, BPackageInfo::Parser::_ParseStringList(BStringList* value,
bool allowQuotedStrings, bool convertToLowerCase) bool requireResolvableName, bool convertToLowerCase)
{ {
struct StringParser : public ListElementParser { struct StringParser : public ListElementParser {
BStringList* value; BStringList* value;
bool allowQuotedStrings; bool requireResolvableName;
bool convertToLowerCase; bool convertToLowerCase;
StringParser(BStringList* value, bool allowQuotedStrings, StringParser(BStringList* value, bool requireResolvableName,
bool convertToLowerCase) bool convertToLowerCase)
: :
value(value), value(value),
allowQuotedStrings(allowQuotedStrings), requireResolvableName(requireResolvableName),
convertToLowerCase(convertToLowerCase) convertToLowerCase(convertToLowerCase)
{ {
} }
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (allowQuotedStrings) { if (token.type != TOKEN_STRING)
if (token.type != TOKEN_QUOTED_STRING throw ParseError("expected string", token.pos);
&& token.type != TOKEN_WORD) {
throw ParseError("expected quoted-string or word", if (requireResolvableName) {
token.pos); int32 errorPos;
if (!_IsValidResolvableName(token.text, &errorPos)) {
throw ParseError("invalid character in resolvable name",
token.pos + errorPos);
} }
} else {
if (token.type != TOKEN_WORD)
throw ParseError("expected word", token.pos);
} }
BString element(token.text); BString element(token.text);
@@ -423,7 +462,7 @@ BPackageInfo::Parser::_ParseStringList(BStringList* value,
value->Add(element); value->Add(element);
} }
} stringParser(value, allowQuotedStrings, convertToLowerCase); } stringParser(value, requireResolvableName, convertToLowerCase);
_ParseList(stringParser, true); _ParseList(stringParser, true);
} }
@@ -443,7 +482,7 @@ BPackageInfo::Parser::_ParseFlags()
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD) if (token.type != TOKEN_STRING)
throw ParseError("expected word (a flag)", token.pos); throw ParseError("expected word (a flag)", token.pos);
if (token.text.ICompare("approve_license") == 0) if (token.text.ICompare("approve_license") == 0)
@@ -482,7 +521,7 @@ BPackageInfo::Parser::_ParseResolvableList(
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD) { if (token.type != TOKEN_STRING) {
throw ParseError("expected word (a resolvable name)", throw ParseError("expected word (a resolvable name)",
token.pos); token.pos);
} }
@@ -507,7 +546,7 @@ BPackageInfo::Parser::_ParseResolvableList(
// parse compatible version // parse compatible version
BPackageVersion compatibleVersion; BPackageVersion compatibleVersion;
Token compatible = parser._NextToken(); Token compatible = parser._NextToken();
if (compatible.type == TOKEN_WORD if (compatible.type == TOKEN_STRING
&& (compatible.text == "compat" && (compatible.text == "compat"
|| compatible.text == "compatible")) { || compatible.text == "compatible")) {
op = parser._NextToken(); op = parser._NextToken();
@@ -548,7 +587,7 @@ BPackageInfo::Parser::_ParseResolvableExprList(
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD) { if (token.type != TOKEN_STRING) {
throw ParseError("expected word (a resolvable name)", throw ParseError("expected word (a resolvable name)",
token.pos); token.pos);
} }
@@ -571,7 +610,7 @@ BPackageInfo::Parser::_ParseResolvableExprList(
if (basePackage != NULL) { if (basePackage != NULL) {
Token base = parser._NextToken(); Token base = parser._NextToken();
if (base.type == TOKEN_WORD && base.text == "base") { if (base.type == TOKEN_STRING && base.text == "base") {
if (!basePackage->IsEmpty()) { if (!basePackage->IsEmpty()) {
throw ParseError( throw ParseError(
"multiple packages marked as base package", "multiple packages marked as base package",
@@ -621,8 +660,8 @@ BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { if (token.type != TOKEN_STRING) {
throw ParseError("expected string (a settings file path)", throw ParseError("expected string (a file path)",
token.pos); token.pos);
} }
@@ -631,12 +670,13 @@ BPackageInfo::Parser::_ParseGlobalWritableFileInfos(
bool isDirectory = false; bool isDirectory = false;
Token nextToken = parser._NextToken(); Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_WORD && nextToken.text == "directory") { if (nextToken.type == TOKEN_STRING
&& nextToken.text == "directory") {
isDirectory = true; isDirectory = true;
nextToken = parser._NextToken(); nextToken = parser._NextToken();
} }
if (nextToken.type == TOKEN_WORD) { if (nextToken.type == TOKEN_STRING) {
const char* const* end = kWritableFileUpdateTypes const char* const* end = kWritableFileUpdateTypes
+ B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT; + B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT;
const char* const* found = std::find(kWritableFileUpdateTypes, const char* const* found = std::find(kWritableFileUpdateTypes,
@@ -685,7 +725,7 @@ BPackageInfo::Parser::_ParseUserSettingsFileInfos(
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD && token.type != TOKEN_QUOTED_STRING) { if (token.type != TOKEN_STRING) {
throw ParseError("expected string (a settings file path)", throw ParseError("expected string (a settings file path)",
token.pos); token.pos);
} }
@@ -694,13 +734,13 @@ BPackageInfo::Parser::_ParseUserSettingsFileInfos(
bool isDirectory = false; bool isDirectory = false;
Token nextToken = parser._NextToken(); Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_WORD && nextToken.text == "directory") { if (nextToken.type == TOKEN_STRING
&& nextToken.text == "directory") {
isDirectory = true; isDirectory = true;
} else if (nextToken.type == TOKEN_WORD } else if (nextToken.type == TOKEN_STRING
&& nextToken.text == "template") { && nextToken.text == "template") {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD if (nextToken.type != TOKEN_STRING) {
&& nextToken.type != TOKEN_QUOTED_STRING) {
throw ParseError( throw ParseError(
"expected string (a settings template file path)", "expected string (a settings template file path)",
nextToken.pos); nextToken.pos);
@@ -744,9 +784,9 @@ BPackageInfo::Parser::_ParseUsers(UserList* users)
virtual void operator()(const Token& token) virtual void operator()(const Token& token)
{ {
if (token.type != TOKEN_WORD) { if (token.type != TOKEN_STRING
throw ParseError("expected a user name", || !BUser::IsValidUserName(token.text)) {
token.pos); throw ParseError("expected a user name", token.pos);
} }
BString realName; BString realName;
@@ -756,31 +796,28 @@ BPackageInfo::Parser::_ParseUsers(UserList* users)
for (;;) { for (;;) {
Token nextToken = parser._NextToken(); Token nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD) { if (nextToken.type != TOKEN_STRING) {
parser._RewindTo(nextToken); parser._RewindTo(nextToken);
break; break;
} }
if (nextToken.text == "real-name") { if (nextToken.text == "real-name") {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD if (nextToken.type != TOKEN_STRING) {
&& nextToken.type != TOKEN_QUOTED_STRING) {
throw ParseError("expected string (a user real name)", throw ParseError("expected string (a user real name)",
nextToken.pos); nextToken.pos);
} }
realName = nextToken.text; realName = nextToken.text;
} else if (nextToken.text == "home") { } else if (nextToken.text == "home") {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD if (nextToken.type != TOKEN_STRING) {
&& nextToken.type != TOKEN_QUOTED_STRING) {
throw ParseError("expected string (a home path)", throw ParseError("expected string (a home path)",
nextToken.pos); nextToken.pos);
} }
home = nextToken.text; home = nextToken.text;
} else if (nextToken.text == "shell") { } else if (nextToken.text == "shell") {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD if (nextToken.type != TOKEN_STRING) {
&& nextToken.type != TOKEN_QUOTED_STRING) {
throw ParseError("expected string (a shell path)", throw ParseError("expected string (a shell path)",
nextToken.pos); nextToken.pos);
} }
@@ -788,7 +825,8 @@ BPackageInfo::Parser::_ParseUsers(UserList* users)
} else if (nextToken.text == "groups") { } else if (nextToken.text == "groups") {
for (;;) { for (;;) {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type == TOKEN_WORD) { if (nextToken.type == TOKEN_STRING
&& BUser::IsValidUserName(nextToken.text)) {
if (!groups.Add(nextToken.text)) if (!groups.Add(nextToken.text))
throw std::bad_alloc(); throw std::bad_alloc();
} else if (nextToken.type == TOKEN_ITEM_SEPARATOR } else if (nextToken.type == TOKEN_ITEM_SEPARATOR
@@ -811,10 +849,10 @@ BPackageInfo::Parser::_ParseUsers(UserList* users)
BString templatePath; BString templatePath;
Token nextToken = parser._NextToken(); Token nextToken = parser._NextToken();
if (nextToken.type == TOKEN_WORD && nextToken.text == "template") { if (nextToken.type == TOKEN_STRING
&& nextToken.text == "template") {
nextToken = parser._NextToken(); nextToken = parser._NextToken();
if (nextToken.type != TOKEN_WORD if (nextToken.type != TOKEN_STRING) {
&& nextToken.type != TOKEN_QUOTED_STRING) {
throw ParseError( throw ParseError(
"expected string (a settings template file path)", "expected string (a settings template file path)",
nextToken.pos); nextToken.pos);
@@ -853,8 +891,8 @@ BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo)
if (t.type == TOKEN_ITEM_SEPARATOR) if (t.type == TOKEN_ITEM_SEPARATOR)
continue; continue;
if (t.type != TOKEN_WORD) if (t.type != TOKEN_STRING)
throw ParseError("expected word (a variable name)", t.pos); throw ParseError("expected string (a variable name)", t.pos);
BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT; BPackageInfoAttributeID attribute = B_PACKAGE_INFO_ENUM_COUNT;
for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) { for (int i = 0; i < B_PACKAGE_INFO_ENUM_COUNT; i++) {
@@ -986,7 +1024,7 @@ BPackageInfo::Parser::_Parse(BPackageInfo* packageInfo)
break; break;
case B_PACKAGE_INFO_REPLACES: case B_PACKAGE_INFO_REPLACES:
_ParseStringList(&packageInfo->fReplacesList, false, true); _ParseStringList(&packageInfo->fReplacesList, true);
break; break;
case B_PACKAGE_INFO_FLAGS: case B_PACKAGE_INFO_FLAGS:
+11 -34
View File
@@ -34,8 +34,7 @@ private:
friend struct ListElementParser; friend struct ListElementParser;
enum TokenType { enum TokenType {
TOKEN_WORD, TOKEN_STRING,
TOKEN_QUOTED_STRING,
TOKEN_OPERATOR_ASSIGN, TOKEN_OPERATOR_ASSIGN,
TOKEN_OPERATOR_LESS, TOKEN_OPERATOR_LESS,
TOKEN_OPERATOR_LESS_EQUAL, TOKEN_OPERATOR_LESS_EQUAL,
@@ -67,7 +66,7 @@ private:
void _ParseList(ListElementParser& elementParser, void _ParseList(ListElementParser& elementParser,
bool allowSingleNonListElement); bool allowSingleNonListElement);
void _ParseStringList(BStringList* value, void _ParseStringList(BStringList* value,
bool allowQuotedStrings = true, bool requireResolvableName = false,
bool convertToLowerCase = false); bool convertToLowerCase = false);
void _ParseResolvableList( void _ParseResolvableList(
BObjectList<BPackageResolvable>* value); BObjectList<BPackageResolvable>* value);
@@ -118,38 +117,16 @@ struct BPackageInfo::Parser::Token {
BString text; BString text;
const char* pos; const char* pos;
Token(TokenType _type, const char* _pos, int length = 0) Token(TokenType _type, const char* _pos, int length = 0,
: type(_type), pos(_pos) const char* text = NULL)
:
type(_type),
pos(_pos)
{ {
if (length != 0) { if (text != NULL)
text.SetTo(pos, length); this->text = text;
else if (length != 0)
if (type == TOKEN_QUOTED_STRING) { this->text.SetTo(pos, length);
// 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 operator bool() const