xref: /haiku/src/kits/package/PackageInfoParser.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
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 
19 namespace BPackageKit {
20 
21 
22 BPackageInfo::ParseErrorListener::~ParseErrorListener()
23 {
24 }
25 
26 
27 BPackageInfo::Parser::Parser(ParseErrorListener* listener)
28 	:
29 	fListener(listener),
30 	fPos(NULL)
31 {
32 }
33 
34 
35 status_t
36 BPackageInfo::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 
84 status_t
85 BPackageInfo::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 
109 status_t
110 BPackageInfo::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 
134 BPackageInfo::Parser::Token
135 BPackageInfo::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 
285 void
286 BPackageInfo::Parser::_RewindTo(const Token& token)
287 {
288 	fPos = token.pos;
289 }
290 
291 
292 void
293 BPackageInfo::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 
305 void
306 BPackageInfo::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 
329 void
330 BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
331 	bool revisionIsOptional)
332 {
333 	Token word = _NextToken();
334 	_ParseVersionValue(word, value, revisionIsOptional);
335 }
336 
337 
338 /*static*/ void
339 BPackageInfo::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 
430 void
431 BPackageInfo::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 
485 void
486 BPackageInfo::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 
511 void
512 BPackageInfo::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 
561 uint32
562 BPackageInfo::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 
596 void
597 BPackageInfo::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 
659 void
660 BPackageInfo::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 
690 void
691 BPackageInfo::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 
755 void
756 BPackageInfo::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 
816 void
817 BPackageInfo::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 
926 void
927 BPackageInfo::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
1106 BPackageInfo::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
1115 BPackageInfo::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
1124 BPackageInfo::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
1140 BPackageInfo::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 
1165 void
1166 BPackageInfo::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