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