/* * Copyright (c) 2005 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: strexpmac.c,v 1.3 2005/07/05 17:20:49 ca Exp $") #include "sm/assert.h" #include "sm/magic.h" #include "sm/ctype.h" #include "sm/str-int.h" #include "sm/strexp.h" /* ** SM_FIND_MACRO -- Find macro in array macros ** ** Parameters: ** str -- str to expand (read) ** mac_begin -- begin of macro in str ** mac_end -- end of macro in str ** pidx -- (pointer to) index in macros if found (otput) ** nmacros -- number of valid elements in macros/repl ** macros -- array of macros ** ** Returns: ** usual sm_error code */ sm_ret_T sm_find_macro(const sm_str_P src, uint len, uint mac_begin, uint mac_end, uint *pidx, uint nmacros, sm_str_P *macros) { uint i, j, k, l; bool match; if (mac_begin >= mac_end || mac_begin >= len || mac_end > len) return sm_err_perm(EINVAL); SM_REQUIRE(pidx != NULL); SM_REQUIRE(macros != NULL); for (i = 0; i < nmacros; i++) { if (macros[i] == NULL) continue; l = sm_str_getlen(macros[i]); if (l != mac_end - mac_begin + 1) continue; match = true; for (k = 0, j = mac_begin; match && k < l && j <= mac_end; k++, j++) { match = sm_str_rd_elem(src, j) == sm_str_rd_elem(macros[i], k); } if (match && k == l && j == mac_end + 1) { *pidx = i; return SM_SUCCESS; } } return sm_err_perm(SM_E_NOTFOUND); } /* ** SM_STR_EXPMAC -- Expand ${macro} in string with repl[macro] ** \$ will be replaced by $ to suppress expansion of ${macro} ** ** Parameters: ** src -- str to expand (read) ** dst -- str into which the expanded sequence is written (output) ** flags -- flags (currently unused) ** macros -- array of macros ** repl -- array of replacement strings to use for expansion ** ** Returns: ** ==0: no macro in src, NOTHING written to dst! ** >=0: number of replacements ** <0: usual sm_error code ** ** Change API to use a callback to get a string for a macro? */ sm_ret_T sm_str_expmac(const sm_str_P src, sm_str_P dst, uint flags, uint nmacros, sm_str_P *macros, sm_str_P *repl) { uint i, len, idx, replacements, mac_begin, mac_end; uchar ch; sm_ret_T ret; bool backslash, found; SM_IS_BUF(dst); SM_IS_BUF(src); if (nmacros == 0) return 0; SM_REQUIRE(macros != NULL); SM_REQUIRE(repl != NULL); #define MACPUTCH(ch) \ do { \ ret = sm_str_put(dst, (ch)); \ if (sm_is_err(ret)) \ goto error; \ } while (0) len = sm_str_getlen(src); backslash = false; found = false; for (i = 0; i < len; i++) { ch = sm_str_rd_elem(src, i); /* need at least ${A} */ if (!backslash && ch == '$' && i + 3 < len && (ch = sm_str_rd_elem(src, i + 1)) == '{') { found = true; break; } if (!backslash) backslash = ch == '\\'; else { /* \$ will be replaced */ if (ch == '$') { found = true; break; } backslash = false; } } if (!found) return 0; replacements = 0; backslash = false; for (i = 0; i < len; i++) { ch = sm_str_rd_elem(src, i); /* need at least ${A} */ if (!backslash && ch == '$' && i + 3 < len && (ch = sm_str_rd_elem(src, i + 1)) == '{') { i += 2; mac_begin = i; for (; i < len && (ch = sm_str_rd_elem(src, i)) != '}'; i++) ; if (i < len && ch == '}') { mac_end = i - 1; ret = sm_find_macro(src, len, mac_begin, mac_end, &idx, nmacros, macros); ++replacements; /* on error: replace with empty string */ /* make this an option to preserve string?? */ if (sm_is_err(ret)) continue; if (idx >= nmacros || repl[idx] == NULL) continue; ret = sm_str_cat(dst, repl[idx]); if (sm_is_err(ret)) goto error; } else { /* copy "skipped" text over */ MACPUTCH('$'); MACPUTCH('{'); for (idx = mac_begin; idx < i; idx++) { ch = sm_str_rd_elem(src, idx); MACPUTCH(ch); } } } else { if (!backslash) { backslash = ch == '\\'; if (backslash) { ++replacements; continue; } } else { backslash = false; if (ch != '$') MACPUTCH('\\'); else ++replacements; } ret = sm_str_put(dst, ch); if (sm_is_err(ret)) goto error; } } if (backslash) MACPUTCH('\\'); if (replacements > (uint)SM_RET_MAX) replacements = SM_RET_MAX; return (sm_ret_T) replacements; error: /* cleanup? */ return ret; }