/* ** VAR - OSSP variable expression library. ** Copyright (c) 2001 The OSSP Project (http://www.ossp.org/) ** Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/) ** ** This file is part of OSSP VAR, an extensible data serialization ** library which can be found at http://www.ossp.org/pkg/var/. ** ** Permission to use, copy, modify, and distribute this software for ** any purpose with or without fee is hereby granted, provided that ** the above copyright notice and this permission notice appear in all ** copies. ** ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. ** ** var.c: VAR library implementation. */ #include #include #include #include #include #include #include "var.h" /* The default configuration for the parser. */ const var_config_t var_config_default = { '$', /* varinit */ '{', /* startdelim */ '}', /* enddelim */ '\\', /* escape */ "a-zA-Z0-9_" /* namechars */ }; /* Routines for manipulation of tokenbufs. */ #define VAR_INITIAL_BUFFER_SIZE 64 typedef struct { const char* begin; const char* end; size_t buffer_size; } tokenbuf; static void init_tokenbuf(tokenbuf* buf) { buf->begin = buf->end = NULL; buf->buffer_size = 0; } static void move_tokenbuf(tokenbuf* src, tokenbuf* dst) { dst->begin = src->begin; dst->end = src->end; dst->buffer_size = src->buffer_size; init_tokenbuf(src); } static int assign_to_tokenbuf(tokenbuf* buf, const char* data, size_t len) { char* p = malloc(len+1); if (p) { memcpy(p, data, len); buf->begin = p; buf->end = p + len; buf->buffer_size = len + 1; *((char*)(buf->end)) = '\0'; return 1; } else return 0; } static int append_to_tokenbuf(tokenbuf* output, const char* data, size_t len) { char* new_buffer; size_t new_size; /* Is the tokenbuffer initialized at all? If not, allocate a standard-sized buffer to begin with. */ if (output->begin == NULL) { if ((output->begin = output->end = malloc(VAR_INITIAL_BUFFER_SIZE)) == NULL) return 0; else output->buffer_size = VAR_INITIAL_BUFFER_SIZE; } /* Does the token contain text, but no buffer has been allocated yet? */ if (output->buffer_size == 0) { /* Check whether data borders to output. If, we can append simly by increasing the end pointer. */ if (output->end == data) { output->end += len; return 1; } /* OK, so copy the contents of output into an allocated buffer so that we can append that way. */ else { char* tmp = malloc(output->end - output->begin + len + 1); if (!tmp) return 0; memcpy(tmp, output->begin, output->end - output->begin); output->buffer_size = output->end - output->begin; output->begin = tmp; output->end = tmp + output->buffer_size; output->buffer_size += len + 1; } } /* Does the token fit into the current buffer? If not, realloc a larger buffer that fits. */ if ((output->buffer_size - (output->end - output->begin)) <= len) { new_size = output->buffer_size; do { new_size *= 2; } while ((new_size - (output->end - output->begin)) <= len); new_buffer = realloc((char*)output->begin, new_size); if (new_buffer == NULL) return 0; output->end = new_buffer + (output->end - output->begin); output->begin = new_buffer; output->buffer_size = new_size; } /* Append the data at the end of the current buffer. */ memcpy((char*)output->end, data, len); output->end += len; *((char*)output->end) = '\0'; return 1; } static void free_tokenbuf(tokenbuf* buf) { if (buf->begin != NULL && buf->buffer_size > 0) free((char*)buf->begin); buf->begin = buf->end = NULL; buf->buffer_size = 0; } static size_t tokenbuf2int(tokenbuf* number) { const char* p; size_t num = 0; for (p = number->begin; p != number->end; ++p) { num *= 10; num += *p - '0'; } return num; } /* Routines for the expansion of quoted-pair expressions. */ static void expand_range(char a, char b, char class[256]) { do { class[(int)a] = 1; } while (++a <= b); } static var_rc_t expand_character_class(const char* desc, char class[256]) { size_t i; /* Clear the class array. */ for (i = 0; i < 256; ++i) class[i] = 0; /* Walk through the class description and set the appropriate entries in the array. */ while(*desc != '\0') { if (desc[1] == '-' && desc[2] != '\0') { if (desc[0] > desc[2]) return VAR_INCORRECT_CLASS_SPEC; expand_range(desc[0], desc[2], class); desc += 3; } else { class[(int)*desc] = 1; ++desc; } } return VAR_OK; } static int isoct(char c) { if (c >= '0' && c <= '7') return 1; else return 0; } static var_rc_t expand_octal(const char** src, char** dst, const char* end) { unsigned char c; if (end - *src < 3) return VAR_INCOMPLETE_OCTAL; if (!isoct(**src) || !isoct((*src)[1]) || !isoct((*src)[2])) return VAR_INVALID_OCTAL; c = **src - '0'; if (c > 3) return VAR_OCTAL_TOO_LARGE; c *= 8; ++(*src); c += **src - '0'; c *= 8; ++(*src); c += **src - '0'; **dst = (char)c; ++(*dst); return VAR_OK; } static int ishex(char c) { if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) return 1; else return 0; } static var_rc_t expand_simple_hex(const char** src, char** dst, const char* end) { unsigned char c = 0; if (end - *src < 2) return VAR_INCOMPLETE_HEX; if (!ishex(**src) || !ishex((*src)[1])) return VAR_INVALID_HEX; if (**src >= '0' && **src <= '9') c = **src - '0'; else if (c >= 'a' && c <= 'f') c = **src - 'a' + 10; else if (c >= 'A' && c <= 'F') c = **src - 'A' + 10; c = c << 4; ++(*src); if (**src >= '0' && **src <= '9') c += **src - '0'; else if (**src >= 'a' && **src <= 'f') c += **src - 'a' + 10; else if (**src >= 'A' && **src <= 'F') c += **src - 'A' + 10; **dst = (char)c; ++(*dst); return VAR_OK; } static var_rc_t expand_grouped_hex(const char** src, char** dst, const char* end) { var_rc_t rc; while (*src < end && **src != '}') { if ((rc = expand_simple_hex(src, dst, end)) != 0) return rc; ++(*src); } if (*src == end) return VAR_INCOMPLETE_GROUPED_HEX; return VAR_OK; } static var_rc_t expand_hex(const char** src, char** dst, const char* end) { if (*src == end) return VAR_INCOMPLETE_HEX; if (**src == '{') { ++(*src); return expand_grouped_hex(src, dst, end); } else return expand_simple_hex(src, dst, end); } var_rc_t var_unescape(const char* src, size_t len, char* dst, int unescape_all) { const char* end = src + len; var_rc_t rc; while (src < end) { if (*src == '\\') { if (++src == end) return VAR_INCOMPLETE_NAMED_CHARACTER; switch (*src) { case 'n': *dst++ = '\n'; break; case 't': *dst++ = '\t'; break; case 'r': *dst++ = '\r'; break; case 'x': ++src; if ((rc = expand_hex(&src, &dst, end)) != 0) return rc; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (end - src >= 3 && isdigit(src[1]) && isdigit(src[2])) { if ((rc = expand_octal(&src, &dst, end)) != 0) return rc; break; } default: if (!unescape_all) { *dst++ = '\\'; } *dst++ = *src; } ++src; } else *dst++ = *src++; } *dst = '\0'; return VAR_OK; } /* The recursive-descent parser for variable expressions. */ static int variable(const char*, const char*, const var_config_t*, const char[256], var_cb_t, void*, int, tokenbuf*); static int command(const char*, const char*, const var_config_t*, const char[256], var_cb_t, void*, int, tokenbuf*); static int text(const char* begin, const char* end, char varinit, char escape) { const char* p; for (p = begin; p != end && *p != varinit; ++p) { if (*p == escape) { if (p+1 == end) return VAR_INCOMPLETE_QUOTED_PAIR; else ++p; } } return p - begin; } static int varname(const char* begin, const char* end, const char nameclass[256]) { const char* p; for (p = begin; p != end && nameclass[(int)*p]; ++p) ; return p - begin; } static int number(const char* begin, const char* end) { const char* p; for (p = begin; p != end && isdigit(*p); ++p) ; return p - begin; } static int substext(const char* begin, const char* end, const var_config_t* config) { const char* p; for (p = begin; p != end && *p != config->varinit && *p != '/'; ++p) { if (*p == config->escape) { if (p+1 == end) return VAR_INCOMPLETE_QUOTED_PAIR; else ++p; } } return p - begin; } static int exptext(const char* begin, const char* end, const var_config_t* config) { const char* p; for (p = begin; p != end && *p != config->varinit && *p != config->enddelim && *p != ':'; ++p) { if (*p == config->escape) { if (p+1 == end) return VAR_INCOMPLETE_QUOTED_PAIR; else ++p; } } return p - begin; } static int expression(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* result) { const char* p = begin; const char* data; size_t len, buffer_size; int failed = 0; int rc; tokenbuf name; tokenbuf tmp; /* Clear the tokenbufs to make sure we have a defined state. */ init_tokenbuf(&name); init_tokenbuf(&tmp); init_tokenbuf(result); /* Expect STARTDELIM. */ if (p == end || *p != config->startdelim) return 0; if (++p == end) return VAR_INCOMPLETE_VARIABLE_SPEC; /* Get the name of the variable to expand. The name may consist of an arbitrary number of VARNAMEs and VARIABLEs. */ do { rc = varname(p, end, nameclass); if (rc < 0) goto error_return; else if (rc > 0) { if (!append_to_tokenbuf(&name, p, rc)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } else p += rc; } rc = variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmp); if (rc < 0) goto error_return; else if (rc > 0) { if (!append_to_tokenbuf(&name, tmp.begin, tmp.end - tmp.begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } else p += rc; } } while (rc > 0); /* We must have the complete variable name now, so make sure we do. */ if (name.begin == name.end) { rc = VAR_INCOMPLETE_VARIABLE_SPEC; goto error_return; } /* Now we have the name of the variable stored in "name". We expect an ENDDELIM here. */ if (p == end || (*p != config->enddelim && *p != ':')) { rc = VAR_INCOMPLETE_VARIABLE_SPEC; goto error_return; } else ++p; /* Use the lookup callback to get the variable's contents. */ rc = (*lookup)(lookup_context, name.begin, name.end - name.begin, &data, &len, &buffer_size); if (rc < 0) goto error_return; else if (rc == 0) { /* The variable is undefined. What we'll do now depends on the force_expand flag. */ if (force_expand) { rc = VAR_UNDEFINED_VARIABLE; goto error_return; } else { /* Initialize result to point back to the original text in the buffer. */ result->begin = begin-1; result->end = p; result->buffer_size = 0; failed = 1; } } else { /* The preliminary result is the contents of the variable. This may be modified by the commands that may follow. */ result->begin = data; result->end = data + len; result->buffer_size = buffer_size; } if (p[-1] == ':') { /* Parse and execute commands. */ free_tokenbuf(&tmp); --p; while (p != end && *p == ':') { ++p; if (!failed) rc = command(p, end, config, nameclass, lookup, lookup_context, force_expand, result); else rc = command(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmp); if (rc < 0) goto error_return; p += rc; if (failed) result->end += rc; } if (p == end || *p != config->enddelim) { rc = VAR_INCOMPLETE_VARIABLE_SPEC; goto error_return; } ++p; if (failed) ++result->end; } /* Exit gracefully. */ free_tokenbuf(&name); free_tokenbuf(&tmp); return p - begin; /* Exit in case of an error. */ error_return: free_tokenbuf(&name); free_tokenbuf(&tmp); free_tokenbuf(result); return rc; } static int variable(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* result) { const char* p = begin; const char* data; size_t len, buffer_size; int rc, rc2; /* Clear the result tokenbuf to make sure we're in a defined state. */ init_tokenbuf(result); /* Expect VARINIT. */ if (p == end || *p != config->varinit) return 0; if (++p == end) return VAR_INCOMPLETE_VARIABLE_SPEC; /* Try to read the variable name. If that fails, we're parsing a complex expression. */ rc = varname(p, end, nameclass); if (rc < 0) return rc; else if (rc > 0) { rc2 = (*lookup)(lookup_context, p, rc, &data, &len, &buffer_size); if (rc2 < 0) return rc2; else if (rc2 == 0) { if (force_expand) return VAR_UNDEFINED_VARIABLE; else { result->begin = begin; result->end = begin + 1 + rc; result->buffer_size = 0; return 1 + rc; } } else { result->begin = data; result->end = data + len; result->buffer_size = buffer_size; return 1 + rc; } } /* OK, we're dealing with a complex expression here. */ rc = expression(p, end, config, nameclass, lookup, lookup_context, force_expand, result); if (rc > 0) ++rc; return rc; } static int exptext_or_variable(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* result) { const char* p = begin; tokenbuf tmp; int rc; init_tokenbuf(result); init_tokenbuf(&tmp); if (begin == end) return 0; do { rc = exptext(p, end, config); if (rc < 0) goto error_return; else if (rc > 0) { if (!append_to_tokenbuf(result, p, rc)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } else p += rc; } rc = variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmp); if (rc < 0) goto error_return; else if (rc > 0) { p += rc; if (!append_to_tokenbuf(result, tmp.begin, tmp.end - tmp.begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } } } while (rc > 0); free_tokenbuf(&tmp); return p - begin; error_return: free_tokenbuf(&tmp); free_tokenbuf(result); return rc; } static int substext_or_variable(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* result) { const char* p = begin; tokenbuf tmp; int rc; init_tokenbuf(result); init_tokenbuf(&tmp); if (begin == end) return 0; do { rc = substext(p, end, config); if (rc < 0) goto error_return; else if (rc > 0) { if (!append_to_tokenbuf(result, p, rc)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } else p += rc; } rc = variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmp); if (rc < 0) goto error_return; else if (rc > 0) { p += rc; if (!append_to_tokenbuf(result, tmp.begin, tmp.end - tmp.begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } } } while (rc > 0); free_tokenbuf(&tmp); return p - begin; error_return: free_tokenbuf(&tmp); free_tokenbuf(result); return rc; } static int expand_class_description(tokenbuf* src, tokenbuf* dst) { unsigned char c, d; const char* p = src->begin; while(p != src->end) { if ((src->end - p) >= 3 && p[1] == '-') { if (*p > p[2]) return VAR_INCORRECT_TRANSPOSE_CLASS_SPEC; for (c = *p, d = p[2]; c <= d; ++c) { if (!append_to_tokenbuf(dst, (char*)&c, 1)) return VAR_OUT_OF_MEMORY; } p += 3; } else { if (!append_to_tokenbuf(dst, p, 1)) return VAR_OUT_OF_MEMORY; else ++p; } } return VAR_OK; } static int transpose(tokenbuf* data, tokenbuf* search, tokenbuf* replace) { tokenbuf srcclass, dstclass; const char* p; int rc; size_t i; init_tokenbuf(&srcclass); init_tokenbuf(&dstclass); if ((rc = expand_class_description(search, &srcclass)) != VAR_OK) goto error_return; if ((rc = expand_class_description(replace, &dstclass)) != VAR_OK) goto error_return; if (srcclass.begin == srcclass.end) { rc = VAR_EMPTY_TRANSPOSE_CLASS; goto error_return; } if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) { rc = VAR_TRANSPOSE_CLASSES_MISMATCH; goto error_return; } if (data->buffer_size == 0) { tokenbuf tmp; if (!assign_to_tokenbuf(&tmp, data->begin, data->end - data->begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } move_tokenbuf(&tmp, data); } for (p = data->begin; p != data->end; ++p) { for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) { if (*p == srcclass.begin[i]) { *((char*)p) = dstclass.begin[i]; break; } } } free_tokenbuf(&srcclass); free_tokenbuf(&dstclass); return VAR_OK; error_return: free_tokenbuf(search); free_tokenbuf(replace); free_tokenbuf(&srcclass); free_tokenbuf(&dstclass); return rc; } static int cut_out_offset(tokenbuf* data, tokenbuf* number1, tokenbuf* number2, int isrange) { tokenbuf res; const char* p; size_t num1 = tokenbuf2int(number1); size_t num2 = tokenbuf2int(number2); /* Determine begin of result string. */ if ((data->end - data->begin) < num1) return VAR_OFFSET_OUT_OF_BOUNDS; else p = data->begin + num1; /* If num2 is zero, we copy the rest from there. */ if (num2 == 0) { if (!assign_to_tokenbuf(&res, p, data->end - p)) return VAR_OUT_OF_MEMORY; } else /* OK, then use num2. */ { if (isrange) { if ((p + num2) > data->end) return VAR_RANGE_OUT_OF_BOUNDS; if (!assign_to_tokenbuf(&res, p, num2)) return VAR_OUT_OF_MEMORY; } else { if (num2 < num1) return VAR_OFFSET_LOGIC_ERROR; if ((data->begin + num2) > data->end) return VAR_RANGE_OUT_OF_BOUNDS; if (!assign_to_tokenbuf(&res, p, (data->begin + num2) - p)) return VAR_OUT_OF_MEMORY; } } free_tokenbuf(data); move_tokenbuf(&res, data); return VAR_OK; } static int search_and_replace(tokenbuf* data, tokenbuf* search, tokenbuf* replace, tokenbuf* flags) { const char* p; int case_insensitive = 0; int global = 0; int no_regex = 0; int rc; if (search->begin == search->end) return VAR_EMPTY_SEARCH_STRING; printf("Search '%s' in '%s' and replace it with '%s'.\n", search->begin, data->begin, replace->begin); for (p = flags->begin; p != flags->end; ++p) { switch (tolower(*p)) { case 'i': case_insensitive = 1; printf("case_insensitive = 1;\n"); break; case 'g': global = 1; printf("global = 1;\n"); break; case 't': no_regex = 1; printf("no_regex = 1;\n"); break; default: return VAR_UNKNOWN_REPLACE_FLAG; } } if (no_regex) { tokenbuf tmp; init_tokenbuf(&tmp); for (p = data->begin; p != data->end; ) { if (case_insensitive) rc = strncasecmp(p, search->begin, search->end - search->begin); else rc = strncmp(p, search->begin, search->end - search->begin); if (rc != 0) { /* no match, copy character */ if (!append_to_tokenbuf(&tmp, p, 1)) { free_tokenbuf(&tmp); return VAR_OUT_OF_MEMORY; } ++p; } else { append_to_tokenbuf(&tmp, replace->begin, replace->end - replace->begin); p += search->end - search->begin; if (!global) { if (!append_to_tokenbuf(&tmp, p, data->end - p)) { free_tokenbuf(&tmp); return VAR_OUT_OF_MEMORY; } break; } } } free_tokenbuf(data); move_tokenbuf(&tmp, data); } else { tokenbuf tmp; tokenbuf mydata; regex_t preg; regmatch_t pmatch[33]; int regexec_flag; /* Copy the pattern and the data to our own buffer to make sure they're terminated with a null byte. */ if (!assign_to_tokenbuf(&tmp, search->begin, search->end - search->begin)) return VAR_OUT_OF_MEMORY; if (!assign_to_tokenbuf(&mydata, data->begin, data->end - data->begin)) { free_tokenbuf(&tmp); return VAR_OUT_OF_MEMORY; } /* Compile the pattern. */ printf("data is.................: '%s'\n", mydata.begin); printf("regex search pattern is.: '%s'\n", tmp.begin); printf("regex replace pattern is: '%s'\n", replace->begin); rc = regcomp(&preg, tmp.begin, REG_EXTENDED | ((case_insensitive) ? REG_ICASE : 0)); free_tokenbuf(&tmp); if (rc != 0) { free_tokenbuf(&mydata); return VAR_INVALID_REGEX_IN_REPLACE; } printf("Subexpression in pattern: '%d'\n", preg.re_nsub); /* Match the pattern and create the result string in the tmp buffer. */ for (p = mydata.begin; p != mydata.end; ) { if (p == mydata.begin || p[-1] == '\n') regexec_flag = 0; else regexec_flag = REG_NOTBOL; if (regexec(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch, regexec_flag) == REG_NOMATCH) { printf("No match; appending remainder ('%s') to output string.\n", p); append_to_tokenbuf(&tmp, p, mydata.end - p); break; } else { if (!append_to_tokenbuf(&tmp, p, pmatch[0].rm_so) || !append_to_tokenbuf(&tmp, replace->begin, replace->end - replace->begin)) { regfree(&preg); free_tokenbuf(&tmp); free_tokenbuf(&mydata); return VAR_OUT_OF_MEMORY; } else p += pmatch[0].rm_eo; if (!global) { append_to_tokenbuf(&tmp, p, mydata.end - p); break; } } } regfree(&preg); free_tokenbuf(data); move_tokenbuf(&tmp, data); free_tokenbuf(&mydata); } return VAR_OK; } static int padding(tokenbuf* data, tokenbuf* widthstr, tokenbuf* fill, char position) { tokenbuf result; size_t width = tokenbuf2int(widthstr); int i; if (fill->begin == fill->end) return VAR_EMPTY_PADDING_FILL_STRING; init_tokenbuf(&result); if (position == 'l') { i = width - (data->end - data->begin); if (i > 0) { i = i / (fill->end - fill->begin); while(i > 0) { if (!append_to_tokenbuf(data, fill->begin, fill->end - fill->begin)) return VAR_OUT_OF_MEMORY; --i; } i = (width - (data->end - data->begin)) % (fill->end - fill->begin); if (!append_to_tokenbuf(data, fill->begin, i)) return VAR_OUT_OF_MEMORY; } } else if (position == 'r') { i = width - (data->end - data->begin); if (i > 0) { i = i / (fill->end - fill->begin); while(i > 0) { if (!append_to_tokenbuf(&result, fill->begin, fill->end - fill->begin)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } --i; } i = (width - (data->end - data->begin)) % (fill->end - fill->begin); if (!append_to_tokenbuf(&result, fill->begin, i)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } if (!append_to_tokenbuf(&result, data->begin, data->end - data->begin)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } free_tokenbuf(data); move_tokenbuf(&result, data); } } else if (position == 'c') { i = (width - (data->end - data->begin)) / 2; if (i > 0) { /* Create the prefix. */ i = i / (fill->end - fill->begin); while(i > 0) { if (!append_to_tokenbuf(&result, fill->begin, fill->end - fill->begin)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } --i; } i = ((width - (data->end - data->begin)) / 2) % (fill->end - fill->begin); if (!append_to_tokenbuf(&result, fill->begin, i)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } /* Append the actual data string. */ if (!append_to_tokenbuf(&result, data->begin, data->end - data->begin)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } /* Append the suffix. */ i = width - (result.end - result.begin); i = i / (fill->end - fill->begin); while(i > 0) { if (!append_to_tokenbuf(&result, fill->begin, fill->end - fill->begin)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } --i; } i = width - (result.end - result.begin); if (!append_to_tokenbuf(&result, fill->begin, i)) { free_tokenbuf(&result); return VAR_OUT_OF_MEMORY; } /* Move string from temporary buffer to data buffer. */ free_tokenbuf(data); move_tokenbuf(&result, data); } } return VAR_OK; } static int command(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* data) { const char* p = begin; tokenbuf tmptokbuf; tokenbuf search, replace, flags; tokenbuf number1, number2; int isrange; int rc; init_tokenbuf(&tmptokbuf); init_tokenbuf(&search); init_tokenbuf(&replace); init_tokenbuf(&flags); init_tokenbuf(&number1); init_tokenbuf(&number2); if (begin == end) return 0; switch (tolower(*p)) { case 'l': /* Turn data to lowercase. */ if (data->begin) { char* ptr; /* If the buffer does not life in an allocated buffer, we have to copy it before modifying the contents. */ if (data->buffer_size == 0) { if (!assign_to_tokenbuf(data, data->begin, data->end - data->begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } } for (ptr = (char*)data->begin; ptr != data->end; ++ptr) *ptr = tolower(*ptr); } ++p; break; case 'u': /* Turn data to uppercase. */ if (data->begin) { char* ptr; if (data->buffer_size == 0) { if (!assign_to_tokenbuf(data, data->begin, data->end - data->begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } } for (ptr = (char*)data->begin; ptr != data->end; ++ptr) *ptr = toupper(*ptr); } ++p; break; case 'o': /* Cut out substrings. */ ++p; rc = number(p, end); if (rc == 0) { rc = VAR_MISSING_START_OFFSET; goto error_return; } else { number1.begin = p; number1.end = p + rc; number1.buffer_size = 0; p += rc; } if (*p == ',') { isrange = 0; ++p; } else if (*p == '-') { isrange = 1; ++p; } else { rc = VAR_INVALID_OFFSET_DELIMITER; goto error_return; } rc = number(p, end); number2.begin = p; number2.end = p + rc; number2.buffer_size = 0; p += rc; if (data->begin) { rc = cut_out_offset(data, &number1, &number2, isrange); if (rc < 0) goto error_return; } break; case '#': /* Substitute length of the string. */ if (data->begin) { char buf[1024]; sprintf(buf, "%d", data->end - data->begin); free_tokenbuf(data); if (!assign_to_tokenbuf(data, buf, strlen(buf))) { rc = VAR_OUT_OF_MEMORY; goto error_return; } } ++p; break; case '-': /* Substitute parameter if data is empty. */ ++p; rc = exptext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmptokbuf); if (rc < 0) goto error_return; else if (rc == 0) { rc = VAR_MISSING_PARAMETER_IN_COMMAND; goto error_return; } else p += rc; if (data->begin != NULL && data->begin == data->end) { free_tokenbuf(data); move_tokenbuf(&tmptokbuf, data); } break; case '*': /* Return "" if data is not empty, parameter otherwise. */ ++p; rc = exptext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmptokbuf); if (rc < 0) goto error_return; else if (rc == 0) { rc = VAR_MISSING_PARAMETER_IN_COMMAND; goto error_return; } else p += rc; if (data->begin != NULL) { if (data->begin == data->end) { free_tokenbuf(data); move_tokenbuf(&tmptokbuf, data); } else { free_tokenbuf(data); data->begin = data->end = ""; data->buffer_size = 0; } } break; case '+': /* Substitute parameter if data is not empty. */ ++p; rc = exptext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &tmptokbuf); if (rc < 0) goto error_return; else if (rc == 0) { rc = VAR_MISSING_PARAMETER_IN_COMMAND; goto error_return; } else p += rc; if (data->begin != NULL) { if (data->begin != data->end) { free_tokenbuf(data); move_tokenbuf(&tmptokbuf, data); } } break; case 's': /* Search and replace. */ ++p; if (*p != '/') return VAR_MALFORMATTED_REPLACE; else ++p; rc = substext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &search); if (rc < 0) goto error_return; else p += rc; if (*p != '/') { rc = VAR_MALFORMATTED_REPLACE; goto error_return; } else ++p; rc = substext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &replace); if (rc < 0) goto error_return; else p += rc; if (*p != '/') { rc = VAR_MALFORMATTED_REPLACE; goto error_return; } else ++p; rc = exptext(p, end, config); if (rc < 0) goto error_return; else { flags.begin = p; flags.end = p + rc; flags.buffer_size = 0; p += rc; } if (data->begin) { rc = search_and_replace(data, &search, &replace, &flags); if (rc < 0) goto error_return; } break; case 'y': /* Transpose characters from class A to class B. */ ++p; if (*p != '/') return VAR_MALFORMATTED_TRANSPOSE; else ++p; rc = substext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &search); if (rc < 0) goto error_return; else p += rc; if (*p != '/') { rc = VAR_MALFORMATTED_TRANSPOSE; goto error_return; } else ++p; rc = substext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &replace); if (rc < 0) goto error_return; else p += rc; if (*p != '/') { rc = VAR_MALFORMATTED_TRANSPOSE; goto error_return; } else ++p; if (data->begin) { rc = transpose(data, &search, &replace); if (rc < 0) goto error_return; } break; case 'p': /* Padding. */ ++p; if (*p != '/') return VAR_MALFORMATTED_PADDING; else ++p; rc = number(p, end); if (rc == 0) { rc = VAR_MISSING_PADDING_WIDTH; goto error_return; } else { number1.begin = p; number1.end = p + rc; number1.buffer_size = 0; p += rc; } if (*p != '/') { rc = VAR_MALFORMATTED_PADDING; goto error_return; } else ++p; rc = substext_or_variable(p, end, config, nameclass, lookup, lookup_context, force_expand, &replace); if (rc < 0) goto error_return; else p += rc; if (*p != '/') { rc = VAR_MALFORMATTED_PADDING; goto error_return; } else ++p; if (*p != 'l' && *p != 'c' && *p != 'r') { rc = VAR_MALFORMATTED_PADDING; goto error_return; } else ++p; if (data->begin) { rc = padding(data, &number1, &replace, p[-1]); if (rc < 0) goto error_return; } break; default: return VAR_UNKNOWN_COMMAND_CHAR; } /* Exit gracefully. */ free_tokenbuf(&tmptokbuf); free_tokenbuf(&search); free_tokenbuf(&replace); free_tokenbuf(&flags); free_tokenbuf(&number1); free_tokenbuf(&number2); return p - begin; error_return: free_tokenbuf(data); free_tokenbuf(&tmptokbuf); free_tokenbuf(&search); free_tokenbuf(&replace); free_tokenbuf(&flags); free_tokenbuf(&number1); free_tokenbuf(&number2); return rc; } static var_rc_t input(const char* begin, const char* end, const var_config_t* config, const char nameclass[256], var_cb_t lookup, void* lookup_context, int force_expand, tokenbuf* output) { int rc; tokenbuf result; init_tokenbuf(&result); do { rc = text(begin, end, config->varinit, config->escape); if (rc > 0) { if (!append_to_tokenbuf(output, begin, rc)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } begin += rc; } else if (rc < 0) goto error_return; rc = variable(begin, end, config, nameclass, lookup, lookup_context, force_expand, &result); if (rc > 0) { if (!append_to_tokenbuf(output, result.begin, result.end - result.begin)) { rc = VAR_OUT_OF_MEMORY; goto error_return; } else begin += rc; } else if (rc < 0) goto error_return; } while (rc > 0); if (begin != end) { rc = VAR_INPUT_ISNT_TEXT_NOR_VARIABLE; goto error_return; } return VAR_OK; error_return: free_tokenbuf(&result); return rc; } var_rc_t var_expand(const char* input_buf, size_t input_len, char** result, size_t* result_len, var_cb_t lookup, void* lookup_context, const var_config_t* config, int force_expand) { char nameclass[256]; var_rc_t rc; tokenbuf output; /* Expand the class description for valid variable names. */ if (config == NULL) config = &var_config_default; rc = expand_character_class(config->namechars, nameclass); if (rc != VAR_OK) return rc; /* Make sure that the specials defined in the configuration do not appear in the character name class. */ if (nameclass[(int)config->varinit] || nameclass[(int)config->startdelim] || nameclass[(int)config->enddelim] || nameclass[(int)config->escape]) return VAR_INVALID_CONFIGURATION; /* Call the parser. */ output.begin = output.end = NULL; output.buffer_size = 0; rc = input(input_buf, input_buf + input_len, config, nameclass, lookup, lookup_context, force_expand, &output); if (rc != VAR_OK) { free_tokenbuf(&output); return rc; } *result = (char*)output.begin; *result_len = output.end - output.begin; return VAR_OK; }