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