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