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