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