bumped version
[gnutls.git] / src / libopts / nested.c
blobed23fd20360ea12d30baa46fd8ad9ff709dfcb25
2 /**
3 * \file nested.c
5 * Time-stamp: "2012-03-04 13:30:07 bkorb"
7 * Automated Options Nested Values module.
9 * This file is part of AutoOpts, a companion to AutoGen.
10 * AutoOpts is free software.
11 * AutoOpts is Copyright (c) 1992-2012 by Bruce Korb - all rights reserved
13 * AutoOpts is available under any one of two licenses. The license
14 * in use must be one of these two and the choice is under the control
15 * of the user of the license.
17 * The GNU Lesser General Public License, version 3 or later
18 * See the files "COPYING.lgplv3" and "COPYING.gplv3"
20 * The Modified Berkeley Software Distribution License
21 * See the file "COPYING.mbsd"
23 * These files have the following md5sums:
25 * 43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
26 * 06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
27 * 66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
30 typedef struct {
31 int xml_ch;
32 int xml_len;
33 char xml_txt[8];
34 } xml_xlate_t;
36 static xml_xlate_t const xml_xlate[] = {
37 { '&', 4, "amp;" },
38 { '<', 3, "lt;" },
39 { '>', 3, "gt;" },
40 { '"', 5, "quot;" },
41 { '\'',5, "apos;" }
44 #ifndef ENOMSG
45 #define ENOMSG ENOENT
46 #endif
48 /* = = = START-STATIC-FORWARD = = = */
49 static void
50 remove_continuation(char* pzSrc);
52 static char const*
53 scan_q_str(char const* pzTxt);
55 static tOptionValue *
56 add_string(void ** pp, char const * pzName, size_t nameLen,
57 char const* pzValue, size_t dataLen);
59 static tOptionValue *
60 add_bool(void ** pp, char const * pzName, size_t nameLen,
61 char const* pzValue, size_t dataLen);
63 static tOptionValue*
64 add_number(void** pp, char const* pzName, size_t nameLen,
65 char const* pzValue, size_t dataLen);
67 static tOptionValue*
68 add_nested(void** pp, char const* pzName, size_t nameLen,
69 char* pzValue, size_t dataLen);
71 static char const *
72 scan_name(char const* pzName, tOptionValue* pRes);
74 static char const*
75 scan_xml(char const* pzName, tOptionValue* pRes);
77 static void
78 sort_list(tArgList* pAL);
79 /* = = = END-STATIC-FORWARD = = = */
81 /**
82 * Backslashes are used for line continuations. We keep the newline
83 * characters, but trim out the backslash:
85 static void
86 remove_continuation(char* pzSrc)
88 char* pzD;
90 do {
91 while (*pzSrc == NL) pzSrc++;
92 pzD = strchr(pzSrc, NL);
93 if (pzD == NULL)
94 return;
97 * pzD has skipped at least one non-newline character and now
98 * points to a newline character. It now becomes the source and
99 * pzD goes to the previous character.
101 pzSrc = pzD--;
102 if (*pzD != '\\')
103 pzD++;
104 } while (pzD == pzSrc);
107 * Start shifting text.
109 for (;;) {
110 char ch = ((*pzD++) = *(pzSrc++));
111 switch (ch) {
112 case NUL: return;
113 case '\\':
114 if (*pzSrc == NL)
115 --pzD; /* rewrite on next iteration */
121 * Find the end of a quoted string, skipping escaped quote characters.
123 static char const*
124 scan_q_str(char const* pzTxt)
126 char q = *(pzTxt++); /* remember the type of quote */
128 for (;;) {
129 char ch = *(pzTxt++);
130 if (ch == NUL)
131 return pzTxt-1;
133 if (ch == q)
134 return pzTxt;
136 if (ch == '\\') {
137 ch = *(pzTxt++);
139 * IF the next character is NUL, drop the backslash, too.
141 if (ch == NUL)
142 return pzTxt - 2;
145 * IF the quote character or the escape character were escaped,
146 * then skip both, as long as the string does not end.
148 if ((ch == q) || (ch == '\\')) {
149 if (*(pzTxt++) == NUL)
150 return pzTxt-1;
158 * Associate a name with either a string or no value.
160 static tOptionValue *
161 add_string(void ** pp, char const * pzName, size_t nameLen,
162 char const* pzValue, size_t dataLen)
164 tOptionValue* pNV;
165 size_t sz = nameLen + dataLen + sizeof(*pNV);
167 pNV = AGALOC(sz, "option name/str value pair");
168 if (pNV == NULL)
169 return NULL;
171 if (pzValue == NULL) {
172 pNV->valType = OPARG_TYPE_NONE;
173 pNV->pzName = pNV->v.strVal;
175 } else {
176 pNV->valType = OPARG_TYPE_STRING;
177 if (dataLen > 0) {
178 char const * pzSrc = pzValue;
179 char * pzDst = pNV->v.strVal;
180 int ct = dataLen;
181 do {
182 int ch = *(pzSrc++) & 0xFF;
183 if (ch == NUL) goto data_copy_done;
184 if (ch == '&')
185 ch = get_special_char(&pzSrc, &ct);
186 *(pzDst++) = (char)ch;
187 } while (--ct > 0);
188 data_copy_done:
189 *pzDst = NUL;
191 } else {
192 pNV->v.strVal[0] = NUL;
195 pNV->pzName = pNV->v.strVal + dataLen + 1;
198 memcpy(pNV->pzName, pzName, nameLen);
199 pNV->pzName[ nameLen ] = NUL;
200 addArgListEntry(pp, pNV);
201 return pNV;
205 * Associate a name with either a string or no value.
207 static tOptionValue *
208 add_bool(void ** pp, char const * pzName, size_t nameLen,
209 char const* pzValue, size_t dataLen)
211 tOptionValue * pNV;
214 size_t sz = nameLen + sizeof(tOptionValue) + 1;
215 pNV = AGALOC(sz, "name/bool value");
219 char * p = SPN_WHITESPACE_CHARS(pzValue);
220 dataLen -= p - pzValue;
221 pzValue = p;
224 if (dataLen == 0)
225 pNV->v.boolVal = 0;
227 else if (IS_DEC_DIGIT_CHAR(*pzValue))
228 pNV->v.boolVal = atoi(pzValue);
230 else pNV->v.boolVal = ! IS_FALSE_TYPE_CHAR(*pzValue);
232 pNV->valType = OPARG_TYPE_BOOLEAN;
233 pNV->pzName = (char*)(pNV + 1);
234 memcpy(pNV->pzName, pzName, nameLen);
235 pNV->pzName[ nameLen ] = NUL;
236 addArgListEntry(pp, pNV);
237 return pNV;
241 * Associate a name with either a string or no value.
243 static tOptionValue*
244 add_number(void** pp, char const* pzName, size_t nameLen,
245 char const* pzValue, size_t dataLen)
247 tOptionValue* pNV;
248 size_t sz = nameLen + sizeof(*pNV) + 1;
250 pNV = AGALOC(sz, "option name/bool value pair");
251 if (pNV == NULL)
252 return NULL;
253 while (IS_WHITESPACE_CHAR(*pzValue) && (dataLen > 0)) {
254 dataLen--; pzValue++;
256 if (dataLen == 0)
257 pNV->v.longVal = 0;
258 else
259 pNV->v.longVal = strtol(pzValue, 0, 0);
261 pNV->valType = OPARG_TYPE_NUMERIC;
262 pNV->pzName = (char*)(pNV + 1);
263 memcpy(pNV->pzName, pzName, nameLen);
264 pNV->pzName[ nameLen ] = NUL;
265 addArgListEntry(pp, pNV);
266 return pNV;
270 * Associate a name with either a string or no value.
272 static tOptionValue*
273 add_nested(void** pp, char const* pzName, size_t nameLen,
274 char* pzValue, size_t dataLen)
276 tOptionValue* pNV;
278 if (dataLen == 0) {
279 size_t sz = nameLen + sizeof(*pNV) + 1;
280 pNV = AGALOC(sz, "empty nested value pair");
281 if (pNV == NULL)
282 return NULL;
283 pNV->v.nestVal = NULL;
284 pNV->valType = OPARG_TYPE_HIERARCHY;
285 pNV->pzName = (char*)(pNV + 1);
286 memcpy(pNV->pzName, pzName, nameLen);
287 pNV->pzName[ nameLen ] = NUL;
289 } else {
290 pNV = optionLoadNested(pzValue, pzName, nameLen);
293 if (pNV != NULL)
294 addArgListEntry(pp, pNV);
296 return pNV;
300 * We have an entry that starts with a name. Find the end of it, cook it
301 * (if called for) and create the name/value association.
303 static char const *
304 scan_name(char const* pzName, tOptionValue* pRes)
306 tOptionValue* pNV;
307 char const * pzScan = pzName+1; /* we know first char is a name char */
308 char const * pzVal;
309 size_t nameLen = 1;
310 size_t dataLen = 0;
313 * Scan over characters that name a value. These names may not end
314 * with a colon, but they may contain colons.
316 pzScan = SPN_VALUE_NAME_CHARS(pzName + 1);
317 if (pzScan[-1] == ':')
318 pzScan--;
319 nameLen = pzScan - pzName;
321 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
323 re_switch:
325 switch (*pzScan) {
326 case '=':
327 case ':':
328 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
329 if ((*pzScan == '=') || (*pzScan == ':'))
330 goto default_char;
331 goto re_switch;
333 case NL:
334 case ',':
335 pzScan++;
336 /* FALLTHROUGH */
338 case NUL:
339 add_string(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
340 break;
342 case '"':
343 case '\'':
344 pzVal = pzScan;
345 pzScan = scan_q_str(pzScan);
346 dataLen = pzScan - pzVal;
347 pNV = add_string(&(pRes->v.nestVal), pzName, nameLen, pzVal,
348 dataLen);
349 if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
350 ao_string_cook(pNV->v.strVal, NULL);
351 break;
353 default:
354 default_char:
356 * We have found some strange text value. It ends with a newline
357 * or a comma.
359 pzVal = pzScan;
360 for (;;) {
361 char ch = *(pzScan++);
362 switch (ch) {
363 case NUL:
364 pzScan--;
365 dataLen = pzScan - pzVal;
366 goto string_done;
367 /* FALLTHROUGH */
369 case NL:
370 if ( (pzScan > pzVal + 2)
371 && (pzScan[-2] == '\\')
372 && (pzScan[ 0] != NUL))
373 continue;
374 /* FALLTHROUGH */
376 case ',':
377 dataLen = (pzScan - pzVal) - 1;
378 string_done:
379 pNV = add_string(&(pRes->v.nestVal), pzName, nameLen,
380 pzVal, dataLen);
381 if (pNV != NULL)
382 remove_continuation(pNV->v.strVal);
383 goto leave_scan_name;
386 break;
387 } leave_scan_name:;
389 return pzScan;
393 * We've found a '<' character. We ignore this if it is a comment or a
394 * directive. If it is something else, then whatever it is we are looking
395 * at is bogus. Returning NULL stops processing.
397 static char const*
398 scan_xml(char const* pzName, tOptionValue* pRes)
400 size_t nameLen;
401 size_t valLen;
402 char const* pzScan = ++pzName;
403 char const* pzVal;
404 tOptionValue valu;
405 tOptionValue* pNewVal;
406 tOptionLoadMode save_mode = option_load_mode;
408 if (! IS_VAR_FIRST_CHAR(*pzName)) {
409 switch (*pzName) {
410 default:
411 pzName = NULL;
412 break;
414 case '!':
415 pzName = strstr(pzName, "-->");
416 if (pzName != NULL)
417 pzName += 3;
418 break;
420 case '?':
421 pzName = strchr(pzName, '>');
422 if (pzName != NULL)
423 pzName++;
424 break;
426 return pzName;
429 pzScan = SPN_VALUE_NAME_CHARS(pzName+1);
430 nameLen = pzScan - pzName;
431 if (nameLen > 64)
432 return NULL;
433 valu.valType = OPARG_TYPE_STRING;
435 switch (*pzScan) {
436 case ' ':
437 case '\t':
438 pzScan = parse_attrs(
439 NULL, (char*)pzScan, &option_load_mode, &valu );
440 if (*pzScan == '>') {
441 pzScan++;
442 break;
445 if (*pzScan != '/') {
446 option_load_mode = save_mode;
447 return NULL;
449 /* FALLTHROUGH */
451 case '/':
452 if (*++pzScan != '>') {
453 option_load_mode = save_mode;
454 return NULL;
456 add_string(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
457 option_load_mode = save_mode;
458 return pzScan+1;
460 default:
461 option_load_mode = save_mode;
462 return NULL;
464 case '>':
465 pzScan++;
466 break;
469 pzVal = pzScan;
472 char z[68];
473 char* pzD = z;
474 int ct = nameLen;
475 char const* pzS = pzName;
477 *(pzD++) = '<';
478 *(pzD++) = '/';
480 do {
481 *(pzD++) = *(pzS++);
482 } while (--ct > 0);
483 *(pzD++) = '>';
484 *pzD = NUL;
486 pzScan = strstr(pzScan, z);
487 if (pzScan == NULL) {
488 option_load_mode = save_mode;
489 return NULL;
491 valLen = (pzScan - pzVal);
492 pzScan += nameLen + 3;
493 pzScan = SPN_WHITESPACE_CHARS(pzScan);
496 switch (valu.valType) {
497 case OPARG_TYPE_NONE:
498 add_string(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
499 break;
501 case OPARG_TYPE_STRING:
502 pNewVal = add_string(
503 &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
505 if (option_load_mode == OPTION_LOAD_KEEP)
506 break;
507 mungeString(pNewVal->v.strVal, option_load_mode);
508 break;
510 case OPARG_TYPE_BOOLEAN:
511 add_bool(&(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
512 break;
514 case OPARG_TYPE_NUMERIC:
515 add_number(&(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
516 break;
518 case OPARG_TYPE_HIERARCHY:
520 char* pz = AGALOC(valLen+1, "hierarchical scan");
521 if (pz == NULL)
522 break;
523 memcpy(pz, pzVal, valLen);
524 pz[valLen] = NUL;
525 add_nested(&(pRes->v.nestVal), pzName, nameLen, pz, valLen);
526 AGFREE(pz);
527 break;
530 case OPARG_TYPE_ENUMERATION:
531 case OPARG_TYPE_MEMBERSHIP:
532 default:
533 break;
536 option_load_mode = save_mode;
537 return pzScan;
542 * Deallocate a list of option arguments. This must have been gotten from
543 * a hierarchical option argument, not a stacked list of strings. It is
544 * an internal call, so it is not validated. The caller is responsible for
545 * knowing what they are doing.
547 LOCAL void
548 unload_arg_list(tArgList* pAL)
550 int ct = pAL->useCt;
551 tCC** ppNV = pAL->apzArgs;
553 while (ct-- > 0) {
554 tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++);
555 if (pNV->valType == OPARG_TYPE_HIERARCHY)
556 unload_arg_list(pNV->v.nestVal);
557 AGFREE(pNV);
560 AGFREE((void*)pAL);
563 /*=export_func optionUnloadNested
565 * what: Deallocate the memory for a nested value
566 * arg: + tOptionValue const * + pOptVal + the hierarchical value +
568 * doc:
569 * A nested value needs to be deallocated. The pointer passed in should
570 * have been gotten from a call to @code{configFileLoad()} (See
571 * @pxref{libopts-configFileLoad}).
573 void
574 optionUnloadNested(tOptionValue const * pOV)
576 if (pOV == NULL) return;
577 if (pOV->valType != OPARG_TYPE_HIERARCHY) {
578 errno = EINVAL;
579 return;
582 unload_arg_list(pOV->v.nestVal);
584 AGFREE((void*)pOV);
588 * This is a _stable_ sort. The entries are sorted alphabetically,
589 * but within entries of the same name the ordering is unchanged.
590 * Typically, we also hope the input is sorted.
592 static void
593 sort_list(tArgList* pAL)
595 int ix;
596 int lm = pAL->useCt;
599 * This loop iterates "useCt" - 1 times.
601 for (ix = 0; ++ix < lm;) {
602 int iy = ix-1;
603 tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);
604 tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]);
607 * For as long as the new entry precedes the "old" entry,
608 * move the old pointer. Stop before trying to extract the
609 * "-1" entry.
611 while (strcmp(pOldNV->pzName, pNewNV->pzName) > 0) {
612 pAL->apzArgs[iy+1] = (void*)pOldNV;
613 pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]);
614 if (iy < 0)
615 break;
619 * Always store the pointer. Sometimes it is redundant,
620 * but the redundancy is cheaper than a test and branch sequence.
622 pAL->apzArgs[iy+1] = (void*)pNewNV;
627 * private:
629 * what: parse a hierarchical option argument
630 * arg: + char const* + pzTxt + the text to scan +
631 * arg: + char const* + pzName + the name for the text +
632 * arg: + size_t + nameLen + the length of "name" +
634 * ret_type: tOptionValue*
635 * ret_desc: An allocated, compound value structure
637 * doc:
638 * A block of text represents a series of values. It may be an
639 * entire configuration file, or it may be an argument to an
640 * option that takes a hierarchical value.
642 * If NULL is returned, errno will be set:
643 * @itemize @bullet
644 * @item
645 * @code{EINVAL} the input text was NULL.
646 * @item
647 * @code{ENOMEM} the storage structures could not be allocated
648 * @item
649 * @code{ENOMSG} no configuration values were found
650 * @end itemize
652 LOCAL tOptionValue*
653 optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen)
655 tOptionValue* pRes;
658 * Make sure we have some data and we have space to put what we find.
660 if (pzTxt == NULL) {
661 errno = EINVAL;
662 return NULL;
664 pzTxt = SPN_WHITESPACE_CHARS(pzTxt);
665 if (*pzTxt == NUL) {
666 errno = ENOMSG;
667 return NULL;
669 pRes = AGALOC(sizeof(*pRes) + nameLen + 1, "nested args");
670 if (pRes == NULL) {
671 errno = ENOMEM;
672 return NULL;
674 pRes->valType = OPARG_TYPE_HIERARCHY;
675 pRes->pzName = (char*)(pRes + 1);
676 memcpy(pRes->pzName, pzName, nameLen);
677 pRes->pzName[nameLen] = NUL;
680 tArgList * pAL = AGALOC(sizeof(*pAL), "nested arg list");
681 if (pAL == NULL) {
682 AGFREE(pRes);
683 return NULL;
686 pRes->v.nestVal = pAL;
687 pAL->useCt = 0;
688 pAL->allocCt = MIN_ARG_ALLOC_CT;
692 * Scan until we hit a NUL.
694 do {
695 pzTxt = SPN_WHITESPACE_CHARS(pzTxt);
696 if (IS_VAR_FIRST_CHAR(*pzTxt))
697 pzTxt = scan_name(pzTxt, pRes);
699 else switch (*pzTxt) {
700 case NUL: goto scan_done;
701 case '<': pzTxt = scan_xml(pzTxt, pRes);
702 if (pzTxt == NULL) goto woops;
703 if (*pzTxt == ',') pzTxt++; break;
704 case '#': pzTxt = strchr(pzTxt, NL); break;
705 default: goto woops;
707 } while (pzTxt != NULL); scan_done:;
710 tArgList * al = pRes->v.nestVal;
711 if (al->useCt == 0) {
712 errno = ENOMSG;
713 goto woops;
715 if (al->useCt > 1)
716 sort_list(al);
719 return pRes;
721 woops:
722 AGFREE(pRes->v.nestVal);
723 AGFREE(pRes);
724 return NULL;
727 /*=export_func optionNestedVal
728 * private:
730 * what: parse a hierarchical option argument
731 * arg: + tOptions* + pOpts + program options descriptor +
732 * arg: + tOptDesc* + pOptDesc + the descriptor for this arg +
734 * doc:
735 * Nested value was found on the command line
737 void
738 optionNestedVal(tOptions* pOpts, tOptDesc* pOD)
740 if (pOpts < OPTPROC_EMIT_LIMIT)
741 return;
743 if (pOD->fOptState & OPTST_RESET) {
744 tArgList* pAL = pOD->optCookie;
745 int ct;
746 tCC ** av;
748 if (pAL == NULL)
749 return;
750 ct = pAL->useCt;
751 av = pAL->apzArgs;
753 while (--ct >= 0) {
754 void * p = (void *)*(av++);
755 optionUnloadNested((tOptionValue const *)p);
758 AGFREE(pOD->optCookie);
760 } else {
761 tOptionValue* pOV = optionLoadNested(
762 pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));
764 if (pOV != NULL)
765 addArgListEntry(&(pOD->optCookie), (void*)pOV);
770 * get_special_char
772 LOCAL int
773 get_special_char(char const ** ppz, int * ct)
775 char const * pz = *ppz;
777 if (*ct < 3)
778 return '&';
780 if (*pz == '#') {
781 int base = 10;
782 int retch;
784 pz++;
785 if (*pz == 'x') {
786 base = 16;
787 pz++;
789 retch = (int)strtoul(pz, (char **)&pz, base);
790 if (*pz != ';')
791 return '&';
792 base = ++pz - *ppz;
793 if (base > *ct)
794 return '&';
796 *ct -= base;
797 *ppz = pz;
798 return retch;
802 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
803 xml_xlate_t const * xlatp = xml_xlate;
805 for (;;) {
806 if ( (*ct >= xlatp->xml_len)
807 && (strncmp(pz, xlatp->xml_txt, xlatp->xml_len) == 0)) {
808 *ppz += xlatp->xml_len;
809 *ct -= xlatp->xml_len;
810 return xlatp->xml_ch;
813 if (--ctr <= 0)
814 break;
815 xlatp++;
818 return '&';
822 * emit_special_char
824 LOCAL void
825 emit_special_char(FILE * fp, int ch)
827 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
828 xml_xlate_t const * xlatp = xml_xlate;
830 putc('&', fp);
831 for (;;) {
832 if (ch == xlatp->xml_ch) {
833 fputs(xlatp->xml_txt, fp);
834 return;
836 if (--ctr <= 0)
837 break;
838 xlatp++;
840 fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
844 * Local Variables:
845 * mode: C
846 * c-file-style: "stroustrup"
847 * indent-tabs-mode: nil
848 * End:
849 * end of autoopts/nested.c */