xref: /haiku/src/kits/package/PackageInfoParser.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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::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 
134 status_t
135 BPackageInfo::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 
159 BPackageInfo::Parser::Token
160 BPackageInfo::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 
310 void
311 BPackageInfo::Parser::_RewindTo(const Token& token)
312 {
313 	fPos = token.pos;
314 }
315 
316 
317 void
318 BPackageInfo::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 
330 void
331 BPackageInfo::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 
354 void
355 BPackageInfo::Parser::_ParseVersionValue(BPackageVersion* value,
356 	bool revisionIsOptional)
357 {
358 	Token word = _NextToken();
359 	_ParseVersionValue(word, value, revisionIsOptional);
360 }
361 
362 
363 /*static*/ void
364 BPackageInfo::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 
455 void
456 BPackageInfo::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 
499 void
500 BPackageInfo::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 
554 void
555 BPackageInfo::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 
580 void
581 BPackageInfo::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 
630 uint32
631 BPackageInfo::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 
665 void
666 BPackageInfo::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 
693 void
694 BPackageInfo::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 
724 void
725 BPackageInfo::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 
789 void
790 BPackageInfo::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 
850 void
851 BPackageInfo::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 
960 void
961 BPackageInfo::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
1144 BPackageInfo::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
1153 BPackageInfo::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
1162 BPackageInfo::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
1178 BPackageInfo::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 
1203 void
1204 BPackageInfo::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