]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/var.c
644afda95c876041d8fc8c3ef7da83d8d651c750
[bacula/bacula] / bacula / src / lib / var.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 Kern Sibbald
5    Copyright (C) 2003-2014 Free Software Foundation Europe e.V.
6
7    The original author of Bacula is Kern Sibbald, with contributions
8    from many others, a complete list can be found in the file AUTHORS.
9
10    You may use this file and others of this release according to the
11    license defined in the LICENSE file, which includes the Affero General
12    Public License, v3.0 ("AGPLv3") and some additional permissions and
13    terms pursuant to its AGPLv3 Section 7.
14
15    This notice must be preserved when any source code is 
16    conveyed and/or propagated.
17
18    Bacula(R) is a registered trademark of Kern Sibbald.
19 */
20 /*
21 **  OSSP var - Variable Expansion
22 **  Copyright (c) 2001-2002 Ralf S. Engelschall <rse@engelschall.com>
23 **  Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
24 **  Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
25 **
26 **  This file is part of OSSP var, a variable expansion
27 **  library which can be found at http://www.ossp.org/pkg/lib/var/.
28 **
29 **  Permission to use, copy, modify, and distribute this software for
30 **  any purpose with or without fee is hereby granted, provided that
31 **  the above copyright notice and this permission notice appear in all
32 **  copies.
33 **
34 **  For disclaimer see below.
35 */
36 /*
37  *  Adapted by Kern Sibbald to Bacula June 2003
38  */
39
40 #include "bacula.h"
41 #if defined(HAVE_PCREPOSIX)
42 #  include <pcreposix.h>
43 #elif defined(HAVE_WIN32)
44 #  include "bregex.h"
45 #else
46 #  include <regex.h>
47 #endif
48 #include "var.h"
49
50 /* support for OSSP ex based exception throwing */
51 #ifdef WITH_EX
52 #include "ex.h"
53 #define VAR_RC(rv) \
54     (  (rv) != VAR_OK && (ex_catching && !ex_shielding) \
55      ? (ex_throw(var_id, NULL, (rv)), (rv)) : (rv) )
56 #else
57 #define VAR_RC(rv) (rv)
58 #endif /* WITH_EX */
59
60 #ifndef EOS
61 #define EOS '\0'
62 #endif
63
64 /*
65 **
66 **  ==== INTERNAL DATA STRUCTURES ====
67 **
68 */
69
70 typedef char char_class_t[256]; /* 256 == 2 ^ sizeof(unsigned char)*8 */
71
72 /* the external context structure */
73 struct var_st {
74     var_syntax_t         syntax;
75     char_class_t         syntax_nameclass;
76     var_cb_value_t       cb_value_fct;
77     void                *cb_value_ctx;
78     var_cb_operation_t   cb_operation_fct;
79     void                *cb_operation_ctx;
80 };
81
82 /* the internal expansion context structure */
83 struct var_parse_st {
84     struct var_parse_st *lower;
85     int                  force_expand;
86     int                  rel_lookup_flag;
87     int                  rel_lookup_cnt;
88     int                  index_this;
89 };
90 typedef struct var_parse_st var_parse_t;
91
92 /* the default syntax configuration */
93 static const var_syntax_t var_syntax_default = {
94     '\\',         /* escape */
95     '$',          /* delim_init */
96     '{',          /* delim_open */
97     '}',          /* delim_close */
98     '[',          /* index_open */
99     ']',          /* index_close */
100     '#',          /* index_mark */
101     "a-zA-Z0-9_"  /* name_chars */
102 };
103
104 /*
105 **
106 **  ==== FORMATTING FUNCTIONS ====
107 **
108 */
109
110 /* minimal output-independent vprintf(3) variant which supports %{c,s,d,%} only */
111 static int
112 var_mvxprintf(
113     int (*output)(void *ctx, const char *buffer, int bufsize), void *ctx,
114     const char *format, va_list ap)
115 {
116     /* sufficient integer buffer: <available-bits> x log_10(2) + safety */
117     char ibuf[((sizeof(int)*8)/3)+10];
118     const char *cp;
119     char c;
120     int d;
121     int n;
122     int bytes;
123
124     if (format == NULL)
125         return -1;
126     bytes = 0;
127     while (*format != '\0') {
128         if (*format == '%') {
129             c = *(format+1);
130             if (c == '%') {
131                 /* expand "%%" */
132                 cp = &c;
133                 n = sizeof(char);
134             }
135             else if (c == 'c') {
136                 /* expand "%c" */
137                 c = (char)va_arg(ap, int);
138                 cp = &c;
139                 n = sizeof(char);
140             }
141             else if (c == 's') {
142                 /* expand "%s" */
143                 if ((cp = (char *)va_arg(ap, char *)) == NULL)
144                     cp = "(null)";
145                 n = strlen(cp);
146             }
147             else if (c == 'd') {
148                 /* expand "%d" */
149                 d = (int)va_arg(ap, int);
150                 bsnprintf(ibuf, sizeof(ibuf), "%d", d); /* explicitly secure */
151                 cp = ibuf;
152                 n = strlen(cp);
153             }
154             else {
155                 /* any other "%X" */
156                 cp = (char *)format;
157                 n  = 2;
158             }
159             format += 2;
160         }
161         else {
162             /* plain text */
163             cp = (char *)format;
164             if ((format = strchr(cp, '%')) == NULL)
165                 format = strchr(cp, '\0');
166             n = format - cp;
167         }
168         /* perform output operation */
169         if (output != NULL)
170             if ((n = output(ctx, cp, n)) == -1)
171                 break;
172         bytes += n;
173     }
174     return bytes;
175 }
176
177 /* output callback function context for var_mvsnprintf() */
178 typedef struct {
179     char *bufptr;
180     int buflen;
181 } var_mvsnprintf_cb_t;
182
183 /* output callback function for var_mvsnprintf() */
184 static int
185 var_mvsnprintf_cb(
186     void *_ctx,
187     const char *buffer, int bufsize)
188 {
189     var_mvsnprintf_cb_t *ctx = (var_mvsnprintf_cb_t *)_ctx;
190
191     if (bufsize > ctx->buflen)
192         return -1;
193     memcpy(ctx->bufptr, buffer, bufsize);
194     ctx->bufptr += bufsize;
195     ctx->buflen -= bufsize;
196     return bufsize;
197 }
198
199 /* minimal vsnprintf(3) variant which supports %{c,s,d} only */
200 static int
201 var_mvsnprintf(
202     char *buffer, int bufsize,
203     const char *format, va_list ap)
204 {
205     int n;
206     var_mvsnprintf_cb_t ctx;
207
208     if (format == NULL)
209         return -1;
210     if (buffer != NULL && bufsize == 0)
211         return -1;
212     if (buffer == NULL)
213         /* just determine output length */
214         n = var_mvxprintf(NULL, NULL, format, ap);
215     else {
216         /* perform real output */
217         ctx.bufptr = buffer;
218         ctx.buflen = bufsize;
219         n = var_mvxprintf(var_mvsnprintf_cb, &ctx, format, ap);
220         if (n != -1 && ctx.buflen == 0)
221             n = -1;
222         if (n != -1)
223             *(ctx.bufptr) = '\0';
224     }
225     return n;
226 }
227
228 /*
229 **
230 **  ==== PARSE CONTEXT FUNCTIONS ====
231 **
232 */
233
234 static var_parse_t *
235 var_parse_push(
236     var_parse_t *lower, var_parse_t *upper)
237 {
238     if (upper == NULL)
239         return NULL;
240     memcpy(upper, lower, sizeof(var_parse_t));
241     upper->lower = lower;
242     return upper;
243 }
244
245 static var_parse_t *
246 var_parse_pop(
247     var_parse_t *upper)
248 {
249     if (upper == NULL)
250         return NULL;
251     return upper->lower;
252 }
253
254 /*
255 **
256 **  ==== TOKEN BUFFER FUNCTIONS ====
257 **
258 */
259
260 #define TOKENBUF_INITIAL_BUFSIZE 64
261
262 typedef struct {
263     const char *begin;
264     const char *end;
265     int buffer_size;
266 } tokenbuf_t;
267
268 static void
269 tokenbuf_init(
270     tokenbuf_t *buf)
271 {
272     buf->begin = NULL;
273     buf->end = NULL;
274     buf->buffer_size = 0;
275     return;
276 }
277
278 static int
279 tokenbuf_isundef(
280     tokenbuf_t *buf)
281 {
282     if (buf->begin == NULL && buf->end == NULL)
283         return 1;
284     return 0;
285 }
286
287 static int
288 tokenbuf_isempty(
289     tokenbuf_t *buf)
290 {
291     if (buf->begin == buf->end)
292         return 1;
293     return 0;
294 }
295
296 static void
297 tokenbuf_set(
298     tokenbuf_t *buf, const char *begin, const char *end, int buffer_size)
299 {
300     buf->begin = begin;
301     buf->end = end;
302     buf->buffer_size = buffer_size;
303     return;
304 }
305
306 static void
307 tokenbuf_move(
308     tokenbuf_t *src, tokenbuf_t *dst)
309 {
310     dst->begin = src->begin;
311     dst->end = src->end;
312     dst->buffer_size = src->buffer_size;
313     tokenbuf_init(src);
314     return;
315 }
316
317 static int
318 tokenbuf_assign(
319     tokenbuf_t *buf, const char *data, int len)
320 {
321     char *p;
322
323     if ((p = (char *)malloc(len + 1)) == NULL)
324         return 0;
325     memcpy(p, data, len);
326     buf->begin = p;
327     buf->end = p + len;
328     buf->buffer_size = len + 1;
329     *((char *)(buf->end)) = EOS;
330     return 1;
331 }
332
333 static int
334 tokenbuf_append(
335     tokenbuf_t *output, const char *data, int len)
336 {
337     char *new_buffer;
338     int new_size;
339     char *tmp;
340
341     /* Is the tokenbuffer initialized at all? If not, allocate a
342        standard-sized buffer to begin with. */
343     if (output->begin == NULL) {
344         if ((output->begin = output->end = (const char *)malloc(TOKENBUF_INITIAL_BUFSIZE)) == NULL)
345             return 0;
346         output->buffer_size = TOKENBUF_INITIAL_BUFSIZE;
347     }
348
349     /* does the token contain text, but no buffer has been allocated yet? */
350     if (output->buffer_size == 0) {
351         /* check whether data borders to output. If, we can append
352            simly by increasing the end pointer. */
353         if (output->end == data) {
354             output->end += len;
355             return 1;
356         }
357         /* ok, so copy the contents of output into an allocated buffer
358            so that we can append that way. */
359         if ((tmp = (char *)malloc(output->end - output->begin + len + 1)) == NULL)
360             return 0;
361         memcpy(tmp, output->begin, output->end - output->begin);
362         output->buffer_size = output->end - output->begin;
363         output->begin = tmp;
364         output->end = tmp + output->buffer_size;
365         output->buffer_size += len + 1;
366     }
367
368     /* does the token fit into the current buffer? If not, realloc a
369        larger buffer that fits. */
370     if ((output->buffer_size - (output->end - output->begin)) <= len) {
371         new_size = output->buffer_size;
372         do {
373             new_size *= 2;
374         } while ((new_size - (output->end - output->begin)) <= len);
375         if ((new_buffer = (char *)realloc((char *)output->begin, new_size)) == NULL)
376             return 0;
377         output->end = new_buffer + (output->end - output->begin);
378         output->begin = new_buffer;
379         output->buffer_size = new_size;
380     }
381
382     /* append the data at the end of the current buffer. */
383     if (len > 0)
384         memcpy((char *)output->end, data, len);
385     output->end += len;
386     *((char *)output->end) = EOS;
387     return 1;
388 }
389
390 static int
391 tokenbuf_merge(
392     tokenbuf_t *output, tokenbuf_t *input)
393 {
394     return tokenbuf_append(output, input->begin, input->end - input->begin);
395 }
396
397 static void
398 tokenbuf_free(
399     tokenbuf_t *buf)
400 {
401     if (buf->begin != NULL && buf->buffer_size > 0)
402         free((char *)buf->begin);
403     buf->begin = buf->end = NULL;
404     buf->buffer_size = 0;
405     return;
406 }
407
408 /*
409 **
410 **  ==== CHARACTER CLASS EXPANSION ====
411 **
412 */
413
414 static void
415 expand_range(char a, char b, char_class_t chrclass)
416 {
417     do {
418         chrclass[(int)a] = 1;
419     } while (++a <= b);
420     return;
421 }
422
423 static var_rc_t
424 expand_character_class(const char *desc, char_class_t chrclass)
425 {
426     int i;
427
428     /* clear the class array. */
429     for (i = 0; i < 256; ++i)
430         chrclass[i] = 0;
431
432     /* walk through class description and set appropriate entries in array */
433     while (*desc != EOS) {
434         if (desc[1] == '-' && desc[2] != EOS) {
435             if (desc[0] > desc[2])
436                 return VAR_ERR_INCORRECT_CLASS_SPEC;
437             expand_range(desc[0], desc[2], chrclass);
438             desc += 3;
439         } else {
440             chrclass[(int) *desc] = 1;
441             desc++;
442         }
443     }
444     return VAR_OK;
445 }
446
447 /*
448 **
449 **  ==== ESCAPE SEQUENCE EXPANSION FUNCTIONS ====
450 **
451 */
452
453 static int
454 expand_isoct(
455     int c)
456 {
457     if (c >= '0' && c <= '7')
458         return 1;
459     else
460         return 0;
461 }
462
463 static var_rc_t
464 expand_octal(
465     const char **src, char **dst, const char *end)
466 {
467     int c;
468
469     if (end - *src < 3)
470         return VAR_ERR_INCOMPLETE_OCTAL;
471     if (   !expand_isoct(**src)
472         || !expand_isoct((*src)[1])
473         || !expand_isoct((*src)[2]))
474         return VAR_ERR_INVALID_OCTAL;
475
476     c = **src - '0';
477     if (c > 3)
478         return VAR_ERR_OCTAL_TOO_LARGE;
479     c *= 8;
480     (*src)++;
481
482     c += **src - '0';
483     c *= 8;
484     (*src)++;
485
486     c += **src - '0';
487
488     **dst = (char) c;
489     (*dst)++;
490     return VAR_OK;
491 }
492
493 static int
494 expand_ishex(
495     int c)
496 {
497     if ((c >= '0' && c <= '9') ||
498         (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
499         return 1;
500     else
501         return 0;
502 }
503
504 static var_rc_t
505 expand_simple_hex(
506     const char **src, char **dst, const char *end)
507 {
508     int c = 0;
509
510     if (end - *src < 2)
511         return VAR_ERR_INCOMPLETE_HEX;
512     if (   !expand_ishex(**src)
513         || !expand_ishex((*src)[1]))
514         return VAR_ERR_INVALID_HEX;
515
516     if (**src >= '0' && **src <= '9')
517         c = **src - '0';
518     else if (**src >= 'a' && **src <= 'f')
519         c = **src - 'a' + 10;
520     else if (**src >= 'A' && **src <= 'F')
521         c = **src - 'A' + 10;
522
523     c = c << 4;
524     (*src)++;
525
526     if (**src >= '0' && **src <= '9')
527         c += **src - '0';
528     else if (**src >= 'a' && **src <= 'f')
529         c += **src - 'a' + 10;
530     else if (**src >= 'A' && **src <= 'F')
531         c += **src - 'A' + 10;
532
533     **dst = (char)c;
534     (*dst)++;
535     return VAR_OK;
536 }
537
538 static var_rc_t
539 expand_grouped_hex(
540     const char **src, char **dst, const char *end)
541 {
542     var_rc_t rc;
543
544     while (*src < end && **src != '}') {
545         if ((rc = expand_simple_hex(src, dst, end)) != VAR_OK)
546             return rc;
547         (*src)++;
548     }
549     if (*src == end)
550         return VAR_ERR_INCOMPLETE_GROUPED_HEX;
551
552     return VAR_OK;
553 }
554
555 static var_rc_t
556 expand_hex(
557     const char **src, char **dst, const char *end)
558 {
559     if (*src == end)
560         return VAR_ERR_INCOMPLETE_HEX;
561     if (**src == '{') {
562         (*src)++;
563         return expand_grouped_hex(src, dst, end);
564     } else
565         return expand_simple_hex(src, dst, end);
566 }
567
568 /*
569 **
570 **  ==== RECURSIVE-DESCEND VARIABLE EXPANSION PARSER ====
571 **
572 */
573
574 /* forward declarations */
575 static int parse_variable(var_t *var, var_parse_t *ctx, const char *begin, const char *end, tokenbuf_t *result);
576 static int parse_numexp (var_t *var, var_parse_t *ctx, const char *begin, const char *end, int *result, int *failed);
577 static int parse_name   (var_t *var, var_parse_t *ctx, const char *begin, const char *end);
578
579 /* parse pattern text */
580 static int
581 parse_pattern(
582     var_t *var, var_parse_t *ctx,
583     const char *begin, const char *end)
584 {
585     const char *p;
586
587     /* parse until '/' */
588     for (p = begin; p != end && *p != '/'; p++) {
589         if (*p == var->syntax.escape) {
590             if (p + 1 == end)
591                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
592             p++;
593         }
594     }
595     return (p - begin);
596 }
597
598 /* parse substitution text */
599 static int
600 parse_substext(
601     var_t *var, var_parse_t *ctx,
602     const char *begin, const char *end)
603 {
604     const char *p;
605
606     /* parse until delim_init or '/' */
607     for (p = begin; p != end && *p != var->syntax.delim_init && *p != '/'; p++) {
608         if (*p == var->syntax.escape) {
609             if (p + 1 == end)
610                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
611             p++;
612         }
613     }
614     return (p - begin);
615 }
616
617 /* parse expression text */
618 static int
619 parse_exptext(
620     var_t *var, var_parse_t *ctx,
621     const char *begin, const char *end)
622 {
623     const char *p;
624
625     /* parse until delim_init or delim_close or ':' */
626     for (p = begin;     p != end
627                     && *p != var->syntax.delim_init
628                     && *p != var->syntax.delim_close
629                     && *p != ':'; p++) {
630         if (*p == var->syntax.escape) {
631             if (p + 1 == end)
632                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
633             p++;
634         }
635     }
636     return (p - begin);
637 }
638
639 /* parse opertion argument text */
640 static int
641 parse_opargtext(
642     var_t *var, var_parse_t *ctx,
643     const char *begin, const char *end)
644 {
645     const char *p;
646
647     /* parse until delim_init or ')' */
648     for (p = begin; p != end && *p != var->syntax.delim_init && *p != ')'; p++) {
649         if (*p == var->syntax.escape) {
650             if (p + 1 == end)
651                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
652             p++;
653         }
654     }
655     return (p - begin);
656 }
657
658 static int
659 parse_opargtext_or_variable(
660     var_t *var, var_parse_t *ctx,
661     const char *begin, const char *end,
662     tokenbuf_t *result)
663 {
664     const char *p;
665     tokenbuf_t tmp;
666     int rc;
667
668     tokenbuf_init(result);
669     tokenbuf_init(&tmp);
670     p = begin;
671     if (p == end)
672         return 0;
673     do {
674         rc = parse_opargtext(var, ctx, p, end);
675         if (rc < 0)
676             goto error_return;
677         if (rc > 0) {
678             if (!tokenbuf_append(result, p, rc)) {
679                 rc = VAR_ERR_OUT_OF_MEMORY;
680                 goto error_return;
681             }
682             p += rc;
683         }
684         rc = parse_variable(var, ctx, p, end, &tmp);
685         if (rc < 0)
686             goto error_return;
687         if (rc > 0) {
688             p += rc;
689             if (!tokenbuf_merge(result, &tmp)) {
690                 rc = VAR_ERR_OUT_OF_MEMORY;
691                 goto error_return;
692             }
693         }
694         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
695     } while (rc > 0);
696     tokenbuf_free(&tmp);
697     return (p - begin);
698
699 error_return:
700     tokenbuf_free(&tmp);
701     tokenbuf_free(result);
702     return rc;
703 }
704
705 /* parse expression or variable */
706 static int
707 parse_exptext_or_variable(
708     var_t *var, var_parse_t *ctx,
709     const char *begin, const char *end,
710     tokenbuf_t *result)
711 {
712     const char *p = begin;
713     tokenbuf_t tmp;
714     int rc;
715
716     tokenbuf_init(result);
717     tokenbuf_init(&tmp);
718     if (begin == end)
719         return 0;
720     do {
721         /* try to parse expression text */
722         rc = parse_exptext(var, ctx, p, end);
723         if (rc < 0)
724             goto error_return;
725         if (rc > 0) {
726             if (!tokenbuf_append(result, p, rc)) {
727                 rc = VAR_ERR_OUT_OF_MEMORY;
728                 goto error_return;
729             }
730             p += rc;
731         }
732
733         /* try to parse variable construct */
734         rc = parse_variable(var, ctx, p, end, &tmp);
735         if (rc < 0)
736             goto error_return;
737         if (rc > 0) {
738             p += rc;
739             if (!tokenbuf_merge(result, &tmp)) {
740                 rc = VAR_ERR_OUT_OF_MEMORY;
741                 goto error_return;
742             }
743         }
744         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
745     } while (rc > 0);
746
747     tokenbuf_free(&tmp);
748     return (p - begin);
749
750 error_return:
751     tokenbuf_free(&tmp);
752     tokenbuf_free(result);
753     return rc;
754 }
755
756 /* parse substitution text or variable */
757 static int
758 parse_substext_or_variable(
759     var_t *var, var_parse_t *ctx,
760     const char *begin, const char *end,
761     tokenbuf_t *result)
762 {
763     const char *p = begin;
764     tokenbuf_t tmp;
765     int rc;
766
767     tokenbuf_init(result);
768     tokenbuf_init(&tmp);
769     if (begin == end)
770         return 0;
771     do {
772         /* try to parse substitution text */
773         rc = parse_substext(var, ctx, p, end);
774         if (rc < 0)
775             goto error_return;
776         if (rc > 0) {
777             if (!tokenbuf_append(result, p, rc)) {
778                 rc = VAR_ERR_OUT_OF_MEMORY;
779                 goto error_return;
780             }
781             p += rc;
782         }
783
784         /* try to parse substitution text */
785         rc = parse_variable(var, ctx, p, end, &tmp);
786         if (rc < 0)
787             goto error_return;
788         if (rc > 0) {
789             p += rc;
790             if (!tokenbuf_merge(result, &tmp)) {
791                 rc = VAR_ERR_OUT_OF_MEMORY;
792                 goto error_return;
793             }
794         }
795         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
796     } while (rc > 0);
797
798     tokenbuf_free(&tmp);
799     return (p - begin);
800
801 error_return:
802     tokenbuf_free(&tmp);
803     tokenbuf_free(result);
804     return rc;
805 }
806
807 /* parse class description */
808 static int
809 parse_class_description(
810     var_t *var, var_parse_t *ctx,
811     tokenbuf_t *src, tokenbuf_t *dst)
812 {
813     unsigned char c, d;
814     const char *p;
815
816     p = src->begin;
817     while (p != src->end) {
818         if ((src->end - p) >= 3 && p[1] == '-') {
819             if (*p > p[2])
820                 return VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC;
821             for (c = *p, d = p[2]; c <= d; ++c) {
822                 if (!tokenbuf_append(dst, (char *)&c, 1))
823                     return VAR_ERR_OUT_OF_MEMORY;
824             }
825             p += 3;
826         } else {
827             if (!tokenbuf_append(dst, p, 1))
828                 return VAR_ERR_OUT_OF_MEMORY;
829             p++;
830         }
831     }
832     return VAR_OK;
833 }
834
835 /* parse regex replace part */
836 static int
837 parse_regex_replace(
838     var_t *var, var_parse_t *ctx,
839     const char *data,
840     tokenbuf_t *orig,
841     regmatch_t *pmatch,
842     tokenbuf_t *expanded)
843 {
844     const char *p;
845     int i;
846
847     p = orig->begin;
848     tokenbuf_init(expanded);
849
850     while (p != orig->end) {
851         if (*p == '\\') {
852             if (orig->end - p <= 1) {
853                 tokenbuf_free(expanded);
854                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
855             }
856             p++;
857             if (*p == '\\') {
858                 if (!tokenbuf_append(expanded, p, 1)) {
859                     tokenbuf_free(expanded);
860                     return VAR_ERR_OUT_OF_MEMORY;
861                 }
862                 p++;
863                 continue;
864             }
865             if (!isdigit((int)*p)) {
866                 tokenbuf_free(expanded);
867                 return VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
868             }
869             i = (*p - '0');
870             p++;
871             if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1) {
872                 tokenbuf_free(expanded);
873                 return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
874             }
875             if (!tokenbuf_append(expanded, data + pmatch[i].rm_so,
876                                  pmatch[i].rm_eo - pmatch[i].rm_so)) {
877                 tokenbuf_free(expanded);
878                 return VAR_ERR_OUT_OF_MEMORY;
879             }
880         } else {
881             if (!tokenbuf_append(expanded, p, 1)) {
882                 tokenbuf_free(expanded);
883                 return VAR_ERR_OUT_OF_MEMORY;
884             }
885             p++;
886         }
887     }
888
889     return VAR_OK;
890 }
891
892 /* operation: transpose */
893 static int
894 op_transpose(
895     var_t *var, var_parse_t *ctx,
896     tokenbuf_t *data,
897     tokenbuf_t *search,
898     tokenbuf_t *replace)
899 {
900     tokenbuf_t srcclass, dstclass;
901     const char *p;
902     int rc;
903     int i;
904
905     tokenbuf_init(&srcclass);
906     tokenbuf_init(&dstclass);
907     if ((rc = parse_class_description(var, ctx, search, &srcclass)) != VAR_OK)
908         goto error_return;
909     if ((rc = parse_class_description(var, ctx, replace, &dstclass)) != VAR_OK)
910         goto error_return;
911     if (srcclass.begin == srcclass.end) {
912         rc = VAR_ERR_EMPTY_TRANSPOSE_CLASS;
913         goto error_return;
914     }
915     if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
916         rc = VAR_ERR_TRANSPOSE_CLASSES_MISMATCH;
917         goto error_return;
918     }
919     if (data->buffer_size == 0) {
920         tokenbuf_t tmp;
921         if (!tokenbuf_assign(&tmp, data->begin, data->end - data->begin)) {
922             rc = VAR_ERR_OUT_OF_MEMORY;
923             goto error_return;
924         }
925         tokenbuf_move(&tmp, data);
926     }
927     for (p = data->begin; p != data->end; ++p) {
928         for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) {
929             if (*p == srcclass.begin[i]) {
930                 *((char *)p) = dstclass.begin[i];
931                 break;
932             }
933         }
934     }
935     tokenbuf_free(&srcclass);
936     tokenbuf_free(&dstclass);
937     return VAR_OK;
938
939 error_return:
940     tokenbuf_free(search);
941     tokenbuf_free(replace);
942     tokenbuf_free(&srcclass);
943     tokenbuf_free(&dstclass);
944     return rc;
945 }
946
947 /* operation: search & replace */
948 static int
949 op_search_and_replace(
950     var_t *var, var_parse_t *ctx,
951     tokenbuf_t *data,
952     tokenbuf_t *search,
953     tokenbuf_t *replace,
954     tokenbuf_t *flags)
955 {
956     tokenbuf_t tmp;
957     const char *p;
958     int case_insensitive = 0;
959     int multiline = 0;
960     int global = 0;
961     int no_regex = 0;
962     int rc;
963
964     if (search->begin == search->end)
965         return VAR_ERR_EMPTY_SEARCH_STRING;
966
967     for (p = flags->begin; p != flags->end; p++) {
968         switch (tolower(*p)) {
969             case 'm':
970                 multiline = 1;
971                 break;
972             case 'i':
973                 case_insensitive = 1;
974                 break;
975             case 'g':
976                 global = 1;
977                 break;
978             case 't':
979                 no_regex = 1;
980                 break;
981             default:
982                 return VAR_ERR_UNKNOWN_REPLACE_FLAG;
983         }
984     }
985
986     if (no_regex) {
987         /* plain text pattern based operation */
988         tokenbuf_init(&tmp);
989         for (p = data->begin; p != data->end;) {
990             if (case_insensitive)
991                 rc = strncasecmp(p, search->begin, search->end - search->begin);
992             else
993                 rc = strncmp(p, search->begin, search->end - search->begin);
994             if (rc != 0) {
995                 /* not matched, copy character */
996                 if (!tokenbuf_append(&tmp, p, 1)) {
997                     tokenbuf_free(&tmp);
998                     return VAR_ERR_OUT_OF_MEMORY;
999                 }
1000                 p++;
1001             } else {
1002                 /* matched, copy replacement string */
1003                 tokenbuf_merge(&tmp, replace);
1004                 p += (search->end - search->begin);
1005                 if (!global) {
1006                     /* append remaining text */
1007                     if (!tokenbuf_append(&tmp, p, data->end - p)) {
1008                         tokenbuf_free(&tmp);
1009                         return VAR_ERR_OUT_OF_MEMORY;
1010                     }
1011                     break;
1012                 }
1013             }
1014         }
1015         tokenbuf_free(data);
1016         tokenbuf_move(&tmp, data);
1017     } else {
1018         /* regular expression pattern based operation */
1019         tokenbuf_t mydata;
1020         tokenbuf_t myreplace;
1021         regex_t preg;
1022         regmatch_t pmatch[10];
1023         int regexec_flag;
1024
1025         /* copy pattern and data to own buffer to make sure they are EOS-terminated */
1026         if (!tokenbuf_assign(&tmp, search->begin, search->end - search->begin))
1027             return VAR_ERR_OUT_OF_MEMORY;
1028         if (!tokenbuf_assign(&mydata, data->begin, data->end - data->begin)) {
1029             tokenbuf_free(&tmp);
1030             return VAR_ERR_OUT_OF_MEMORY;
1031         }
1032
1033         /* compile the pattern. */
1034         rc = regcomp(&preg, tmp.begin,
1035                      (  REG_EXTENDED
1036                       | (multiline ? REG_NEWLINE : 0)
1037                       | (case_insensitive ? REG_ICASE : 0)));
1038         tokenbuf_free(&tmp);
1039         if (rc != 0) {
1040             tokenbuf_free(&mydata);
1041             return VAR_ERR_INVALID_REGEX_IN_REPLACE;
1042         }
1043
1044         /* match the pattern and create the result string in the tmp buffer */
1045         tokenbuf_append(&tmp, "", 0);
1046         for (p = mydata.begin; p < mydata.end; ) {
1047             if (p == mydata.begin || p[-1] == '\n')
1048                 regexec_flag = 0;
1049             else
1050                 regexec_flag = REG_NOTBOL;
1051             rc = regexec(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch, regexec_flag);
1052             if (rc != 0) {
1053                 /* no (more) matching */
1054                 tokenbuf_append(&tmp, p, mydata.end - p);
1055                 break;
1056             }
1057             else if (   multiline
1058                      && (p + pmatch[0].rm_so) == mydata.end
1059                      && (pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1060                 /* special case: found empty pattern (usually /^/ or /$/ only)
1061                    in multi-line at end of data (after the last newline) */
1062                 tokenbuf_append(&tmp, p, mydata.end - p);
1063                 break;
1064             }
1065             else {
1066                 /* append prolog string */
1067                 if (!tokenbuf_append(&tmp, p, pmatch[0].rm_so)) {
1068                     regfree(&preg);
1069                     tokenbuf_free(&tmp);
1070                     tokenbuf_free(&mydata);
1071                     return VAR_ERR_OUT_OF_MEMORY;
1072                 }
1073                 /* create replace string */
1074                 rc = parse_regex_replace(var, ctx, p, replace, pmatch, &myreplace);
1075                 if (rc != VAR_OK) {
1076                     regfree(&preg);
1077                     tokenbuf_free(&tmp);
1078                     tokenbuf_free(&mydata);
1079                     return rc;
1080                 }
1081                 /* append replace string */
1082                 if (!tokenbuf_merge(&tmp, &myreplace)) {
1083                     regfree(&preg);
1084                     tokenbuf_free(&tmp);
1085                     tokenbuf_free(&mydata);
1086                     tokenbuf_free(&myreplace);
1087                     return VAR_ERR_OUT_OF_MEMORY;
1088                 }
1089                 tokenbuf_free(&myreplace);
1090                 /* skip now processed data */
1091                 p += pmatch[0].rm_eo;
1092                 /* if pattern matched an empty part (think about
1093                    anchor-only regular expressions like /^/ or /$/) we
1094                    skip the next character to make sure we do not enter
1095                    an infinitive loop in matching */
1096                 if ((pmatch[0].rm_eo - pmatch[0].rm_so) == 0) {
1097                     if (p >= mydata.end)
1098                         break;
1099                     if (!tokenbuf_append(&tmp, p, 1)) {
1100                         regfree(&preg);
1101                         tokenbuf_free(&tmp);
1102                         tokenbuf_free(&mydata);
1103                         return VAR_ERR_OUT_OF_MEMORY;
1104                     }
1105                     p++;
1106                 }
1107                 /* append prolog string and stop processing if we
1108                    do not perform the search & replace globally */
1109                 if (!global) {
1110                     if (!tokenbuf_append(&tmp, p, mydata.end - p)) {
1111                         regfree(&preg);
1112                         tokenbuf_free(&tmp);
1113                         tokenbuf_free(&mydata);
1114                         return VAR_ERR_OUT_OF_MEMORY;
1115                     }
1116                     break;
1117                 }
1118             }
1119         }
1120         regfree(&preg);
1121         tokenbuf_free(data);
1122         tokenbuf_move(&tmp, data);
1123         tokenbuf_free(&mydata);
1124     }
1125
1126     return VAR_OK;
1127 }
1128
1129 /* operation: offset substring */
1130 static int
1131 op_offset(
1132     var_t *var, var_parse_t *ctx,
1133     tokenbuf_t *data,
1134     int num1,
1135     int num2,
1136     int isrange)
1137 {
1138     tokenbuf_t res;
1139     const char *p;
1140
1141     /* determine begin of result string */
1142     if ((data->end - data->begin) < num1)
1143         return VAR_ERR_OFFSET_OUT_OF_BOUNDS;
1144     p = data->begin + num1;
1145
1146     /* if num2 is zero, we copy the rest from there. */
1147     if (num2 == 0) {
1148         if (!tokenbuf_assign(&res, p, data->end - p))
1149             return VAR_ERR_OUT_OF_MEMORY;
1150     } else {
1151         /* ok, then use num2. */
1152         if (isrange) {
1153             if ((p + num2) > data->end)
1154                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1155             if (!tokenbuf_assign(&res, p, num2))
1156                 return VAR_ERR_OUT_OF_MEMORY;
1157         } else {
1158             if (num2 < num1)
1159                 return VAR_ERR_OFFSET_LOGIC;
1160             if ((data->begin + num2) > data->end)
1161                 return VAR_ERR_RANGE_OUT_OF_BOUNDS;
1162             if (!tokenbuf_assign(&res, p, num2 - num1 + 1))
1163                 return VAR_ERR_OUT_OF_MEMORY;
1164         }
1165     }
1166     tokenbuf_free(data);
1167     tokenbuf_move(&res, data);
1168     return VAR_OK;
1169 }
1170
1171 /* operation: padding */
1172 static int
1173 op_padding(
1174     var_t *var, var_parse_t *ctx,
1175     tokenbuf_t *data,
1176     int width,
1177     tokenbuf_t *fill,
1178     char position)
1179 {
1180     tokenbuf_t result;
1181     int i;
1182
1183     if (fill->begin == fill->end)
1184         return VAR_ERR_EMPTY_PADDING_FILL_STRING;
1185     tokenbuf_init(&result);
1186     if (position == 'l') {
1187         /* left padding */
1188         i = width - (data->end - data->begin);
1189         if (i > 0) {
1190             i = i / (fill->end - fill->begin);
1191             while (i > 0) {
1192                 if (!tokenbuf_append(data, fill->begin, fill->end - fill->begin))
1193                     return VAR_ERR_OUT_OF_MEMORY;
1194                 i--;
1195             }
1196             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1197             if (!tokenbuf_append(data, fill->begin, i))
1198                 return VAR_ERR_OUT_OF_MEMORY;
1199         }
1200     } else if (position == 'r') {
1201         /* right padding */
1202         i = width - (data->end - data->begin);
1203         if (i > 0) {
1204             i = i / (fill->end - fill->begin);
1205             while (i > 0) {
1206                 if (!tokenbuf_merge(&result, fill)) {
1207                     tokenbuf_free(&result);
1208                     return VAR_ERR_OUT_OF_MEMORY;
1209                 }
1210                 i--;
1211             }
1212             i = (width - (data->end - data->begin)) % (fill->end - fill->begin);
1213             if (!tokenbuf_append(&result, fill->begin, i)) {
1214                 tokenbuf_free(&result);
1215                 return VAR_ERR_OUT_OF_MEMORY;
1216             }
1217             if (!tokenbuf_merge(&result, data)) {
1218                 tokenbuf_free(&result);
1219                 return VAR_ERR_OUT_OF_MEMORY;
1220             }
1221             /* move string from temporary buffer to data buffer */
1222             tokenbuf_free(data);
1223             tokenbuf_move(&result, data);
1224         }
1225     } else if (position == 'c') {
1226         /* centered padding */
1227         i = (width - (data->end - data->begin)) / 2;
1228         if (i > 0) {
1229             /* create the prefix */
1230             i = i / (fill->end - fill->begin);
1231             while (i > 0) {
1232                 if (!tokenbuf_merge(&result, fill)) {
1233                     tokenbuf_free(&result);
1234                     return VAR_ERR_OUT_OF_MEMORY;
1235                 }
1236                 i--;
1237             }
1238             i = ((width - (data->end - data->begin)) / 2)
1239                 % (fill->end - fill->begin);
1240             if (!tokenbuf_append(&result, fill->begin, i)) {
1241                 tokenbuf_free(&result);
1242                 return VAR_ERR_OUT_OF_MEMORY;
1243             }
1244             /* append the actual data string */
1245             if (!tokenbuf_merge(&result, data)) {
1246                 tokenbuf_free(&result);
1247                 return VAR_ERR_OUT_OF_MEMORY;
1248             }
1249             /* append the suffix */
1250             i = width - (result.end - result.begin);
1251             i = i / (fill->end - fill->begin);
1252             while (i > 0) {
1253                 if (!tokenbuf_merge(&result, fill)) {
1254                     tokenbuf_free(&result);
1255                     return VAR_ERR_OUT_OF_MEMORY;
1256                 }
1257                 i--;
1258             }
1259             i = width - (result.end - result.begin);
1260             if (!tokenbuf_append(&result, fill->begin, i)) {
1261                 tokenbuf_free(&result);
1262                 return VAR_ERR_OUT_OF_MEMORY;
1263             }
1264             /* move string from temporary buffer to data buffer */
1265             tokenbuf_free(data);
1266             tokenbuf_move(&result, data);
1267         }
1268     }
1269     return VAR_OK;
1270 }
1271
1272 /* parse an integer number ("123") */
1273 static int
1274 parse_integer(
1275     var_t *var, var_parse_t *ctx,
1276     const char *begin, const char *end,
1277     int *result)
1278 {
1279     const char *p;
1280     int num;
1281
1282     p = begin;
1283     num = 0;
1284     while (isdigit(*p) && p != end) {
1285         num *= 10;
1286         num += (*p - '0');
1287         p++;
1288     }
1289     if (result != NULL)
1290         *result = num;
1291     return (p - begin);
1292 }
1293
1294 /* parse an operation (":x...") */
1295 static int
1296 parse_operation(
1297     var_t *var, var_parse_t *ctx,
1298     const char *begin, const char *end,
1299     tokenbuf_t *data)
1300 {
1301     const char *p;
1302     tokenbuf_t tmptokbuf;
1303     tokenbuf_t search, replace, flags;
1304     tokenbuf_t number1, number2;
1305     int num1, num2;
1306     int isrange;
1307     int rc;
1308     char *ptr;
1309
1310     /* initialization */
1311     tokenbuf_init(&tmptokbuf);
1312     tokenbuf_init(&search);
1313     tokenbuf_init(&replace);
1314     tokenbuf_init(&flags);
1315     tokenbuf_init(&number1);
1316     tokenbuf_init(&number2);
1317     p = begin;
1318     if (p == end)
1319         return 0;
1320
1321     /* dispatch through the first operation character */
1322     switch (tolower(*p)) {
1323         case 'l': {
1324             /* turn value to lowercase. */
1325             if (data->begin != NULL) {
1326                 /* if the buffer does not live in an allocated buffer,
1327                    we have to copy it before modifying the contents. */
1328                 if (data->buffer_size == 0) {
1329                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1330                         rc = VAR_ERR_OUT_OF_MEMORY;
1331                         goto error_return;
1332                     }
1333                 }
1334                 /* convert value */
1335                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1336                     *ptr = (char)tolower((int)(*ptr));
1337             }
1338             p++;
1339             break;
1340         }
1341         case 'u': {
1342             /* turn value to uppercase. */
1343             if (data->begin != NULL) {
1344                 /* if the buffer does not live in an allocated buffer,
1345                    we have to copy it before modifying the contents. */
1346                 if (data->buffer_size == 0) {
1347                     if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
1348                         rc = VAR_ERR_OUT_OF_MEMORY;
1349                         goto error_return;
1350                     }
1351                 }
1352                 /* convert value */
1353                 for (ptr = (char *)data->begin; ptr != data->end; ptr++)
1354                     *ptr = (char)toupper((int)(*ptr));
1355             }
1356             p++;
1357             break;
1358         }
1359         case 'o': {
1360             /* cut out substring of value. */
1361             p++;
1362             rc = parse_integer(var, ctx, p, end, &num1);
1363             if (rc == 0) {
1364                 rc = VAR_ERR_MISSING_START_OFFSET;
1365                 goto error_return;
1366             }
1367             else if (rc < 0)
1368                 goto error_return;
1369             p += rc;
1370             if (*p == ',') {
1371                 isrange = 0;
1372                 p++;
1373             } else if (*p == '-') {
1374                 isrange = 1;
1375                 p++;
1376             } else {
1377                 rc = VAR_ERR_INVALID_OFFSET_DELIMITER;
1378                 goto error_return;
1379             }
1380             rc = parse_integer(var, ctx, p, end, &num2);
1381             p += rc;
1382             if (data->begin != NULL) {
1383                 rc = op_offset(var, ctx, data, num1, num2, isrange);
1384                 if (rc < 0)
1385                     goto error_return;
1386             }
1387             break;
1388         }
1389         case '#': {
1390             /* determine length of the value */
1391             if (data->begin != NULL) {
1392                 char buf[((sizeof(int)*8)/3)+10]; /* sufficient size: <#bits> x log_10(2) + safety */
1393                 sprintf(buf, "%d", (int)(data->end - data->begin));
1394                 tokenbuf_free(data);
1395                 if (!tokenbuf_assign(data, buf, strlen(buf))) {
1396                     rc = VAR_ERR_OUT_OF_MEMORY;
1397                     goto error_return;
1398                 }
1399             }
1400             p++;
1401             break;
1402         }
1403         case '-': {
1404             /* substitute parameter if data is empty */
1405             p++;
1406             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1407             if (rc < 0)
1408                 goto error_return;
1409             if (rc == 0) {
1410                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1411                 goto error_return;
1412             }
1413             p += rc;
1414             if (tokenbuf_isundef(data))
1415                 tokenbuf_move(&tmptokbuf, data);
1416             else if (tokenbuf_isempty(data)) {
1417                 tokenbuf_free(data);
1418                 tokenbuf_move(&tmptokbuf, data);
1419             }
1420             break;
1421         }
1422         case '*': {
1423             /* substitute empty string if data is not empty, parameter otherwise. */
1424             p++;
1425             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1426             if (rc < 0)
1427                 goto error_return;
1428             if (rc == 0) {
1429                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1430                 goto error_return;
1431             }
1432             p += rc;
1433             if (data->begin != NULL) {
1434                 if (data->begin == data->end) {
1435                     tokenbuf_free(data);
1436                     tokenbuf_move(&tmptokbuf, data);
1437                 } else {
1438                     tokenbuf_free(data);
1439                     data->begin = data->end = "";
1440                     data->buffer_size = 0;
1441                 }
1442             }
1443             break;
1444         }
1445         case '+': {
1446             /* substitute parameter if data is not empty. */
1447             p++;
1448             rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
1449             if (rc < 0)
1450                 goto error_return;
1451             if (rc == 0) {
1452                 rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
1453                 goto error_return;
1454             }
1455             p += rc;
1456             if (data->begin != NULL && data->begin != data->end) {
1457                 tokenbuf_free(data);
1458                 tokenbuf_move(&tmptokbuf, data);
1459             }
1460             break;
1461         }
1462         case 's': {
1463             /* search and replace. */
1464             p++;
1465             if (*p != '/')
1466                 return VAR_ERR_MALFORMATTED_REPLACE;
1467             p++;
1468             rc = parse_pattern(var, ctx, p, end);
1469             if (rc < 0)
1470                 goto error_return;
1471             tokenbuf_set(&search, p, p + rc, 0);
1472             p += rc;
1473             if (*p != '/') {
1474                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1475                 goto error_return;
1476             }
1477             p++;
1478             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1479             if (rc < 0)
1480                 goto error_return;
1481             p += rc;
1482             if (*p != '/') {
1483                 rc = VAR_ERR_MALFORMATTED_REPLACE;
1484                 goto error_return;
1485             }
1486             p++;
1487             rc = parse_exptext(var, ctx, p, end);
1488             if (rc < 0)
1489                 goto error_return;
1490             tokenbuf_set(&flags, p, p + rc, 0);
1491             p += rc;
1492             if (data->begin != NULL) {
1493                 rc = op_search_and_replace(var, ctx, data, &search, &replace, &flags);
1494                 if (rc < 0)
1495                     goto error_return;
1496             }
1497             break;
1498         }
1499         case 'y': {
1500             /* transpose characters from class A to class B. */
1501             p++;
1502             if (*p != '/')
1503                 return VAR_ERR_MALFORMATTED_TRANSPOSE;
1504             p++;
1505             rc = parse_substext_or_variable(var, ctx, p, end, &search);
1506             if (rc < 0)
1507                 goto error_return;
1508             p += rc;
1509             if (*p != '/') {
1510                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1511                 goto error_return;
1512             }
1513             p++;
1514             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1515             if (rc < 0)
1516                 goto error_return;
1517             p += rc;
1518             if (*p != '/') {
1519                 rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
1520                 goto error_return;
1521             } else
1522                 p++;
1523             if (data->begin) {
1524                 rc = op_transpose(var, ctx, data, &search, &replace);
1525                 if (rc < 0)
1526                     goto error_return;
1527             }
1528             break;
1529         }
1530         case 'p': {
1531             /* padding. */
1532             p++;
1533             if (*p != '/')
1534                 return VAR_ERR_MALFORMATTED_PADDING;
1535             p++;
1536             rc = parse_integer(var, ctx, p, end, &num1);
1537             if (rc == 0) {
1538                 rc = VAR_ERR_MISSING_PADDING_WIDTH;
1539                 goto error_return;
1540             }
1541             p += rc;
1542             if (*p != '/') {
1543                 rc = VAR_ERR_MALFORMATTED_PADDING;
1544                 goto error_return;
1545             }
1546             p++;
1547             rc = parse_substext_or_variable(var, ctx, p, end, &replace);
1548             if (rc < 0)
1549                 goto error_return;
1550             p += rc;
1551             if (*p != '/') {
1552                 rc = VAR_ERR_MALFORMATTED_PADDING;
1553                 goto error_return;
1554             }
1555             p++;
1556             if (*p != 'l' && *p != 'c' && *p != 'r') {
1557                 rc = VAR_ERR_MALFORMATTED_PADDING;
1558                 goto error_return;
1559             }
1560             p++;
1561             if (data->begin) {
1562                 rc = op_padding(var, ctx, data, num1, &replace, p[-1]);
1563                 if (rc < 0)
1564                     goto error_return;
1565             }
1566             break;
1567         }
1568         case '%': {
1569             /* operation callback function */
1570             const char *op_ptr;
1571             int op_len;
1572             const char *arg_ptr;
1573             int arg_len;
1574             const char *val_ptr;
1575             int val_len;
1576             const char *out_ptr;
1577             int out_len;
1578             int out_size;
1579             tokenbuf_t args;
1580
1581             p++;
1582             rc = parse_name(var, ctx, p, end);
1583             if (rc < 0)
1584                 goto error_return;
1585             op_ptr = p;
1586             op_len = rc;
1587             p += rc;
1588             if (*p == '(') {
1589                 p++;
1590                 tokenbuf_init(&args);
1591                 rc = parse_opargtext_or_variable(var, ctx, p, end, &args);
1592                 if (rc < 0)
1593                     goto error_return;
1594                 p += rc;
1595                 arg_ptr = args.begin;
1596                 arg_len = args.end - args.begin;
1597                 if (*p != ')') {
1598                     rc = VAR_ERR_MALFORMED_OPERATION_ARGUMENTS;
1599                     goto error_return;
1600                 }
1601                 p++;
1602             }
1603             else {
1604                 arg_ptr = NULL;
1605                 arg_len = 0;
1606             }
1607             val_ptr = data->begin;
1608             val_len = data->end - data->begin;
1609
1610             if (data->begin != NULL && var->cb_operation_fct != NULL) {
1611                 /* call operation callback function */
1612                 rc = (*var->cb_operation_fct)(var, var->cb_operation_ctx,
1613                                               op_ptr, op_len,
1614                                               arg_ptr, arg_len,
1615                                               val_ptr, val_len,
1616                                               &out_ptr, &out_len, &out_size);
1617                 if (rc < 0) {
1618                     if (arg_ptr != NULL)
1619                         free((void *)arg_ptr);
1620                     goto error_return;
1621                 }
1622                 tokenbuf_free(data);
1623                 tokenbuf_set(data, out_ptr, out_ptr+out_len, out_size);
1624             }
1625             if (arg_ptr != NULL)
1626                free((void *)arg_ptr);
1627             break;
1628         }
1629         default:
1630             return VAR_ERR_UNKNOWN_COMMAND_CHAR;
1631     }
1632
1633     /* return successfully */
1634     tokenbuf_free(&tmptokbuf);
1635     tokenbuf_free(&search);
1636     tokenbuf_free(&replace);
1637     tokenbuf_free(&flags);
1638     tokenbuf_free(&number1);
1639     tokenbuf_free(&number2);
1640     return (p - begin);
1641
1642     /* return with an error */
1643 error_return:
1644     tokenbuf_free(data);
1645     tokenbuf_free(&tmptokbuf);
1646     tokenbuf_free(&search);
1647     tokenbuf_free(&replace);
1648     tokenbuf_free(&flags);
1649     tokenbuf_free(&number1);
1650     tokenbuf_free(&number2);
1651     return rc;
1652 }
1653
1654 /* parse numerical expression operand */
1655 static int
1656 parse_numexp_operand(
1657     var_t *var, var_parse_t *ctx,
1658     const char *begin, const char *end,
1659     int *result, int *failed)
1660 {
1661     const char *p;
1662     tokenbuf_t tmp;
1663     int rc;
1664     var_parse_t myctx;
1665
1666     /* initialization */
1667     p = begin;
1668     tokenbuf_init(&tmp);
1669     if (p == end)
1670         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1671
1672     /* parse opening numerical expression */
1673     if (*p == '(') {
1674         /* parse inner numerical expression */
1675         rc = parse_numexp(var, ctx, ++p, end, result, failed);
1676         if (rc < 0)
1677             return rc;
1678         p += rc;
1679         if (p == end)
1680             return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1681         /* parse closing parenthesis */
1682         if (*p != ')')
1683             return VAR_ERR_UNCLOSED_BRACKET_IN_INDEX;
1684         p++;
1685     }
1686     /* parse contained variable */
1687     else if (*p == var->syntax.delim_init) {
1688         /* parse variable with forced expansion */
1689         ctx = var_parse_push(ctx, &myctx);
1690         ctx->force_expand = 1;
1691         rc = parse_variable(var, ctx, p, end, &tmp);
1692         ctx = var_parse_pop(ctx);
1693
1694         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1695             *failed = 1;
1696             /* parse variable without forced expansion */
1697             ctx = var_parse_push(ctx, &myctx);
1698             ctx->force_expand = 0;
1699             rc = parse_variable(var, ctx, p, end, &tmp);
1700             ctx = var_parse_pop(ctx);
1701             if (rc < 0)
1702                 return rc;
1703             p += rc;
1704             *result = 0;
1705             tokenbuf_free(&tmp);      /* KES 11/9/2003 */
1706         } else if (rc < 0) {
1707             return rc;
1708         } else {
1709             p += rc;
1710             /* parse remaining numerical expression */
1711             rc = parse_numexp(var, ctx, tmp.begin, tmp.end, result, failed);
1712             tokenbuf_free(&tmp);
1713             if (rc < 0)
1714                 return rc;
1715         }
1716     }
1717     /* parse relative index mark ("#") */
1718     else if (   var->syntax.index_mark != EOS
1719              && *p == var->syntax.index_mark) {
1720         p++;
1721         *result = ctx->index_this;
1722         if (ctx->rel_lookup_flag)
1723             ctx->rel_lookup_cnt++;
1724     }
1725     /* parse plain integer number */
1726     else if (isdigit(*p)) {
1727         rc = parse_integer(var, ctx, p, end, result);
1728         p += rc;
1729     }
1730     /* parse signed positive integer number */
1731     else if (*p == '+') {
1732         if ((end - p) > 1 && isdigit(p[1])) {
1733             p++;
1734             rc = parse_integer(var, ctx, p, end, result);
1735             p += rc;
1736         }
1737         else
1738             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1739     }
1740     /* parse signed negative integer number */
1741     else if (*p == '-') {
1742         if (end - p > 1 && isdigit(p[1])) {
1743             p++;
1744             rc = parse_integer(var, ctx, p, end, result);
1745             *result = -(*result);
1746             p += rc;
1747         }
1748         else
1749             return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1750     }
1751     /* else we failed to parse anything reasonable */
1752     else
1753         return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1754
1755     return (p - begin);
1756 }
1757
1758 /* parse numerical expression ("x+y") */
1759 static int
1760 parse_numexp(
1761     var_t *var, var_parse_t *ctx,
1762     const char *begin, const char *end,
1763     int *result, int *failed)
1764 {
1765     const char *p;
1766     char op;
1767     int right;
1768     int rc;
1769
1770     /* initialization */
1771     p = begin;
1772     if (p == end)
1773         return VAR_ERR_INCOMPLETE_INDEX_SPEC;
1774
1775     /* parse left numerical operand */
1776     rc = parse_numexp_operand(var, ctx, p, end, result, failed);
1777     if (rc < 0)
1778         return rc;
1779     p += rc;
1780
1781     /* parse numerical operator */
1782     while (p != end) {
1783         if (*p == '+' || *p == '-') {
1784             op = *p++;
1785             /* recursively parse right operand (light binding) */
1786             rc = parse_numexp(var, ctx, p, end, &right, failed);
1787             if (rc < 0)
1788                 return rc;
1789             p += rc;
1790             if (op == '+')
1791                 *result = (*result + right);
1792             else
1793                 *result = (*result - right);
1794         }
1795         else if (*p == '*' || *p == '/' || *p == '%') {
1796             op = *p++;
1797             /* recursively parse right operand (string binding) */
1798             rc = parse_numexp_operand(var, ctx, p, end, &right, failed);
1799             if (rc < 0)
1800                 return rc;
1801             p += rc;
1802             if (op == '*')
1803                 *result = (*result * right);
1804             else if (op == '/') {
1805                 if (right == 0) {
1806                     if (*failed)
1807                         *result = 0;
1808                     else
1809                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1810                 }
1811                 else
1812                     *result = (*result / right);
1813             }
1814             else if (op == '%') {
1815                 if (right == 0) {
1816                     if (*failed)
1817                         *result = 0;
1818                     else
1819                         return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
1820                 }
1821                 else
1822                     *result = (*result % right);
1823             }
1824         }
1825         else
1826             break;
1827     }
1828
1829     /* return amount of parsed input */
1830     return (p - begin);
1831 }
1832
1833 /* parse variable name ("abc") */
1834 static int
1835 parse_name(
1836     var_t *var, var_parse_t *ctx,
1837     const char *begin, const char *end)
1838 {
1839     const char *p;
1840
1841     /* parse as long as name class characters are found */
1842     for (p = begin; p != end && var->syntax_nameclass[(int)(*p)]; p++)
1843         ;
1844     return (p - begin);
1845 }
1846
1847 /* lookup a variable value through the callback function */
1848 static int
1849 lookup_value(
1850     var_t *var, var_parse_t *ctx,
1851     const char  *var_ptr, int  var_len, int var_inc, int var_idx,
1852     const char **val_ptr, int *val_len, int *val_size)
1853 {
1854     char buf[1];
1855     int rc;
1856
1857     /* pass through to original callback */
1858     rc = (*var->cb_value_fct)(var, var->cb_value_ctx,
1859                               var_ptr, var_len, var_inc, var_idx,
1860                               val_ptr, val_len, val_size);
1861
1862     /* convert undefined variable into empty variable if relative
1863        lookups are counted. This is the case inside an active loop
1864        construct if no limits are given. There the parse_input()
1865        has to proceed until all variables have undefined values.
1866        This trick here allows it to determine this case. */
1867     if (ctx->rel_lookup_flag && rc == VAR_ERR_UNDEFINED_VARIABLE) {
1868         ctx->rel_lookup_cnt--;
1869         buf[0] = EOS;
1870         /* ****FIXME**** passing back stack variable!!! */
1871         *val_ptr  = buf;
1872         *val_len  = 0;
1873         *val_size = 0;
1874         return VAR_OK;
1875     }
1876
1877     return rc;
1878 }
1879
1880 /* parse complex variable construct ("${name...}") */
1881 static int
1882 parse_variable_complex(
1883     var_t *var, var_parse_t *ctx,
1884     const char *begin, const char *end,
1885     tokenbuf_t *result)
1886 {
1887     const char *p;
1888     const char *data;
1889     int len, buffer_size;
1890     int failed = 0;
1891     int rc;
1892     int idx = 0;
1893     int inc;
1894     tokenbuf_t name;
1895     tokenbuf_t tmp;
1896
1897     /* initializations */
1898     p = begin;
1899     tokenbuf_init(&name);
1900     tokenbuf_init(&tmp);
1901     tokenbuf_init(result);
1902
1903     /* parse open delimiter */
1904     if (p == end || *p != var->syntax.delim_open)
1905         return 0;
1906     p++;
1907     if (p == end)
1908         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1909
1910     /* parse name of variable to expand. The name may consist of an
1911        arbitrary number of variable name character and contained variable
1912        constructs. */
1913     do {
1914         /* parse a variable name */
1915         rc = parse_name(var, ctx, p, end);
1916         if (rc < 0)
1917             goto error_return;
1918         if (rc > 0) {
1919             if (!tokenbuf_append(&name, p, rc)) {
1920                 rc = VAR_ERR_OUT_OF_MEMORY;
1921                 goto error_return;
1922             }
1923             p += rc;
1924         }
1925
1926         /* parse an (embedded) variable */
1927         rc = parse_variable(var, ctx, p, end, &tmp);
1928         if (rc < 0)
1929             goto error_return;
1930         if (rc > 0) {
1931             if (!tokenbuf_merge(&name, &tmp)) {
1932                 rc = VAR_ERR_OUT_OF_MEMORY;
1933                 goto error_return;
1934             }
1935             p += rc;
1936         }
1937         tokenbuf_free(&tmp);          /* KES 11/9/2003 */
1938     } while (rc > 0);
1939
1940     /* we must have the complete expanded variable name now,
1941        so make sure we really do. */
1942     if (name.begin == name.end) {
1943         if (ctx->force_expand) {
1944             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1945             goto error_return;
1946         }
1947         else {
1948             /* If no force_expand is requested, we have to back-off.
1949                We're not sure whether our approach here is 100% correct,
1950                because it _could_ have side-effects according to Peter
1951                Simons, but as far as we know and tried it, it is
1952                correct. But be warned -- RSE */
1953             tokenbuf_set(result, begin - 1, p, 0);
1954             goto goahead;
1955         }
1956     }
1957
1958     /* parse an optional index specification */
1959     if (   var->syntax.index_open != EOS
1960         && *p == var->syntax.index_open) {
1961         p++;
1962         rc = parse_numexp(var, ctx, p, end, &idx, &failed);
1963         if (rc < 0)
1964             goto error_return;
1965         if (rc == 0) {
1966             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1967             goto error_return;
1968         }
1969         p += rc;
1970         if (p == end) {
1971             rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
1972             goto error_return;
1973         }
1974         if (*p != var->syntax.index_close) {
1975             rc = VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
1976             goto error_return;
1977         }
1978         p++;
1979     }
1980
1981     /* parse end of variable construct or start of post-operations */
1982     if (p == end || (*p != var->syntax.delim_close && *p != ':' && *p != '+')) {
1983         rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
1984         goto error_return;
1985     }
1986     if ((inc = (*p++ == '+'))) {
1987        p++;                           /* skip the + */
1988     }
1989
1990     /* lookup the variable value now */
1991     if (failed) {
1992         tokenbuf_set(result, begin - 1, p, 0);
1993     } else {
1994         rc = lookup_value(var, ctx,
1995                           name.begin, name.end-name.begin, inc, idx,
1996                           &data, &len, &buffer_size);
1997         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
1998             tokenbuf_init(result); /* delayed handling of undefined variable */
1999         } else if (rc < 0) {
2000             goto error_return;
2001         } else {
2002             /* the preliminary result is the raw value of the variable.
2003                This may be modified by the operations that may follow. */
2004             tokenbuf_set(result, data, data + len, buffer_size);
2005         }
2006     }
2007
2008     /* parse optional post-operations */
2009 goahead:
2010     if (p[-1] == ':') {
2011         tokenbuf_free(&tmp);
2012         tokenbuf_init(&tmp);
2013         p--;
2014         while (p != end && *p == ':') {
2015             p++;
2016             if (!failed)
2017                 rc = parse_operation(var, ctx, p, end, result);
2018             else
2019                 rc = parse_operation(var, ctx, p, end, &tmp);
2020             if (rc < 0)
2021                 goto error_return;
2022             p += rc;
2023             if (failed)
2024                 result->end += rc;
2025         }
2026         if (p == end || *p != var->syntax.delim_close) {
2027             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2028             goto error_return;
2029         }
2030         p++;
2031         if (failed)
2032             result->end++;
2033     } else if (p[-1] == '+') {
2034        p++;
2035     }
2036
2037     /* lazy handling of undefined variable */
2038     if (!failed && tokenbuf_isundef(result)) {
2039         if (ctx->force_expand) {
2040             rc = VAR_ERR_UNDEFINED_VARIABLE;
2041             goto error_return;
2042         } else {
2043             tokenbuf_set(result, begin - 1, p, 0);
2044         }
2045     }
2046
2047     /* return successfully */
2048     tokenbuf_free(&name);
2049     tokenbuf_free(&tmp);
2050     return (p - begin);
2051
2052     /* return with an error */
2053 error_return:
2054     tokenbuf_free(&name);
2055     tokenbuf_free(&tmp);
2056     tokenbuf_free(result);
2057     return rc;
2058 }
2059
2060 /* parse variable construct ("$name" or "${name...}") */
2061 static int
2062 parse_variable(
2063     var_t *var, var_parse_t *ctx,
2064     const char *begin, const char *end,
2065     tokenbuf_t *result)
2066 {
2067     const char *p;
2068     const char *data;
2069     int len, buffer_size;
2070     int rc, rc2;
2071     int inc;
2072
2073     /* initialization */
2074     p = begin;
2075     tokenbuf_init(result);
2076
2077     /* parse init delimiter */
2078     if (p == end || *p != var->syntax.delim_init)
2079         return 0;
2080     p++;
2081     if (p == end)
2082         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2083
2084     /* parse a simple variable name.
2085        (if this fails, we're try to parse a complex variable construct) */
2086     rc = parse_name(var, ctx, p, end);
2087     if (rc < 0)
2088         return rc;
2089     if (rc > 0) {
2090         inc = (p[rc] == '+');
2091         rc2 = lookup_value(var, ctx, p, rc, inc, 0, &data, &len, &buffer_size);
2092         if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !ctx->force_expand) {
2093             tokenbuf_set(result, begin, begin + 1 + rc, 0);
2094             return (1 + rc);
2095         }
2096         if (rc2 < 0)
2097             return rc2;
2098         tokenbuf_set(result, data, data + len, buffer_size);
2099         return (1 + rc);
2100     }
2101
2102     /* parse a complex variable construct (else case) */
2103     rc = parse_variable_complex(var, ctx, p, end, result);
2104     if (rc > 0)
2105         rc++;
2106     return rc;
2107 }
2108
2109 /* parse loop construct limits ("[...]{b,s,e}") */
2110 static var_rc_t
2111 parse_looplimits(
2112     var_t *var, var_parse_t *ctx,
2113     const char *begin, const char *end,
2114     int *start, int *step, int *stop, int *open_stop)
2115 {
2116     const char *p;
2117     int rc;
2118     int failed;
2119
2120     /* initialization */
2121     p = begin;
2122
2123     /* we are happy if nothing is to left to parse */
2124     if (p == end)
2125         return VAR_OK;
2126
2127     /* parse start delimiter */
2128     if (*p != var->syntax.delim_open)
2129         return VAR_OK;
2130     p++;
2131
2132     /* parse loop start value */
2133     failed = 0;
2134     rc = parse_numexp(var, ctx, p, end, start, &failed);
2135     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2136         *start = 0; /* use default */
2137     else if (rc < 0)
2138         return (var_rc_t)rc;
2139     else
2140         p += rc;
2141     if (failed)
2142         return VAR_ERR_UNDEFINED_VARIABLE;
2143
2144     /* parse separator */
2145     if (*p != ',')
2146         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2147     p++;
2148
2149     /* parse loop step value */
2150     failed = 0;
2151     rc = parse_numexp(var, ctx, p, end, step, &failed);
2152     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2153         *step = 1; /* use default */
2154     else if (rc < 0)
2155         return (var_rc_t)rc;
2156     else
2157         p += rc;
2158     if (failed)
2159         return VAR_ERR_UNDEFINED_VARIABLE;
2160
2161     /* parse separator */
2162     if (*p != ',') {
2163         /* if not found, parse end delimiter */
2164         if (*p != var->syntax.delim_close)
2165             return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2166         p++;
2167
2168         /* shift step value to stop value */
2169         *stop = *step;
2170         *step = 1;
2171
2172         /* determine whether loop end is open */
2173         if (rc > 0)
2174             *open_stop = 0;
2175         else
2176             *open_stop = 1;
2177         return (var_rc_t)(p - begin);
2178     }
2179     p++;
2180
2181     /* parse loop stop value */
2182     failed = 0;
2183     rc = parse_numexp(var, ctx, p, end, stop, &failed);
2184     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
2185         *stop = 0; /* use default */
2186         *open_stop = 1;
2187     }
2188     else if (rc < 0)
2189         return (var_rc_t)rc;
2190     else {
2191         *open_stop = 0;
2192         p += rc;
2193     }
2194     if (failed)
2195         return VAR_ERR_UNDEFINED_VARIABLE;
2196
2197     /* parse end delimiter */
2198     if (*p != var->syntax.delim_close)
2199         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2200     p++;
2201
2202     /* return amount of parsed input */
2203     return (var_rc_t)(p - begin);
2204 }
2205
2206 /* parse plain text */
2207 static int
2208 parse_text(
2209     var_t *var, var_parse_t *ctx,
2210     const char *begin, const char *end)
2211 {
2212     const char *p;
2213
2214     /* parse until delim_init (variable construct)
2215        or index_open (loop construct) is found */
2216     for (p = begin; p != end; p++) {
2217         if (*p == var->syntax.escape) {
2218             p++; /* skip next character */
2219             if (p == end)
2220                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
2221         }
2222         else if (*p == var->syntax.delim_init)
2223             break;
2224         else if (   var->syntax.index_open != EOS
2225                  && (   *p == var->syntax.index_open
2226                      || *p == var->syntax.index_close))
2227             break;
2228     }
2229     return (p - begin);
2230 }
2231
2232 /* expand input in general */
2233 static var_rc_t
2234 parse_input(
2235     var_t *var, var_parse_t *ctx,
2236     const char *begin, const char *end,
2237     tokenbuf_t *output, int recursion_level)
2238 {
2239     const char *p;
2240     int rc, rc2;
2241     tokenbuf_t result;
2242     int start, step, stop, open_stop;
2243     int i;
2244     int output_backup;
2245     int rel_lookup_cnt;
2246     int loop_limit_length;
2247     var_parse_t myctx;
2248
2249     /* initialization */
2250     p = begin;
2251
2252     do {
2253         /* try to parse a loop construct */
2254         if (   p != end
2255             && var->syntax.index_open != EOS
2256             && *p == var->syntax.index_open) {
2257             p++;
2258
2259             /* loop preparation */
2260             loop_limit_length = -1;
2261             rel_lookup_cnt = ctx->rel_lookup_cnt;
2262             open_stop = 1;
2263             rc = 0;
2264             start = 0;
2265             step  = 1;
2266             stop  = 0;
2267             output_backup = 0;
2268
2269             /* iterate over loop construct, either as long as there is
2270                (still) nothing known about the limit, or there is an open
2271                (=unknown) limit stop and there are still defined variables
2272                or there is a stop limit known and it is still not reached */
2273             re_loop:
2274             for (i = start;
2275                  (   (   open_stop
2276                       && (   loop_limit_length < 0
2277                           || rel_lookup_cnt > ctx->rel_lookup_cnt))
2278                   || (   !open_stop
2279                       && i <= stop)                                );
2280                  i += step) {
2281
2282                 /* remember current output end for restoring */
2283                 output_backup = (output->end - output->begin);
2284
2285                 /* open temporary context for recursion */
2286                 ctx = var_parse_push(ctx, &myctx);
2287                 ctx->force_expand    = 1;
2288                 ctx->rel_lookup_flag = 1;
2289                 ctx->index_this      = i;
2290
2291                 /* recursive parse input through ourself */
2292                 rc = parse_input(var, ctx, p, end,
2293                                  output, recursion_level+1);
2294
2295                 /* retrieve info and close temporary context */
2296                 rel_lookup_cnt = ctx->rel_lookup_cnt;
2297                 ctx = var_parse_pop(ctx);
2298
2299                 /* error handling */
2300                 if (rc < 0)
2301                     goto error_return;
2302
2303                 /* make sure the loop construct is closed */
2304                 if (p[rc] != var->syntax.index_close) {
2305                     rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
2306                     goto error_return;
2307                 }
2308
2309                 /* try to parse loop construct limit specification */
2310                 if (loop_limit_length < 0) {
2311                     rc2 = parse_looplimits(var, ctx, p+rc+1, end,
2312                                            &start, &step, &stop, &open_stop);
2313                     if (rc2 < 0)
2314                         goto error_return;
2315                     else if (rc2 == 0)
2316                         loop_limit_length = 0;
2317                     else if (rc2 > 0) {
2318                         loop_limit_length = rc2;
2319                         /* restart loop from scratch */
2320                         output->end = (output->begin + output_backup);
2321                         goto re_loop;
2322                     }
2323                 }
2324             }
2325
2326             /* if stop value is open, restore to the output end
2327                because the last iteration was just to determine the loop
2328                termination and its result has to be discarded */
2329             if (open_stop)
2330                 output->end = (output->begin + output_backup);
2331
2332             /* skip parsed loop construct */
2333             p += rc;
2334             p++;
2335             p += loop_limit_length;
2336
2337             continue;
2338         }
2339
2340         /* try to parse plain text */
2341         rc = parse_text(var, ctx, p, end);
2342         if (rc > 0) {
2343             if (!tokenbuf_append(output, p, rc)) {
2344                 rc = VAR_ERR_OUT_OF_MEMORY;
2345                 goto error_return;
2346             }
2347             p += rc;
2348             continue;
2349         } else if (rc < 0)
2350             goto error_return;
2351
2352         /* try to parse a variable construct */
2353         tokenbuf_init(&result);
2354         rc = parse_variable(var, ctx, p, end, &result);
2355         if (rc > 0) {
2356             if (!tokenbuf_merge(output, &result)) {
2357                 tokenbuf_free(&result);
2358                 rc = VAR_ERR_OUT_OF_MEMORY;
2359                 goto error_return;
2360             }
2361             tokenbuf_free(&result);
2362             p += rc;
2363             continue;
2364         }
2365         tokenbuf_free(&result);
2366         if (rc < 0)
2367             goto error_return;
2368
2369     } while (p != end && rc > 0);
2370
2371     /* We do not know whether this really could happen, but because we
2372        are paranoid, report an error at the outer most parsing level if
2373        there is still any input. Because this would mean that we are no
2374        longer able to parse the remaining input as a loop construct, a
2375        text or a variable construct. This would be very strange, but
2376        could perhaps happen in case of configuration errors!?... */
2377     if (recursion_level == 0 && p != end) {
2378         rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
2379         goto error_return;
2380     }
2381
2382     /* return amount of parsed text */
2383     return (var_rc_t)(p - begin);
2384
2385     /* return with an error where as a special case the output begin is
2386        set to the input begin and the output end to the last input parsing
2387        position. */
2388     error_return:
2389     tokenbuf_free(output);
2390     tokenbuf_set(output, begin, p, 0);
2391     return (var_rc_t)rc;
2392 }
2393
2394 /*
2395 **
2396 **  ==== APPLICATION PROGRAMMING INTERFACE (API) ====
2397 **
2398 */
2399
2400 /* create variable expansion context */
2401 var_rc_t
2402 var_create(
2403     var_t **pvar)
2404 {
2405     var_t *var;
2406
2407     if (pvar == NULL)
2408         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2409     if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
2410         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2411     memset(var, 0, sizeof(var_t));
2412     var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
2413     *pvar = var;
2414     return VAR_OK;
2415 }
2416
2417 /* destroy variable expansion context */
2418 var_rc_t
2419 var_destroy(
2420     var_t *var)
2421 {
2422     if (var == NULL)
2423         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2424     free(var);
2425     return VAR_OK;
2426 }
2427
2428 /* configure variable expansion context */
2429 var_rc_t
2430 var_config(
2431     var_t *var,
2432     var_config_t mode,
2433     ...)
2434 {
2435     va_list ap;
2436     var_rc_t rc = VAR_OK;
2437
2438     if (var == NULL)
2439         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2440     va_start(ap, mode);
2441     switch (mode) {
2442         case VAR_CONFIG_SYNTAX: {
2443             var_syntax_t *s;
2444             s = (var_syntax_t *)va_arg(ap, void *);
2445             if (s == NULL)
2446                 return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2447             var->syntax.escape      = s->escape;
2448             var->syntax.delim_init  = s->delim_init;
2449             var->syntax.delim_open  = s->delim_open;
2450             var->syntax.delim_close = s->delim_close;
2451             var->syntax.index_open  = s->index_open;
2452             var->syntax.index_close = s->index_close;
2453             var->syntax.index_mark  = s->index_mark;
2454             var->syntax.name_chars  = NULL; /* unused internally */
2455             if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
2456                 return VAR_RC(rc);
2457             if (   var->syntax_nameclass[(int)var->syntax.delim_init]
2458                 || var->syntax_nameclass[(int)var->syntax.delim_open]
2459                 || var->syntax_nameclass[(int)var->syntax.delim_close]
2460                 || var->syntax_nameclass[(int)var->syntax.escape])
2461                 return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
2462             break;
2463         }
2464         case VAR_CONFIG_CB_VALUE: {
2465             var_cb_value_t fct;
2466             void *ctx;
2467             fct = (var_cb_value_t)va_arg(ap, void *);
2468             ctx = (void *)va_arg(ap, void *);
2469             var->cb_value_fct = fct;
2470             var->cb_value_ctx = ctx;
2471             break;
2472         }
2473         case VAR_CONFIG_CB_OPERATION: {
2474             var_cb_operation_t fct;
2475             void *ctx;
2476             fct = (var_cb_operation_t)va_arg(ap, void *);
2477             ctx = (void *)va_arg(ap, void *);
2478             var->cb_operation_fct = fct;
2479             var->cb_operation_ctx = ctx;
2480             break;
2481         }
2482         default:
2483             return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2484     }
2485     va_end(ap);
2486     return VAR_OK;
2487 }
2488
2489 /* perform unescape operation on a buffer */
2490 var_rc_t
2491 var_unescape(
2492     var_t *var,
2493     const char *src, int srclen,
2494     char *dst, int dstlen,
2495     int all)
2496 {
2497     const char *end;
2498     var_rc_t rc;
2499
2500     if (var == NULL || src == NULL || dst == NULL)
2501         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2502     end = src + srclen;
2503     while (src < end) {
2504         if (*src == '\\') {
2505             if (++src == end)
2506                 return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
2507             switch (*src) {
2508                 case '\\':
2509                     if (!all) {
2510                         *dst++ = '\\';
2511                     }
2512                     *dst++ = '\\';
2513                     break;
2514                 case 'n':
2515                     *dst++ = '\n';
2516                     break;
2517                 case 't':
2518                     *dst++ = '\t';
2519                     break;
2520                 case 'r':
2521                     *dst++ = '\r';
2522                     break;
2523                 case 'x':
2524                     ++src;
2525                     if ((rc = expand_hex(&src, &dst, end)) != VAR_OK)
2526                         return VAR_RC(rc);
2527                     break;
2528                 case '0': case '1': case '2': case '3': case '4':
2529                 case '5': case '6': case '7': case '8': case '9':
2530                     if (   end - src >= 3
2531                         && isdigit((int)src[1])
2532                         && isdigit((int)src[2])) {
2533                         if ((rc = expand_octal(&src, &dst, end)) != 0)
2534                             return VAR_RC(rc);
2535                         break;
2536                     }
2537                 default:
2538                     if (!all) {
2539                         *dst++ = '\\';
2540                     }
2541                     *dst++ = *src;
2542             }
2543             ++src;
2544         } else
2545             *dst++ = *src++;
2546     }
2547     *dst = EOS;
2548     return VAR_OK;
2549 }
2550
2551 /* perform expand operation on a buffer */
2552 var_rc_t
2553 var_expand(
2554     var_t *var,
2555     const char *src_ptr, int src_len,
2556     char **dst_ptr, int *dst_len,
2557     int force_expand)
2558 {
2559     var_parse_t ctx;
2560     tokenbuf_t output;
2561     var_rc_t rc;
2562
2563     /* argument sanity checks */
2564     if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
2565         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2566
2567     /* prepare internal expansion context */
2568     ctx.lower           = NULL;
2569     ctx.force_expand    = force_expand;
2570     ctx.rel_lookup_flag = 0;
2571     ctx.rel_lookup_cnt  = 0;
2572     ctx.index_this      = 0;
2573
2574     /* start the parsing */
2575     tokenbuf_init(&output);
2576     rc = parse_input(var, &ctx, src_ptr, src_ptr+src_len, &output, 0);
2577
2578     /* post-processing */
2579     if (rc >= 0) {
2580         /* always EOS-terminate output for convinience reasons
2581            but do not count the EOS-terminator in the length */
2582         if (!tokenbuf_append(&output, "\0", 1)) {
2583             tokenbuf_free(&output);
2584             return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2585         }
2586         output.end--;
2587
2588         /* provide result */
2589         *dst_ptr = (char *)output.begin;
2590         if (dst_len != NULL)
2591             *dst_len = (output.end - output.begin);
2592         rc = VAR_OK;
2593     }
2594     else {
2595         /* provide result */
2596         if (dst_len != NULL)
2597             *dst_len = (output.end - output.begin);
2598     }
2599
2600     return VAR_RC(rc);
2601 }
2602
2603 /* format and expand a string */
2604 var_rc_t
2605 var_formatv(
2606     var_t *var,
2607     char **dst_ptr, int force_expand,
2608     const char *fmt, va_list ap)
2609 {
2610     var_rc_t rc;
2611     char *cpBuf;
2612     int nBuf = 5000;
2613
2614     /* argument sanity checks */
2615     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2616         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2617
2618     /* perform formatting */
2619     if ((cpBuf = (char *)malloc(nBuf+1)) == NULL)
2620         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2621     nBuf = var_mvsnprintf(cpBuf, nBuf+1, fmt, ap);
2622     if (nBuf == -1) {
2623         free(cpBuf);
2624         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2625     }
2626
2627     /* perform expansion */
2628     if ((rc = var_expand(var, cpBuf, nBuf, dst_ptr, NULL, force_expand)) != VAR_OK) {
2629         free(cpBuf);
2630         return VAR_RC(rc);
2631     }
2632
2633     /* cleanup */
2634     free(cpBuf);
2635
2636     return VAR_OK;
2637 }
2638
2639 /* format and expand a string */
2640 var_rc_t
2641 var_format(
2642     var_t *var,
2643     char **dst_ptr, int force_expand,
2644     const char *fmt, ...)
2645 {
2646     var_rc_t rc;
2647     va_list ap;
2648
2649     /* argument sanity checks */
2650     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2651         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2652
2653     va_start(ap, fmt);
2654     rc = var_formatv(var, dst_ptr, force_expand, fmt, ap);
2655     va_end(ap);
2656
2657     return VAR_RC(rc);
2658 }
2659
2660 /* var_rc_t to string mapping table */
2661 static const char *var_errors[] = {
2662     _("everything ok"),                                           /* VAR_OK = 0 */
2663     _("incomplete named character"),                              /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
2664     _("incomplete hexadecimal value"),                            /* VAR_ERR_INCOMPLETE_HEX */
2665     _("invalid hexadecimal value"),                               /* VAR_ERR_INVALID_HEX */
2666     _("octal value too large"),                                   /* VAR_ERR_OCTAL_TOO_LARGE */
2667     _("invalid octal value"),                                     /* VAR_ERR_INVALID_OCTAL */
2668     _("incomplete octal value"),                                  /* VAR_ERR_INCOMPLETE_OCTAL */
2669     _("incomplete grouped hexadecimal value"),                    /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
2670     _("incorrect character class specification"),                 /* VAR_ERR_INCORRECT_CLASS_SPEC */
2671     _("invalid expansion configuration"),                         /* VAR_ERR_INVALID_CONFIGURATION */
2672     _("out of memory"),                                           /* VAR_ERR_OUT_OF_MEMORY */
2673     _("incomplete variable specification"),                       /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
2674     _("undefined variable"),                                      /* VAR_ERR_UNDEFINED_VARIABLE */
2675     _("input is neither text nor variable"),                      /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
2676     _("unknown command character in variable"),                   /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
2677     _("malformatted search and replace operation"),               /* VAR_ERR_MALFORMATTED_REPLACE */
2678     _("unknown flag in search and replace operation"),            /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
2679     _("invalid regex in search and replace operation"),           /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
2680     _("missing parameter in command"),                            /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
2681     _("empty search string in search and replace operation"),     /* VAR_ERR_EMPTY_SEARCH_STRING */
2682     _("start offset missing in cut operation"),                   /* VAR_ERR_MISSING_START_OFFSET */
2683     _("offsets in cut operation delimited by unknown character"), /* VAR_ERR_INVALID_OFFSET_DELIMITER */
2684     _("range out of bounds in cut operation"),                    /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
2685     _("offset out of bounds in cut operation"),                   /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
2686     _("logic error in cut operation"),                            /* VAR_ERR_OFFSET_LOGIC */
2687     _("malformatted transpose operation"),                        /* VAR_ERR_MALFORMATTED_TRANSPOSE */
2688     _("source and target class mismatch in transpose operation"), /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
2689     _("empty character class in transpose operation"),            /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
2690     _("incorrect character class in transpose operation"),        /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
2691     _("malformatted padding operation"),                          /* VAR_ERR_MALFORMATTED_PADDING */
2692     _("width parameter missing in padding operation"),            /* VAR_ERR_MISSING_PADDING_WIDTH */
2693     _("fill string missing in padding operation"),                /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
2694     _("unknown quoted pair in search and replace operation"),     /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
2695     _("sub-matching reference out of range"),                     /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
2696     _("invalid argument"),                                        /* VAR_ERR_INVALID_ARGUMENT */
2697     _("incomplete quoted pair"),                                  /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
2698     _("lookup function does not support variable arrays"),        /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
2699     _("index of array variable contains an invalid character"),   /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
2700     _("index of array variable is incomplete"),                   /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
2701     _("bracket expression in array variable's index not closed"), /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
2702     _("division by zero error in index specification"),           /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
2703     _("unterminated loop construct"),                             /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
2704     _("invalid character in loop limits"),                        /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
2705     _("malformed operation argument list"),                       /* VAR_ERR_MALFORMED_OPERATION_ARGUMENTS */
2706     _("undefined operation"),                                     /* VAR_ERR_UNDEFINED_OPERATION */
2707     _("formatting failure")                                       /* VAR_ERR_FORMATTING_FAILURE */
2708 };
2709
2710 /* translate a return code into its corresponding descriptive text */
2711 const char *var_strerror(var_t *var, var_rc_t rc)
2712 {
2713     const char *str;
2714     rc = (var_rc_t)(0 - rc);
2715     if (rc < 0 || rc >= (int)sizeof(var_errors) / (int)sizeof(char *)) {
2716         str = _("unknown error");
2717     } else {
2718         str = (char *)var_errors[rc];
2719     }
2720     return str;
2721 }