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