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