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