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