/*
 * 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
