1 /*
2 	qfplist.c
3 
4 	Property list management
5 
6 	Copyright (C) 2000  Jeff Teunissen <deek@d2dc.net>
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #include <ctype.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #if defined(_WIN32) && defined(HAVE_MALLOC_H)
36 #include <malloc.h>
37 #endif
38 
39 #include "qfalloca.h"
40 
41 #include "QF/dstring.h"
42 #include "QF/hash.h"
43 #include "QF/qfplist.h"
44 #include "QF/qtypes.h"
45 #include "QF/sys.h"
46 
47 /*
48 	Generic property list item.
49 */
50 struct plitem_s {
51 	pltype_t	type;
52 	void		*data;
53 };
54 
55 /*
56 	Dictionaries
57 */
58 struct dictkey_s {
59 	char		*key;
60 	plitem_t	*value;
61 };
62 typedef struct dictkey_s	dictkey_t;
63 
64 /*
65 	Arrays
66 */
67 struct plarray_s {
68 	int				numvals;		///< Number of items in array
69 	int				maxvals;		///< Number of items that can be stored
70 									///< before a realloc is necesary.
71 	struct plitem_s **values;		///< Array data
72 };
73 typedef struct plarray_s	plarray_t;
74 
75 /*
76 	Typeless, unformatted binary data
77 */
78 struct plbinary_s {
79 	size_t		size;
80 	void		*data;
81 };
82 typedef struct plbinary_s	plbinary_t;
83 
84 typedef struct pldata_s {	// Unparsed property list string
85 	const char		*ptr;
86 	unsigned int	end;
87 	unsigned int	pos;
88 	unsigned int	line;
89 	const char		*error;
90 } pldata_t;
91 
92 //	Ugly defines for fast checking and conversion from char to number
93 #define inrange(ch,min,max) ((ch) >= (min) && (ch) <= (max))
94 #define char2num(ch) \
95 	(inrange((ch), '0', '9') ? ((ch) - '0') \
96 							 : 10 + (inrange((ch), 'a', 'f') ? ((ch) - 'a') \
97 															 : ((ch) - 'A')))
98 
99 static byte quotable_bitmap[32];
100 static inline int
is_quotable(byte x)101 is_quotable (byte x)
102 {
103 	return quotable_bitmap[x / 8] & (1 << (x % 8));
104 }
105 
106 static void
init_quotables(void)107 init_quotables (void)
108 {
109 	const char *unquotables = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
110 							  "abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^";
111 	const byte *c;
112 	memset (quotable_bitmap, ~0, sizeof (quotable_bitmap));
113 	for (c = (byte *) unquotables; *c; c++)
114 		quotable_bitmap[*c / 8] &= ~(1 << (*c % 8));
115 }
116 
117 static plitem_t *PL_ParsePropertyListItem (pldata_t *);
118 static qboolean PL_SkipSpace (pldata_t *);
119 static char *PL_ParseQuotedString (pldata_t *);
120 static char *PL_ParseUnquotedString (pldata_t *);
121 static char *PL_ParseData (pldata_t *, int *);
122 
123 static const char *
dict_get_key(const void * i,void * unused)124 dict_get_key (const void *i, void *unused)
125 {
126 	dictkey_t	*item = (dictkey_t *) i;
127 	return item->key;
128 }
129 
130 static void
dict_free(void * i,void * unused)131 dict_free (void *i, void *unused)
132 {
133 	dictkey_t	*item = (dictkey_t *) i;
134 	free (item->key);
135 	if (item->value)		// Make descended stuff get freed
136 		PL_Free (item->value);
137 	free (item);
138 }
139 
140 static plitem_t *
PL_NewItem(pltype_t type)141 PL_NewItem (pltype_t type)
142 {
143 	plitem_t   *item = calloc (1, sizeof (plitem_t));
144 	item->type = type;
145 	return item;
146 }
147 
148 VISIBLE plitem_t *
PL_NewDictionary(void)149 PL_NewDictionary (void)
150 {
151 	plitem_t   *item = PL_NewItem (QFDictionary);
152 	hashtab_t  *dict = Hash_NewTable (1021, dict_get_key, dict_free, NULL);
153 	item->data = dict;
154 	return item;
155 }
156 
157 VISIBLE plitem_t *
PL_NewArray(void)158 PL_NewArray (void)
159 {
160 	plitem_t   *item = PL_NewItem (QFArray);
161 	plarray_t  *array = calloc (1, sizeof (plarray_t));
162 	item->data = array;
163 	return item;
164 }
165 
166 VISIBLE plitem_t *
PL_NewData(void * data,size_t size)167 PL_NewData (void *data, size_t size)
168 {
169 	plitem_t   *item = PL_NewItem (QFBinary);
170 	plbinary_t *bin = malloc (sizeof (plbinary_t));
171 	item->data = bin;
172 	bin->data = data;
173 	bin->size = size;
174 	return item;
175 }
176 
177 static plitem_t *
new_string(char * str)178 new_string (char *str)
179 {
180 	plitem_t   *item = PL_NewItem (QFString);
181 	item->data = str;
182 	return item;
183 }
184 
185 VISIBLE plitem_t *
PL_NewString(const char * str)186 PL_NewString (const char *str)
187 {
188 	return new_string (strdup (str));
189 }
190 
191 VISIBLE void
PL_Free(plitem_t * item)192 PL_Free (plitem_t *item)
193 {
194 	switch (item->type) {
195 		case QFDictionary:
196 			Hash_DelTable (item->data);
197 			break;
198 
199 		case QFArray: {
200 				int 	i = ((plarray_t *) item->data)->numvals;
201 
202 				while (i-- > 0) {
203 					PL_Free (((plarray_t *) item->data)->values[i]);
204 				}
205 				free (((plarray_t *) item->data)->values);
206 				free (item->data);
207 			}
208 			break;
209 
210 		case QFBinary:
211 			free (((plbinary_t *) item->data)->data);
212 			free (item->data);
213 			break;
214 
215 		case QFString:
216 			free (item->data);
217 			break;
218 	}
219 	free (item);
220 }
221 
222 VISIBLE const char *
PL_String(plitem_t * string)223 PL_String (plitem_t *string)
224 {
225 	if (string->type != QFString)
226 		return NULL;
227 	return string->data;
228 }
229 
230 VISIBLE plitem_t *
PL_ObjectForKey(plitem_t * dict,const char * key)231 PL_ObjectForKey (plitem_t *dict, const char *key)
232 {
233 	hashtab_t  *table = (hashtab_t *) dict->data;
234 	dictkey_t  *k;
235 
236 	if (dict->type != QFDictionary)
237 		return NULL;
238 
239 	k = (dictkey_t *) Hash_Find (table, key);
240 	return k ? k->value : NULL;
241 }
242 
243 VISIBLE plitem_t *
PL_RemoveObjectForKey(plitem_t * dict,const char * key)244 PL_RemoveObjectForKey (plitem_t *dict, const char *key)
245 {
246 	hashtab_t  *table = (hashtab_t *) dict->data;
247 	dictkey_t  *k;
248 	plitem_t   *value;
249 
250 	if (dict->type != QFDictionary)
251 		return NULL;
252 
253 	k = (dictkey_t *) Hash_Del (table, key);
254 	if (!k)
255 		return NULL;
256 	value = k->value;
257 	k->value = 0;
258 	dict_free (k, 0);
259 	return value;
260 }
261 
262 VISIBLE plitem_t *
PL_D_AllKeys(plitem_t * dict)263 PL_D_AllKeys (plitem_t *dict)
264 {
265 	void		**list, **l;
266 	dictkey_t	*current;
267 	plitem_t	*array;
268 
269 	if (dict->type != QFDictionary)
270 		return NULL;
271 
272 	if (!(l = list = Hash_GetList ((hashtab_t *) dict->data)))
273 		return NULL;
274 
275 	if (!(array = PL_NewArray ()))
276 		return NULL;
277 
278 	while ((current = (dictkey_t *) *l++)) {
279 		PL_A_AddObject (array, PL_NewString (current->key));
280 	}
281 	free (list);
282 
283 	return array;
284 }
285 
286 VISIBLE int
PL_D_NumKeys(plitem_t * dict)287 PL_D_NumKeys (plitem_t *dict)
288 {
289 	if (dict->type != QFDictionary)
290 		return 0;
291 	return Hash_NumElements ((hashtab_t *) dict->data);
292 }
293 
294 VISIBLE plitem_t *
PL_ObjectAtIndex(plitem_t * array,int index)295 PL_ObjectAtIndex (plitem_t *array, int index)
296 {
297 	plarray_t  *arr = (plarray_t *) array->data;
298 
299 	if (array->type != QFArray)
300 		return NULL;
301 
302 	return index >= 0 && index < arr->numvals ? arr->values[index] : NULL;
303 }
304 
305 VISIBLE qboolean
PL_D_AddObject(plitem_t * dict,const char * key,plitem_t * value)306 PL_D_AddObject (plitem_t *dict, const char *key, plitem_t *value)
307 {
308 	dictkey_t	*k;
309 
310 	if (dict->type != QFDictionary)
311 		return false;
312 
313 	if ((k = Hash_Find ((hashtab_t *)dict->data, key))) {
314 		PL_Free ((plitem_t *) k->value);
315 		k->value = value;
316 	} else {
317 		k = malloc (sizeof (dictkey_t));
318 
319 		if (!k)
320 			return false;
321 
322 		k->key = strdup (key);
323 		k->value = value;
324 
325 		Hash_Add ((hashtab_t *)dict->data, k);
326 	}
327 	return true;
328 }
329 
330 VISIBLE qboolean
PL_A_InsertObjectAtIndex(plitem_t * array,plitem_t * item,int index)331 PL_A_InsertObjectAtIndex (plitem_t *array, plitem_t *item, int index)
332 {
333 	plarray_t  *arr;
334 
335 	if (array->type != QFArray)
336 		return false;
337 
338 	arr = (plarray_t *)array->data;
339 
340 	if (arr->numvals == arr->maxvals) {
341 		int         size = (arr->maxvals + 128) * sizeof (plitem_t *);
342 		plitem_t  **tmp = realloc (arr->values, size);
343 
344 		if (!tmp)
345 			return false;
346 
347 		arr->maxvals += 128;
348 		arr->values = tmp;
349 		memset (arr->values + arr->numvals, 0,
350 				(arr->maxvals - arr->numvals) * sizeof (plitem_t *));
351 	}
352 
353 	if (index == -1)
354 		index = arr->numvals;
355 
356 	if (index < 0 || index > arr->numvals)
357 		return false;
358 
359 	memmove (arr->values + index + 1, arr->values + index,
360 			 (arr->numvals - index) * sizeof (plitem_t *));
361 	arr->values[index] = item;
362 	arr->numvals++;
363 	return true;
364 }
365 
366 VISIBLE qboolean
PL_A_AddObject(plitem_t * array,plitem_t * item)367 PL_A_AddObject (plitem_t *array, plitem_t *item)
368 {
369 	return PL_A_InsertObjectAtIndex (array, item, -1);
370 }
371 
372 VISIBLE int
PL_A_NumObjects(plitem_t * array)373 PL_A_NumObjects (plitem_t *array)
374 {
375 	if (array->type != QFArray)
376 		return 0;
377 	return ((plarray_t *) array->data)->numvals;
378 }
379 
380 VISIBLE plitem_t *
PL_RemoveObjectAtIndex(plitem_t * array,int index)381 PL_RemoveObjectAtIndex (plitem_t *array, int index)
382 {
383 	plarray_t  *arr;
384 	plitem_t   *item;
385 
386 	if (array->type != QFArray)
387 		return 0;
388 
389 	arr = (plarray_t *)array->data;
390 
391 	if (index < 0 || index >= arr->numvals)
392 		return 0;
393 
394 	item = arr->values[index];
395 	arr->numvals--;
396 	while (index < arr->numvals) {
397 		arr->values[index] = arr->values[index + 1];
398 		index++;
399 	}
400 
401 	return item;
402 }
403 
404 static qboolean
PL_SkipSpace(pldata_t * pl)405 PL_SkipSpace (pldata_t *pl)
406 {
407 	while (pl->pos < pl->end) {
408 		char	c = pl->ptr[pl->pos];
409 
410 		if (!isspace ((byte) c)) {
411 			if (c == '/' && pl->pos < pl->end - 1) {	// check for comments
412 				if (pl->ptr[pl->pos + 1] == '/') {
413 					pl->pos += 2;
414 
415 					while (pl->pos < pl->end) {
416 						c = pl->ptr[pl->pos];
417 
418 						if (c == '\n')
419 							break;
420 						pl->pos++;
421 					}
422 					if (pl->pos >= pl->end) {
423 						pl->error = "Reached end of string in comment";
424 						return false;
425 					}
426 				} else if (pl->ptr[pl->pos + 1] == '*') {	// "/*" comments
427 					pl->pos += 2;
428 
429 					while (pl->pos < pl->end) {
430 						c = pl->ptr[pl->pos];
431 
432 						if (c == '\n') {
433 							pl->line++;
434 						} else if (c == '*' && pl->pos < pl->end - 1
435 									&& pl->ptr[pl->pos+1] == '/') {
436 							pl->pos++;
437 							break;
438 						}
439 						pl->pos++;
440 					}
441 					if (pl->pos >= pl->end) {
442 						pl->error = "Reached end of string in comment";
443 						return false;
444 					}
445 				} else {
446 					return true;
447 				}
448 			} else {
449 				return true;
450 			}
451 		}
452 		if (c == '\n') {
453 			pl->line++;
454 		}
455 		pl->pos++;
456 	}
457 	pl->error = "Reached end of string";
458 	return false;
459 }
460 
461 static inline byte
from_hex(byte a)462 from_hex (byte a)
463 {
464 	if (a >= '0' && a <= '9')
465 		return a - '0';
466 	if (a >= 'A' && a <= 'F')
467 		return a - 'A' + 10;
468 	return a - 'a' + 10;
469 }
470 
471 static inline byte
make_byte(byte h,byte l)472 make_byte (byte h, byte l)
473 {
474 	return (from_hex (h) << 4) | from_hex (l);
475 }
476 
477 static char *
PL_ParseData(pldata_t * pl,int * len)478 PL_ParseData (pldata_t *pl, int *len)
479 {
480 	unsigned    start = ++pl->pos;
481 	int         nibbles = 0, i;
482 	char       *str, c;
483 
484 	while (pl->pos < pl->end) {
485 		c = pl->ptr[pl->pos++];
486 		if (isxdigit ((byte) c)) {
487 			nibbles++;
488 			continue;
489 		}
490 		if (c == '>') {
491 			if (nibbles & 1) {
492 				pl->error = "invalid data, missing nibble";
493 				return NULL;
494 			}
495 			*len = nibbles / 2;
496 			str = malloc (*len);
497 			for (i = 0; i < *len; i++)
498 				str[i] = make_byte (pl->ptr[start + i * 2],
499 									pl->ptr[start + i * 2 + 1]);
500 			return str;
501 		}
502 		pl->error = "invalid character in data";
503 		return NULL;
504 	}
505 	pl->error = "Reached end of string while parsing data";
506 	return NULL;
507 }
508 
509 static char *
PL_ParseQuotedString(pldata_t * pl)510 PL_ParseQuotedString (pldata_t *pl)
511 {
512 	unsigned int	start = ++pl->pos;
513 	unsigned int	escaped = 0;
514 	unsigned int	shrink = 0;
515 	qboolean		hex = false;
516 	qboolean        long_string = false;
517 	char			*str;
518 
519 	if (pl->ptr[pl->pos] == '"' &&
520 		pl->ptr[pl->pos + 1] == '"') {
521 		long_string = true;
522 		start += 2;
523 		pl->pos += 2;
524 	}
525 
526 	while (pl->pos < pl->end) {
527 
528 		char	c = pl->ptr[pl->pos];
529 
530 		if (escaped) {
531 			if (escaped == 1 && inrange (c, '0', '7')) {
532 				escaped++;
533 				hex = false;
534 			} else if (escaped > 1) {
535 				if (escaped == 2 && c == 'x') {
536 					hex = true;
537 					shrink++;
538 					escaped++;
539 				} else if (hex && inrange (escaped, 3, 4)
540 						   && isxdigit ((byte) c)) {
541 					shrink++;
542 					escaped++;
543 				} else if (inrange (escaped, 1, 3) && inrange (c, '0', '7')) {
544 					shrink++;
545 					escaped++;
546 				} else {
547 					pl->pos--;
548 					escaped = 0;
549 				}
550 			} else {
551 				escaped = 0;
552 			}
553 		} else {
554 			if (c == '\\') {
555 				escaped = 1;
556 				shrink++;
557 			} else if (c == '"'
558 					   && (!long_string || (pl->ptr[pl->pos + 1] == '"'
559 											&& pl->ptr[pl->pos + 2] == '"'))) {
560 				break;
561 			}
562 		}
563 
564 		if (c == '\n') {
565 			pl->line++;
566 		}
567 
568 		pl->pos++;
569 	}
570 
571 	if (pl->pos >= pl->end) {
572 		pl->error = "Reached end of string while parsing quoted string";
573 		return NULL;
574 	}
575 
576 	if (pl->pos - start - shrink == 0) {
577 		str = strdup ("");
578 	} else {
579 		char			*chars = alloca(pl->pos - start - shrink);
580 		unsigned int	j;
581 		unsigned int	k;
582 
583 		escaped = 0;
584 		hex = false;
585 
586 		for (j = start, k = 0; j < pl->pos; j++) {
587 
588 			char	c = pl->ptr[j];
589 
590 			if (escaped) {
591 				if (escaped == 1 && inrange (c, '0', '7')) {
592 					chars[k] = c - '0';
593 					hex = false;
594 					escaped++;
595 				} else if (escaped > 1) {
596 					if (escaped == 2 && c == 'x') {
597 						hex = true;
598 						escaped++;
599 					} else if (hex && inrange (escaped, 3, 4)
600 							   && isxdigit ((byte) c)) {
601 						chars[k] <<= 4;
602 						chars[k] |= char2num (c);
603 						escaped++;
604 					} else if (inrange (escaped, 1, 3)
605 							   && inrange (c, '0', '7')) {
606 						chars[k] <<= 3;
607 						chars[k] |= (c - '0');
608 						escaped++;
609 					} else {
610 						escaped = 0;
611 						j--;
612 						k++;
613 					}
614 				} else {
615 					escaped = 0;
616 					switch (c) {
617 						case 'a':
618 							chars[k] = '\a';
619 							break;
620 						case 'b':
621 							chars[k] = '\b';
622 							break;
623 						case 't':
624 							chars[k] = '\t';
625 							break;
626 						case 'r':
627 							chars[k] = '\r';
628 							break;
629 						case 'n':
630 							chars[k] = '\n';
631 							break;
632 						case 'v':
633 							chars[k] = '\v';
634 							break;
635 						case 'f':
636 							chars[k] = '\f';
637 							break;
638 						default:
639 							chars[k] = c;
640 							break;
641 					}
642 					k++;
643 				}
644 			} else {
645 				chars[k] = c;
646 				if (c == '\\') {
647 					escaped = 1;
648 				} else {
649 					k++;
650 				}
651 			}
652 		}
653 		str = strncat (calloc ((pl->pos - start - shrink) + 1, 1), chars,
654 					   pl->pos - start - shrink);
655 	}
656 	if (long_string)
657 		pl->pos += 2;
658 	pl->pos++;
659 	return str;
660 }
661 
662 static char *
PL_ParseUnquotedString(pldata_t * pl)663 PL_ParseUnquotedString (pldata_t *pl)
664 {
665 	unsigned int	start = pl->pos;
666 	char			*str;
667 
668 	while (pl->pos < pl->end) {
669 		if (is_quotable (pl->ptr[pl->pos]))
670 			break;
671 		pl->pos++;
672 	}
673 	str = strncat (calloc ((pl->pos - start) + 1, 1), &pl->ptr[start],
674 				   pl->pos - start);
675 	return str;
676 }
677 
678 static plitem_t *
PL_ParsePropertyListItem(pldata_t * pl)679 PL_ParsePropertyListItem (pldata_t *pl)
680 {
681 	plitem_t	*item = NULL;
682 
683 	if (!PL_SkipSpace (pl))
684 		return NULL;
685 
686 	switch (pl->ptr[pl->pos]) {
687 	case '{':
688 	{
689 		item = PL_NewDictionary ();
690 
691 		pl->pos++;
692 
693 		while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != '}') {
694 			plitem_t	*key;
695 			plitem_t	*value;
696 
697 			if (!(key = PL_ParsePropertyListItem (pl))) {
698 				PL_Free (item);
699 				return NULL;
700 			}
701 
702 			if (!(PL_SkipSpace (pl))) {
703 				PL_Free (key);
704 				PL_Free (item);
705 				return NULL;
706 			}
707 
708 			if (key->type != QFString) {
709 				pl->error = "Key is not a string";
710 				PL_Free (key);
711 				PL_Free (item);
712 				return NULL;
713 			}
714 
715 			if (pl->ptr[pl->pos] != '=') {
716 				pl->error = "Unexpected character (expected '=')";
717 				PL_Free (key);
718 				PL_Free (item);
719 				return NULL;
720 			}
721 			pl->pos++;
722 
723 			// If there is no value, lose the key
724 			if (!(value = PL_ParsePropertyListItem (pl))) {
725 				PL_Free (key);
726 				PL_Free (item);
727 				return NULL;
728 			}
729 
730 			if (!(PL_SkipSpace (pl))) {
731 				PL_Free (key);
732 				PL_Free (value);
733 				PL_Free (item);
734 				return NULL;
735 			}
736 
737 			if (pl->ptr[pl->pos] == ';') {
738 				pl->pos++;
739 			} else if (pl->ptr[pl->pos] != '}') {
740 				pl->error = "Unexpected character (wanted ';' or '}')";
741 				PL_Free (key);
742 				PL_Free (value);
743 				PL_Free (item);
744 				return NULL;
745 			}
746 
747 			// Add the key/value pair to the dictionary
748 			if (!PL_D_AddObject (item, PL_String (key), value)) {
749 				PL_Free (key);
750 				PL_Free (value);
751 				PL_Free (item);
752 				return NULL;
753 			}
754 			PL_Free (key);
755 		}
756 
757 		if (pl->pos >= pl->end) {	// Catch the error
758 			pl->error = "Unexpected end of string when parsing dictionary";
759 			PL_Free (item);
760 			return NULL;
761 		}
762 		pl->pos++;
763 
764 		return item;
765 	}
766 
767 	case '(': {
768 		item = PL_NewArray ();
769 
770 		pl->pos++;
771 
772 		while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != ')') {
773 			plitem_t	*value;
774 
775 			if (!(value = PL_ParsePropertyListItem (pl))) {
776 				PL_Free (item);
777 				return NULL;
778 			}
779 
780 			if (!(PL_SkipSpace (pl))) {
781 				PL_Free (value);
782 				PL_Free (item);
783 				return NULL;
784 			}
785 
786 			if (pl->ptr[pl->pos] == ',') {
787 				pl->pos++;
788 			} else if (pl->ptr[pl->pos] != ')') {
789 				pl->error = "Unexpected character (wanted ',' or ')')";
790 				PL_Free (value);
791 				PL_Free (item);
792 				return NULL;
793 			}
794 
795 			if (!PL_A_AddObject (item, value)) {
796 				pl->error = "Unexpected character (too many items in array)";
797 				PL_Free (value);
798 				PL_Free (item);
799 				return NULL;
800 			}
801 		}
802 		pl->pos++;
803 
804 		return item;
805 	}
806 
807 	case '<': {
808 		int len;
809 		char *str = PL_ParseData (pl, &len);
810 
811 		if (!str) {
812 			return NULL;
813 		} else {
814 			return PL_NewData (str, len);
815 		}
816 	}
817 
818 	case '"': {
819 		char *str = PL_ParseQuotedString (pl);
820 
821 		if (!str) {
822 			return NULL;
823 		} else {
824 			return new_string (str);
825 		}
826 	}
827 
828 	default: {
829 		char *str = PL_ParseUnquotedString (pl);
830 
831 		if (!str) {
832 			return NULL;
833 		} else {
834 			return new_string (str);
835 		}
836 	}
837 	} // switch
838 }
839 
840 VISIBLE plitem_t *
PL_GetPropertyList(const char * string)841 PL_GetPropertyList (const char *string)
842 {
843 	pldata_t	*pl = calloc (1, sizeof (pldata_t));
844 	plitem_t	*newpl = NULL;
845 
846 	if (!quotable_bitmap[0])
847 		init_quotables ();
848 
849 	pl->ptr = string;
850 	pl->pos = 0;
851 	pl->end = strlen (string);
852 	pl->error = NULL;
853 	pl->line = 1;
854 
855 	if ((newpl = PL_ParsePropertyListItem (pl))) {
856 		free (pl);
857 		return newpl;
858 	} else {
859 		if (pl && pl->error && pl->error[0])
860 			Sys_Printf ("plist: %d,%d: %s", pl->line, pl->pos, pl->error);
861 		free (pl);
862 		return NULL;
863 	}
864 }
865 
866 static void
write_tabs(dstring_t * dstr,int num)867 write_tabs (dstring_t *dstr, int num)
868 {
869 	char       *tabs = dstring_reservestr (dstr, num);
870 
871 	memset (tabs, '\t', num);
872 	tabs[num] = 0;
873 }
874 
875 static void
write_string_len(dstring_t * dstr,const char * str,int len)876 write_string_len (dstring_t *dstr, const char *str, int len)
877 {
878 	char       *dst = dstring_reservestr (dstr, len);
879 	memcpy (dst, str, len);
880 	dst[len] = 0;
881 }
882 
883 static char
to_hex(byte b)884 to_hex (byte b)
885 {
886 	char        c = (b & 0xf) + '0';
887 	if (c > '9')
888 		c = c - '0' + 'A';
889 	return c;
890 }
891 
892 static void
write_binary(dstring_t * dstr,byte * binary,int len)893 write_binary (dstring_t *dstr, byte *binary, int len)
894 {
895 	int         i;
896 	char       *dst = dstring_reservestr (dstr, len * 2);
897 	for (i = 0; i < len; i++) {
898 		*dst++ = to_hex (binary[i] >> 4);
899 		*dst++ = to_hex (binary[i]);
900 	}
901 }
902 
903 static void
write_string(dstring_t * dstr,const char * str)904 write_string (dstring_t *dstr, const char *str)
905 {
906 	const char *s;
907 	int         len = 0;
908 	char       *dst;
909 	int         quoted = 0;
910 
911 	for (s = str; *s; s++) {
912 		if (is_quotable (*s))
913 			quoted = 1;
914 		len++;
915 	}
916 	if (!quoted) {
917 		dst = dstring_reservestr (dstr, len);
918 		strcpy (dst, str);
919 		return;
920 	}
921 	// assume worst case of all octal chars plus two quotes.
922 	dst = dstring_reservestr (dstr, len * 4 + 2);
923 	*dst++= '\"';
924 	while (*str) {
925 		if (*str && isascii ((byte) *str) && isprint ((byte) *str)
926 			&& *str != '\\' && *str != '\"') {
927 			*dst++ = *str++;
928 			continue;
929 		}
930 		if (*str) {
931 			*dst++ = '\\';
932 			switch (*str) {
933 				case '\"':
934 				case '\\':
935 					*dst++ = *str;
936 					break;
937 				case '\n':
938 					*dst++ = 'n';
939 					break;
940 				case '\a':
941 					*dst++ = 'a';
942 					break;
943 				case '\b':
944 					*dst++ = 'b';
945 					break;
946 				case '\f':
947 					*dst++ = 'f';
948 					break;
949 				case '\r':
950 					*dst++ = 'r';
951 					break;
952 				case '\t':
953 					*dst++ = 't';
954 					break;
955 				case '\v':
956 					*dst++ = 'v';
957 					break;
958 				default:
959 					*dst++ = '0' + ((((byte) *str) >> 6) & 3);
960 					*dst++ = '0' + ((((byte) *str) >> 3) & 7);
961 					*dst++ = '0' + (((byte) *str) & 7);
962 					break;
963 			}
964 			str++;
965 		}
966 	}
967 	*dst++ = '\"';
968 	*dst++ = 0;
969 	dstr->size = dst - dstr->str;
970 }
971 
972 static void
write_item(dstring_t * dstr,plitem_t * item,int level)973 write_item (dstring_t *dstr, plitem_t *item, int level)
974 {
975 	void		**list, **l;
976 	dictkey_t	*current;
977 	plarray_t	*array;
978 	plbinary_t	*binary;
979 	int			i;
980 
981 	switch (item->type) {
982 		case QFDictionary:
983 			write_string_len (dstr, "{\n", 2);
984 			l = list = Hash_GetList ((hashtab_t *) item->data);
985 			while ((current = (dictkey_t *) *l++)) {
986 				write_tabs (dstr, level + 1);
987 				write_string (dstr, current->key);
988 				write_string_len (dstr, " = ", 3);
989 				write_item (dstr, current->value, level + 1);
990 				write_string_len (dstr, ";\n", 2);
991 			}
992 			free (list);
993 			write_tabs (dstr, level);
994 			write_string_len (dstr, "}", 1);
995 			break;
996 		case QFArray:
997 			write_string_len (dstr, "(\n", 2);
998 			array = (plarray_t *) item->data;
999 			for (i = 0; i < array->numvals; i++) {
1000 				write_tabs (dstr, level + 1);
1001 				write_item (dstr, array->values[i], level + 1);
1002 				if (i < array->numvals - 1)
1003 					write_string_len (dstr, ",\n", 2);
1004 			}
1005 			write_string_len (dstr, "\n", 1);
1006 			write_tabs (dstr, level);
1007 			write_string_len (dstr, ")", 1);
1008 			break;
1009 		case QFBinary:
1010 			write_string_len (dstr, "<", 1);
1011 			binary = (plbinary_t *) item->data;
1012 			write_binary (dstr, binary->data, binary->size);
1013 			write_string_len (dstr, ">", 1);
1014 			break;
1015 		case QFString:
1016 			write_string (dstr, item->data);
1017 			break;
1018 		default:
1019 			break;
1020 	}
1021 }
1022 
1023 VISIBLE char *
PL_WritePropertyList(plitem_t * pl)1024 PL_WritePropertyList (plitem_t *pl)
1025 {
1026 	dstring_t  *dstr = dstring_newstr ();
1027 
1028 	if (!quotable_bitmap[0])
1029 		init_quotables ();
1030 	write_item (dstr, pl, 0);
1031 	write_string_len (dstr, "\n", 1);
1032 	return dstring_freeze (dstr);
1033 }
1034 
1035 VISIBLE pltype_t
PL_Type(plitem_t * item)1036 PL_Type (plitem_t *item)
1037 {
1038 	return item->type;
1039 }
1040