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