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