]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/var.c
kes Fix sscanf problems reported by Peter Buschman that caused
[bacula/bacula] / bacula / src / lib / var.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2003-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29 **  OSSP var - Variable Expansion
30 **  Copyright (c) 2001-2002 Ralf S. Engelschall <rse@engelschall.com>
31 **  Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
32 **  Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
33 **
34 **  This file is part of OSSP var, a variable expansion
35 **  library which can be found at http://www.ossp.org/pkg/lib/var/.
36 **
37 **  Permission to use, copy, modify, and distribute this software for
38 **  any purpose with or without fee is hereby granted, provided that
39 **  the above copyright notice and this permission notice appear in all
40 **  copies.
41 **
42 **  For disclaimer see below.
43 */
44 /*
45  *  Adapted by Kern Sibbald to Bacula June 2003
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     if ((inc = (*p++ == '+'))) {
1994        p++;                           /* skip the + */ 
1995     }
1996
1997     /* lookup the variable value now */
1998     if (failed) {
1999         tokenbuf_set(result, begin - 1, p, 0);
2000     } else {
2001         rc = lookup_value(var, ctx,
2002                           name.begin, name.end-name.begin, inc, idx,
2003                           &data, &len, &buffer_size);
2004         if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
2005             tokenbuf_init(result); /* delayed handling of undefined variable */
2006         } else if (rc < 0) {
2007             goto error_return;
2008         } else {
2009             /* the preliminary result is the raw value of the variable.
2010                This may be modified by the operations that may follow. */
2011             tokenbuf_set(result, data, data + len, buffer_size);
2012         }
2013     }
2014
2015     /* parse optional post-operations */
2016 goahead:
2017     if (p[-1] == ':') {
2018         tokenbuf_free(&tmp);
2019         tokenbuf_init(&tmp);
2020         p--;
2021         while (p != end && *p == ':') {
2022             p++;
2023             if (!failed)
2024                 rc = parse_operation(var, ctx, p, end, result);
2025             else
2026                 rc = parse_operation(var, ctx, p, end, &tmp);
2027             if (rc < 0)
2028                 goto error_return;
2029             p += rc;
2030             if (failed)
2031                 result->end += rc;
2032         }
2033         if (p == end || *p != var->syntax.delim_close) {
2034             rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2035             goto error_return;
2036         }
2037         p++;
2038         if (failed)
2039             result->end++;
2040     } else if (p[-1] == '+') {
2041        p++;
2042     }
2043
2044     /* lazy handling of undefined variable */
2045     if (!failed && tokenbuf_isundef(result)) {
2046         if (ctx->force_expand) {
2047             rc = VAR_ERR_UNDEFINED_VARIABLE;
2048             goto error_return;
2049         } else {
2050             tokenbuf_set(result, begin - 1, p, 0);
2051         }
2052     }
2053
2054     /* return successfully */
2055     tokenbuf_free(&name);
2056     tokenbuf_free(&tmp);
2057     return (p - begin);
2058
2059     /* return with an error */
2060 error_return:
2061     tokenbuf_free(&name);
2062     tokenbuf_free(&tmp);
2063     tokenbuf_free(result);
2064     return rc;
2065 }
2066
2067 /* parse variable construct ("$name" or "${name...}") */
2068 static int
2069 parse_variable(
2070     var_t *var, var_parse_t *ctx,
2071     const char *begin, const char *end,
2072     tokenbuf_t *result)
2073 {
2074     const char *p;
2075     const char *data;
2076     int len, buffer_size;
2077     int rc, rc2;
2078     int inc;
2079
2080     /* initialization */
2081     p = begin;
2082     tokenbuf_init(result);
2083
2084     /* parse init delimiter */
2085     if (p == end || *p != var->syntax.delim_init)
2086         return 0;
2087     p++;
2088     if (p == end)
2089         return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
2090
2091     /* parse a simple variable name.
2092        (if this fails, we're try to parse a complex variable construct) */
2093     rc = parse_name(var, ctx, p, end);
2094     if (rc < 0)
2095         return rc;
2096     if (rc > 0) {
2097         inc = (p[rc] == '+');
2098         rc2 = lookup_value(var, ctx, p, rc, inc, 0, &data, &len, &buffer_size);
2099         if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !ctx->force_expand) {
2100             tokenbuf_set(result, begin, begin + 1 + rc, 0);
2101             return (1 + rc);
2102         }
2103         if (rc2 < 0)
2104             return rc2;
2105         tokenbuf_set(result, data, data + len, buffer_size);
2106         return (1 + rc);
2107     }
2108
2109     /* parse a complex variable construct (else case) */
2110     rc = parse_variable_complex(var, ctx, p, end, result);
2111     if (rc > 0)
2112         rc++;
2113     return rc;
2114 }
2115
2116 /* parse loop construct limits ("[...]{b,s,e}") */
2117 static var_rc_t
2118 parse_looplimits(
2119     var_t *var, var_parse_t *ctx,
2120     const char *begin, const char *end,
2121     int *start, int *step, int *stop, int *open_stop)
2122 {
2123     const char *p;
2124     int rc;
2125     int failed;
2126
2127     /* initialization */
2128     p = begin;
2129
2130     /* we are happy if nothing is to left to parse */
2131     if (p == end)
2132         return VAR_OK;
2133
2134     /* parse start delimiter */
2135     if (*p != var->syntax.delim_open)
2136         return VAR_OK;
2137     p++;
2138
2139     /* parse loop start value */
2140     failed = 0;
2141     rc = parse_numexp(var, ctx, p, end, start, &failed);
2142     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2143         *start = 0; /* use default */
2144     else if (rc < 0)
2145         return (var_rc_t)rc;
2146     else
2147         p += rc;
2148     if (failed)
2149         return VAR_ERR_UNDEFINED_VARIABLE;
2150
2151     /* parse separator */
2152     if (*p != ',')
2153         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2154     p++;
2155
2156     /* parse loop step value */
2157     failed = 0;
2158     rc = parse_numexp(var, ctx, p, end, step, &failed);
2159     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
2160         *step = 1; /* use default */
2161     else if (rc < 0)
2162         return (var_rc_t)rc;
2163     else
2164         p += rc;
2165     if (failed)
2166         return VAR_ERR_UNDEFINED_VARIABLE;
2167
2168     /* parse separator */
2169     if (*p != ',') {
2170         /* if not found, parse end delimiter */
2171         if (*p != var->syntax.delim_close)
2172             return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2173         p++;
2174
2175         /* shift step value to stop value */
2176         *stop = *step;
2177         *step = 1;
2178
2179         /* determine whether loop end is open */
2180         if (rc > 0)
2181             *open_stop = 0;
2182         else
2183             *open_stop = 1;
2184         return (var_rc_t)(p - begin);
2185     }
2186     p++;
2187
2188     /* parse loop stop value */
2189     failed = 0;
2190     rc = parse_numexp(var, ctx, p, end, stop, &failed);
2191     if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
2192         *stop = 0; /* use default */
2193         *open_stop = 1;
2194     }
2195     else if (rc < 0)
2196         return (var_rc_t)rc;
2197     else {
2198         *open_stop = 0;
2199         p += rc;
2200     }
2201     if (failed)
2202         return VAR_ERR_UNDEFINED_VARIABLE;
2203
2204     /* parse end delimiter */
2205     if (*p != var->syntax.delim_close)
2206         return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
2207     p++;
2208
2209     /* return amount of parsed input */
2210     return (var_rc_t)(p - begin);
2211 }
2212
2213 /* parse plain text */
2214 static int
2215 parse_text(
2216     var_t *var, var_parse_t *ctx,
2217     const char *begin, const char *end)
2218 {
2219     const char *p;
2220
2221     /* parse until delim_init (variable construct)
2222        or index_open (loop construct) is found */
2223     for (p = begin; p != end; p++) {
2224         if (*p == var->syntax.escape) {
2225             p++; /* skip next character */
2226             if (p == end)
2227                 return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
2228         }
2229         else if (*p == var->syntax.delim_init)
2230             break;
2231         else if (   var->syntax.index_open != EOS
2232                  && (   *p == var->syntax.index_open
2233                      || *p == var->syntax.index_close))
2234             break;
2235     }
2236     return (p - begin);
2237 }
2238
2239 /* expand input in general */
2240 static var_rc_t
2241 parse_input(
2242     var_t *var, var_parse_t *ctx,
2243     const char *begin, const char *end,
2244     tokenbuf_t *output, int recursion_level)
2245 {
2246     const char *p;
2247     int rc, rc2;
2248     tokenbuf_t result;
2249     int start, step, stop, open_stop;
2250     int i;
2251     int output_backup;
2252     int rel_lookup_cnt;
2253     int loop_limit_length;
2254     var_parse_t myctx;
2255
2256     /* initialization */
2257     p = begin;
2258
2259     do {
2260         /* try to parse a loop construct */
2261         if (   p != end
2262             && var->syntax.index_open != EOS
2263             && *p == var->syntax.index_open) {
2264             p++;
2265
2266             /* loop preparation */
2267             loop_limit_length = -1;
2268             rel_lookup_cnt = ctx->rel_lookup_cnt;
2269             open_stop = 1;
2270             rc = 0;
2271             start = 0;
2272             step  = 1;
2273             stop  = 0;
2274             output_backup = 0;
2275
2276             /* iterate over loop construct, either as long as there is
2277                (still) nothing known about the limit, or there is an open
2278                (=unknown) limit stop and there are still defined variables
2279                or there is a stop limit known and it is still not reached */
2280             re_loop:
2281             for (i = start;
2282                  (   (   open_stop
2283                       && (   loop_limit_length < 0
2284                           || rel_lookup_cnt > ctx->rel_lookup_cnt))
2285                   || (   !open_stop
2286                       && i <= stop)                                );
2287                  i += step) {
2288
2289                 /* remember current output end for restoring */
2290                 output_backup = (output->end - output->begin);
2291
2292                 /* open temporary context for recursion */
2293                 ctx = var_parse_push(ctx, &myctx);
2294                 ctx->force_expand    = 1;
2295                 ctx->rel_lookup_flag = 1;
2296                 ctx->index_this      = i;
2297
2298                 /* recursive parse input through ourself */
2299                 rc = parse_input(var, ctx, p, end,
2300                                  output, recursion_level+1);
2301
2302                 /* retrieve info and close temporary context */
2303                 rel_lookup_cnt = ctx->rel_lookup_cnt;
2304                 ctx = var_parse_pop(ctx);
2305
2306                 /* error handling */
2307                 if (rc < 0)
2308                     goto error_return;
2309
2310                 /* make sure the loop construct is closed */
2311                 if (p[rc] != var->syntax.index_close) {
2312                     rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
2313                     goto error_return;
2314                 }
2315
2316                 /* try to parse loop construct limit specification */
2317                 if (loop_limit_length < 0) {
2318                     rc2 = parse_looplimits(var, ctx, p+rc+1, end,
2319                                            &start, &step, &stop, &open_stop);
2320                     if (rc2 < 0)
2321                         goto error_return;
2322                     else if (rc2 == 0)
2323                         loop_limit_length = 0;
2324                     else if (rc2 > 0) {
2325                         loop_limit_length = rc2;
2326                         /* restart loop from scratch */
2327                         output->end = (output->begin + output_backup);
2328                         goto re_loop;
2329                     }
2330                 }
2331             }
2332
2333             /* if stop value is open, restore to the output end
2334                because the last iteration was just to determine the loop
2335                termination and its result has to be discarded */
2336             if (open_stop)
2337                 output->end = (output->begin + output_backup);
2338
2339             /* skip parsed loop construct */
2340             p += rc;
2341             p++;
2342             p += loop_limit_length;
2343
2344             continue;
2345         }
2346
2347         /* try to parse plain text */
2348         rc = parse_text(var, ctx, p, end);
2349         if (rc > 0) {
2350             if (!tokenbuf_append(output, p, rc)) {
2351                 rc = VAR_ERR_OUT_OF_MEMORY;
2352                 goto error_return;
2353             }
2354             p += rc;
2355             continue;
2356         } else if (rc < 0)
2357             goto error_return;
2358
2359         /* try to parse a variable construct */
2360         tokenbuf_init(&result);
2361         rc = parse_variable(var, ctx, p, end, &result);
2362         if (rc > 0) {
2363             if (!tokenbuf_merge(output, &result)) {
2364                 tokenbuf_free(&result);
2365                 rc = VAR_ERR_OUT_OF_MEMORY;
2366                 goto error_return;
2367             }
2368             tokenbuf_free(&result);
2369             p += rc;
2370             continue;
2371         }
2372         tokenbuf_free(&result);
2373         if (rc < 0)
2374             goto error_return;
2375
2376     } while (p != end && rc > 0);
2377
2378     /* We do not know whether this really could happen, but because we
2379        are paranoid, report an error at the outer most parsing level if
2380        there is still any input. Because this would mean that we are no
2381        longer able to parse the remaining input as a loop construct, a
2382        text or a variable construct. This would be very strange, but
2383        could perhaps happen in case of configuration errors!?... */
2384     if (recursion_level == 0 && p != end) {
2385         rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
2386         goto error_return;
2387     }
2388
2389     /* return amount of parsed text */
2390     return (var_rc_t)(p - begin);
2391
2392     /* return with an error where as a special case the output begin is
2393        set to the input begin and the output end to the last input parsing
2394        position. */
2395     error_return:
2396     tokenbuf_free(output);
2397     tokenbuf_set(output, begin, p, 0);
2398     return (var_rc_t)rc;
2399 }
2400
2401 /*
2402 **
2403 **  ==== APPLICATION PROGRAMMING INTERFACE (API) ====
2404 **
2405 */
2406
2407 /* create variable expansion context */
2408 var_rc_t
2409 var_create(
2410     var_t **pvar)
2411 {
2412     var_t *var;
2413
2414     if (pvar == NULL)
2415         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2416     if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
2417         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2418     memset(var, 0, sizeof(var));
2419     var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
2420     *pvar = var;
2421     return VAR_OK;
2422 }
2423
2424 /* destroy variable expansion context */
2425 var_rc_t
2426 var_destroy(
2427     var_t *var)
2428 {
2429     if (var == NULL)
2430         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2431     free(var);
2432     return VAR_OK;
2433 }
2434
2435 /* configure variable expansion context */
2436 var_rc_t
2437 var_config(
2438     var_t *var,
2439     var_config_t mode,
2440     ...)
2441 {
2442     va_list ap;
2443     var_rc_t rc;
2444
2445     if (var == NULL)
2446         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2447     va_start(ap, mode);
2448     switch (mode) {
2449         case VAR_CONFIG_SYNTAX: {
2450             var_syntax_t *s;
2451             s = (var_syntax_t *)va_arg(ap, void *);
2452             if (s == NULL)
2453                 return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2454             var->syntax.escape      = s->escape;
2455             var->syntax.delim_init  = s->delim_init;
2456             var->syntax.delim_open  = s->delim_open;
2457             var->syntax.delim_close = s->delim_close;
2458             var->syntax.index_open  = s->index_open;
2459             var->syntax.index_close = s->index_close;
2460             var->syntax.index_mark  = s->index_mark;
2461             var->syntax.name_chars  = NULL; /* unused internally */
2462             if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
2463                 return VAR_RC(rc);
2464             if (   var->syntax_nameclass[(int)var->syntax.delim_init]
2465                 || var->syntax_nameclass[(int)var->syntax.delim_open]
2466                 || var->syntax_nameclass[(int)var->syntax.delim_close]
2467                 || var->syntax_nameclass[(int)var->syntax.escape])
2468                 return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
2469             break;
2470         }
2471         case VAR_CONFIG_CB_VALUE: {
2472             var_cb_value_t fct;
2473             void *ctx;
2474             fct = (var_cb_value_t)va_arg(ap, void *);
2475             ctx = (void *)va_arg(ap, void *);
2476             var->cb_value_fct = fct;
2477             var->cb_value_ctx = ctx;
2478             break;
2479         }
2480         case VAR_CONFIG_CB_OPERATION: {
2481             var_cb_operation_t fct;
2482             void *ctx;
2483             fct = (var_cb_operation_t)va_arg(ap, void *);
2484             ctx = (void *)va_arg(ap, void *);
2485             var->cb_operation_fct = fct;
2486             var->cb_operation_ctx = ctx;
2487             break;
2488         }
2489         default:
2490             return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2491     }
2492     va_end(ap);
2493     return VAR_OK;
2494 }
2495
2496 /* perform unescape operation on a buffer */
2497 var_rc_t
2498 var_unescape(
2499     var_t *var,
2500     const char *src, int srclen,
2501     char *dst, int dstlen,
2502     int all)
2503 {
2504     const char *end;
2505     var_rc_t rc;
2506
2507     if (var == NULL || src == NULL || dst == NULL)
2508         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2509     end = src + srclen;
2510     while (src < end) {
2511         if (*src == '\\') {
2512             if (++src == end)
2513                 return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
2514             switch (*src) {
2515                 case '\\':
2516                     if (!all) {
2517                         *dst++ = '\\';
2518                     }
2519                     *dst++ = '\\';
2520                     break;
2521                 case 'n':
2522                     *dst++ = '\n';
2523                     break;
2524                 case 't':
2525                     *dst++ = '\t';
2526                     break;
2527                 case 'r':
2528                     *dst++ = '\r';
2529                     break;
2530                 case 'x':
2531                     ++src;
2532                     if ((rc = expand_hex(&src, &dst, end)) != VAR_OK)
2533                         return VAR_RC(rc);
2534                     break;
2535                 case '0': case '1': case '2': case '3': case '4':
2536                 case '5': case '6': case '7': case '8': case '9':
2537                     if (   end - src >= 3
2538                         && isdigit((int)src[1])
2539                         && isdigit((int)src[2])) {
2540                         if ((rc = expand_octal(&src, &dst, end)) != 0)
2541                             return VAR_RC(rc);
2542                         break;
2543                     }
2544                 default:
2545                     if (!all) {
2546                         *dst++ = '\\';
2547                     }
2548                     *dst++ = *src;
2549             }
2550             ++src;
2551         } else
2552             *dst++ = *src++;
2553     }
2554     *dst = EOS;
2555     return VAR_OK;
2556 }
2557
2558 /* perform expand operation on a buffer */
2559 var_rc_t
2560 var_expand(
2561     var_t *var,
2562     const char *src_ptr, int src_len,
2563     char **dst_ptr, int *dst_len,
2564     int force_expand)
2565 {
2566     var_parse_t ctx;
2567     tokenbuf_t output;
2568     var_rc_t rc;
2569
2570     /* argument sanity checks */
2571     if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
2572         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2573
2574     /* prepare internal expansion context */
2575     ctx.lower           = NULL;
2576     ctx.force_expand    = force_expand;
2577     ctx.rel_lookup_flag = 0;
2578     ctx.rel_lookup_cnt  = 0;
2579     ctx.index_this      = 0;
2580
2581     /* start the parsing */
2582     tokenbuf_init(&output);
2583     rc = parse_input(var, &ctx, src_ptr, src_ptr+src_len, &output, 0);
2584
2585     /* post-processing */
2586     if (rc >= 0) {
2587         /* always EOS-terminate output for convinience reasons
2588            but do not count the EOS-terminator in the length */
2589         if (!tokenbuf_append(&output, "\0", 1)) {
2590             tokenbuf_free(&output);
2591             return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2592         }
2593         output.end--;
2594
2595         /* provide result */
2596         *dst_ptr = (char *)output.begin;
2597         if (dst_len != NULL)
2598             *dst_len = (output.end - output.begin);
2599         rc = VAR_OK;
2600     }
2601     else {
2602         /* provide result */
2603         if (dst_len != NULL)
2604             *dst_len = (output.end - output.begin);
2605     }
2606
2607     return VAR_RC(rc);
2608 }
2609
2610 /* format and expand a string */
2611 var_rc_t
2612 var_formatv(
2613     var_t *var,
2614     char **dst_ptr, int force_expand,
2615     const char *fmt, va_list ap)
2616 {
2617     var_rc_t rc;
2618     char *cpBuf;
2619     int nBuf = 5000;
2620
2621     /* argument sanity checks */
2622     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2623         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2624
2625     /* perform formatting */
2626     if ((cpBuf = (char *)malloc(nBuf+1)) == NULL)
2627         return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
2628     nBuf = var_mvsnprintf(cpBuf, nBuf+1, fmt, ap);
2629     if (nBuf == -1) {
2630         free(cpBuf);
2631         return VAR_RC(VAR_ERR_FORMATTING_FAILURE);
2632     }
2633
2634     /* perform expansion */
2635     if ((rc = var_expand(var, cpBuf, nBuf, dst_ptr, NULL, force_expand)) != VAR_OK) {
2636         free(cpBuf);
2637         return VAR_RC(rc);
2638     }
2639
2640     /* cleanup */
2641     free(cpBuf);
2642
2643     return VAR_OK;
2644 }
2645
2646 /* format and expand a string */
2647 var_rc_t
2648 var_format(
2649     var_t *var,
2650     char **dst_ptr, int force_expand,
2651     const char *fmt, ...)
2652 {
2653     var_rc_t rc;
2654     va_list ap;
2655
2656     /* argument sanity checks */
2657     if (var == NULL || dst_ptr == NULL || fmt == NULL)
2658         return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
2659
2660     va_start(ap, fmt);
2661     rc = var_formatv(var, dst_ptr, force_expand, fmt, ap);
2662     va_end(ap);
2663
2664     return VAR_RC(rc);
2665 }
2666
2667 /* var_rc_t to string mapping table */
2668 static const char *var_errors[] = {
2669     _("everything ok"),                                           /* VAR_OK = 0 */
2670     _("incomplete named character"),                              /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
2671     _("incomplete hexadecimal value"),                            /* VAR_ERR_INCOMPLETE_HEX */
2672     _("invalid hexadecimal value"),                               /* VAR_ERR_INVALID_HEX */
2673     _("octal value too large"),                                   /* VAR_ERR_OCTAL_TOO_LARGE */
2674     _("invalid octal value"),                                     /* VAR_ERR_INVALID_OCTAL */
2675     _("incomplete octal value"),                                  /* VAR_ERR_INCOMPLETE_OCTAL */
2676     _("incomplete grouped hexadecimal value"),                    /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
2677     _("incorrect character class specification"),                 /* VAR_ERR_INCORRECT_CLASS_SPEC */
2678     _("invalid expansion configuration"),                         /* VAR_ERR_INVALID_CONFIGURATION */
2679     _("out of memory"),                                           /* VAR_ERR_OUT_OF_MEMORY */
2680     _("incomplete variable specification"),                       /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
2681     _("undefined variable"),                                      /* VAR_ERR_UNDEFINED_VARIABLE */
2682     _("input is neither text nor variable"),                      /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
2683     _("unknown command character in variable"),                   /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
2684     _("malformatted search and replace operation"),               /* VAR_ERR_MALFORMATTED_REPLACE */
2685     _("unknown flag in search and replace operation"),            /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
2686     _("invalid regex in search and replace operation"),           /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
2687     _("missing parameter in command"),                            /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
2688     _("empty search string in search and replace operation"),     /* VAR_ERR_EMPTY_SEARCH_STRING */
2689     _("start offset missing in cut operation"),                   /* VAR_ERR_MISSING_START_OFFSET */
2690     _("offsets in cut operation delimited by unknown character"), /* VAR_ERR_INVALID_OFFSET_DELIMITER */
2691     _("range out of bounds in cut operation"),                    /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
2692     _("offset out of bounds in cut operation"),                   /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
2693     _("logic error in cut operation"),                            /* VAR_ERR_OFFSET_LOGIC */
2694     _("malformatted transpose operation"),                        /* VAR_ERR_MALFORMATTED_TRANSPOSE */
2695     _("source and target class mismatch in transpose operation"), /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
2696     _("empty character class in transpose operation"),            /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
2697     _("incorrect character class in transpose operation"),        /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
2698     _("malformatted padding operation"),                          /* VAR_ERR_MALFORMATTED_PADDING */
2699     _("width parameter missing in padding operation"),            /* VAR_ERR_MISSING_PADDING_WIDTH */
2700     _("fill string missing in padding operation"),                /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
2701     _("unknown quoted pair in search and replace operation"),     /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
2702     _("sub-matching reference out of range"),                     /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
2703     _("invalid argument"),                                        /* VAR_ERR_INVALID_ARGUMENT */
2704     _("incomplete quoted pair"),                                  /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
2705     _("lookup function does not support variable arrays"),        /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
2706     _("index of array variable contains an invalid character"),   /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
2707     _("index of array variable is incomplete"),                   /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
2708     _("bracket expression in array variable's index not closed"), /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
2709     _("division by zero error in index specification"),           /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
2710     _("unterminated loop construct"),                             /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
2711     _("invalid character in loop limits"),                        /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
2712     _("malformed operation argument list"),                       /* VAR_ERR_MALFORMED_OPERATION_ARGUMENTS */
2713     _("undefined operation"),                                     /* VAR_ERR_UNDEFINED_OPERATION */
2714     _("formatting failure")                                       /* VAR_ERR_FORMATTING_FAILURE */
2715 };
2716
2717 /* translate a return code into its corresponding descriptive text */
2718 const char *var_strerror(var_t *var, var_rc_t rc)
2719 {
2720     const char *str;
2721     rc = (var_rc_t)(0 - rc);
2722     if (rc < 0 || rc >= (int)sizeof(var_errors) / (int)sizeof(char *)) {
2723         str = _("unknown error");
2724     } else {
2725         str = (char *)var_errors[rc];
2726     }
2727     return str;
2728 }