Skip to content
Snippets Groups Projects
func_strings.c 68.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		ast_log(LOG_ERROR, "No <find-string> specified\n");
    
    Jonathan Rose's avatar
    Jonathan Rose committed
    		return -1;
    	}
    	find_size = strlen(args.find_string);
    
    
    	/* set varsubstr to the matching variable */
    
    	varsubstr = ast_alloca(strlen(args.varname) + 4);
    
    	sprintf(varsubstr, "${%s}", args.varname);
    	ast_str_substitute_variables(&str, 0, chan, varsubstr);
    
    	/* Determine how many replacements are allowed. */
    	if (!args.max_replacements
    		|| (max_matches = atoi(args.max_replacements)) <= 0) {
    		/* Unlimited replacements are allowed. */
    		max_matches = -1;
    
    	/* Generate the search and replaced string. */
    	start = ast_str_buffer(str);
    	for (count = 0; count < max_matches; ++count) {
    		end = strstr(start, args.find_string);
    		if (!end) {
    			/* Did not find a matching substring in the remainder. */
    
    		/* Replace the found substring. */
    		*end = '\0';
    		ast_str_append(buf, len, "%s", start);
    		if (args.replace_string) {
    			/* Append the replacement string */
    			ast_str_append(buf, len, "%s", args.replace_string);
    		}
    		start = end + find_size;
    
    	ast_str_append(buf, len, "%s", start);
    
    Jonathan Rose's avatar
    Jonathan Rose committed
    
    	return 0;
    }
    
    static struct ast_custom_function strreplace_function = {
    	.name = "STRREPLACE",
    	.read2 = strreplace,
    };
    
    
    static int strbetween(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
    {
    	int c, origsize;
    	char *varsubstr, *origstr;
    	struct ast_str *str = ast_str_thread_get(&result_buf, 16); /* Holds the data obtained from varname */
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(varname);
    		AST_APP_ARG(insert_string);
    		AST_APP_ARG(other);	/* Any remining unused arguments */
    	);
    
    	ast_str_reset(*buf);
    
    	if (!str) {
    		ast_log(LOG_ERROR, "Couldn't obtain string\n");
    		return -1;
    	}
    
    	AST_STANDARD_APP_ARGS(args, data);
    
    	if (args.argc != 2 || ast_strlen_zero(args.varname)) {
    		ast_log(LOG_ERROR, "Usage: %s(<varname>,<insert-string>)\n", cmd);
    		return -1;
    	}
    
    	varsubstr = ast_alloca(strlen(args.varname) + 4);
    	sprintf(varsubstr, "${%s}", args.varname);
    	ast_str_substitute_variables(&str, 0, chan, varsubstr);
    	origstr = ast_str_buffer(str);
    	origsize = strlen(origstr);
    	for (c = 0; c < origsize; c++) {
    		ast_str_append(buf, len, "%c", origstr[c]);
    		/* no insert after the last character */
    		if (c < (origsize - 1)) {
    			ast_str_append(buf, len, "%s", args.insert_string);
    		}
    	}
    
    	return 0;
    }
    
    static struct ast_custom_function strbetween_function = {
    	.name = "STRBETWEEN",
    	.read2 = strbetween,
    };
    
    
    #define ltrim(s) while (isspace(*s)) s++;
    #define rtrim(s) { \
    	if (s) { \
    		char *back = s + strlen(s); \
    		while (back != s && isspace(*--back)); \
    		if (*s) { \
    			*(back + 1) = '\0'; \
    		} \
    	} \
    }
    
    static int function_trim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	char *c;
    
    	if (ast_strlen_zero(data)) {
    		return -1;
    	}
    
    	c = ast_strdupa(data);
    	ltrim(c);
    	rtrim(c);
    
    	ast_copy_string(buf, c, len);
    
    	return 0;
    }
    
    static int function_ltrim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	char *c;
    
    	if (ast_strlen_zero(data)) {
    		return -1;
    	}
    
    	c = data;
    	ltrim(c);
    
    	ast_copy_string(buf, c, len);
    
    	return 0;
    }
    
    static int function_rtrim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	char *c;
    
    	if (ast_strlen_zero(data)) {
    		return -1;
    	}
    
    	c = ast_strdupa(data);
    	rtrim(c);
    
    	ast_copy_string(buf, c, len);
    
    	return 0;
    }
    
    #undef ltrim
    #undef rtrim
    
    static struct ast_custom_function trim_function = {
    	.name = "TRIM",
    	.read = function_trim,
    };
    
    static struct ast_custom_function ltrim_function = {
    	.name = "LTRIM",
    	.read = function_ltrim,
    };
    
    static struct ast_custom_function rtrim_function = {
    	.name = "RTRIM",
    	.read = function_rtrim,
    };
    
    
    static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf,
    
    		 size_t len)
    
    	AST_DECLARE_APP_ARGS(args,
    
    			     AST_APP_ARG(null);
    			     AST_APP_ARG(reg);
    			     AST_APP_ARG(str);
    
    	int errcode;
    	regex_t regexbuf;
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    	buf[0] = '\0';
    
    	AST_NONSTANDARD_APP_ARGS(args, parse, '"');
    
    	if (args.argc != 3) {
    		ast_log(LOG_ERROR, "Unexpected arguments: should have been in the form '\"<regex>\" <string>'\n");
    		return -1;
    	}
    
    	if ((*args.str == ' ') || (*args.str == '\t'))
    		args.str++;
    
    	ast_debug(1, "FUNCTION REGEX (%s)(%s)\n", args.reg, args.str);
    
    
    	if ((errcode = regcomp(&regexbuf, args.reg, REG_EXTENDED | REG_NOSUB))) {
    
    		regerror(errcode, &regexbuf, buf, len);
    		ast_log(LOG_WARNING, "Malformed input %s(%s): %s\n", cmd, parse, buf);
    		return -1;
    
    	strcpy(buf, regexec(&regexbuf, args.str, 0, NULL, 0) ? "0" : "1");
    
    
    static struct ast_custom_function regex_function = {
    
    #define HASH_PREFIX	"~HASH~%s~"
    #define HASH_FORMAT	HASH_PREFIX "%s~"
    
    static char *app_clearhash = "ClearHash";
    
    /* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */
    static void clearvar_prefix(struct ast_channel *chan, const char *prefix)
    {
    	struct ast_var_t *var;
    	int len = strlen(prefix);
    
    	AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_varshead(chan), var, entries) {
    
    		if (strncmp(prefix, ast_var_name(var), len) == 0) {
    
    			AST_LIST_REMOVE_CURRENT(entries);
    
    static int exec_clearhash(struct ast_channel *chan, const char *data)
    
    {
    	char prefix[80];
    	snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null");
    	clearvar_prefix(chan, prefix);
    	return 0;
    }
    
    
    static int array(struct ast_channel *chan, const char *cmd, char *var,
    
    		 const char *value)
    
    	AST_DECLARE_APP_ARGS(arg1,
    
    			     AST_APP_ARG(var)[100];
    
    			     AST_APP_ARG(val)[100];
    
    	char *origvar = "", *value2, varname[256];
    	int i, ishash = 0;
    
    		return -1;
    
    	}
    	value2 = ast_strdupa(value);
    
    	if (!strcmp(cmd, "HASH")) {
    		const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~");
    		origvar = var;
    		if (var2)
    			var = ast_strdupa(var2);
    
    		else {
    			if (chan)
    				ast_autoservice_stop(chan);
    
    	/* The functions this will generally be used with are SORT and ODBC_*, which
    	 * both return comma-delimited lists.  However, if somebody uses literal lists,
    	 * their commas will be translated to vertical bars by the load, and I don't
    	 * want them to be surprised by the result.  Hence, we prefer commas as the
    	 * delimiter, but we'll fall back to vertical bars if commas aren't found.
    	 */
    
    	ast_debug(1, "array (%s=%s)\n", var, S_OR(value2, ""));
    
    	for (i = 0; i < arg1.argc; i++) {
    
    		ast_debug(1, "array set value (%s=%s)\n", arg1.var[i],
    
    				S_OR(arg2.val[i], ""));
    
    				if (origvar[0] == '_') {
    					if (origvar[1] == '_') {
    						snprintf(varname, sizeof(varname), "__" HASH_FORMAT, origvar + 2, arg1.var[i]);
    					} else {
    						snprintf(varname, sizeof(varname), "_" HASH_FORMAT, origvar + 1, arg1.var[i]);
    					}
    				} else {
    					snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
    				}
    
    
    				pbx_builtin_setvar_helper(chan, varname, arg2.val[i]);
    			} else {
    				pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
    			}
    
    		} else {
    			/* We could unset the variable, by passing a NULL, but due to
    			 * pushvar semantics, that could create some undesired behavior. */
    
    			if (ishash) {
    				snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
    				pbx_builtin_setvar_helper(chan, varname, "");
    			} else {
    				pbx_builtin_setvar_helper(chan, arg1.var[i], "");
    			}
    
    static const char *get_key(const struct ast_str *prefix, const struct ast_var_t *var)
    {
    	const char *prefix_name = ast_str_buffer(prefix);
    	const char *var_name = ast_var_name(var);
    	int prefix_len;
    	int var_len;
    
    	if (ast_strlen_zero(var_name)) {
    		return NULL;
    	}
    
    	prefix_len = ast_str_strlen(prefix);
    	var_len = strlen(var_name);
    
    	/*
    	 * Make sure we only match on non-empty, hash function created keys. If valid
    	 * then return a pointer to the variable that's just after the prefix.
    	 */
    	return var_len > (prefix_len + 1) && var_name[var_len - 1] == '~' &&
    		strncmp(prefix_name, var_name, prefix_len) == 0 ? var_name + prefix_len : NULL;
    }
    
    
    static int hashkeys_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    	struct ast_str *prefix = ast_str_alloca(80);
    
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    
    	ast_str_set(&prefix, -1, HASH_PREFIX, data);
    
    	AST_LIST_TRAVERSE(ast_channel_varshead(chan), newvar, entries) {
    
    		const char *key = get_key(prefix, newvar);
    
    		if (key) {
    			strncat(buf, key, len - strlen(buf) - 1);
    			/* Replace the trailing ~ */
    
    			buf[strlen(buf) - 1] = ',';
    		}
    	}
    	/* Trim the trailing comma */
    
    	buf_len = strlen(buf);
    	if (buf_len) {
    		buf[buf_len - 1] = '\0';
    	}
    
    static int hashkeys_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
    {
    	struct ast_var_t *newvar;
    	struct ast_str *prefix = ast_str_alloca(80);
    
    
    	if (!chan) {
    		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
    		return -1;
    	}
    
    
    	ast_str_set(&prefix, -1, HASH_PREFIX, data);
    
    
    	AST_LIST_TRAVERSE(ast_channel_varshead(chan), newvar, entries) {
    
    		const char *key = get_key(prefix, newvar);
    
    		if (key) {
    			char *tmp;
    
    			ast_str_append(buf, len, "%s", key);
    			/* Replace the trailing ~ */
    
    			tmp = ast_str_buffer(*buf);
    			tmp[ast_str_strlen(*buf) - 1] = ',';
    		}
    	}
    
    static int hash_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
    
    {
    	char varname[256];
    	AST_DECLARE_APP_ARGS(arg,
    		AST_APP_ARG(hashname);
    		AST_APP_ARG(hashkey);
    	);
    
    
    		/* Single argument version */
    		return array(chan, "HASH", var, value);
    	}
    
    	AST_STANDARD_APP_ARGS(arg, var);
    
    	if (arg.hashname[0] == '_') {
    		if (arg.hashname[1] == '_') {
    			snprintf(varname, sizeof(varname), "__" HASH_FORMAT, arg.hashname + 2, arg.hashkey);
    		} else {
    			snprintf(varname, sizeof(varname), "_" HASH_FORMAT, arg.hashname + 1, arg.hashkey);
    		}
    	} else {
    		snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
    	}
    
    	pbx_builtin_setvar_helper(chan, varname, value);
    
    	return 0;
    }
    
    
    static int hash_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    {
    	char varname[256];
    	const char *varvalue;
    	AST_DECLARE_APP_ARGS(arg,
    		AST_APP_ARG(hashname);
    		AST_APP_ARG(hashkey);
    	);
    
    	AST_STANDARD_APP_ARGS(arg, data);
    	if (arg.argc == 2) {
    		snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
    		varvalue = pbx_builtin_getvar_helper(chan, varname);
    		if (varvalue)
    			ast_copy_string(buf, varvalue, len);
    		else
    			*buf = '\0';
    	} else if (arg.argc == 1) {
    		char colnames[4096];
    		int i;
    		AST_DECLARE_APP_ARGS(arg2,
    			AST_APP_ARG(col)[100];
    		);
    
    
    		if (!chan) {
    			ast_log(LOG_WARNING, "No channel and only 1 parameter was provided to %s function.\n", cmd);
    			return -1;
    		}
    
    
    		/* Get column names, in no particular order */
    		hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames));
    		pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
    
    
    		*buf = '\0';
    
    		/* Now get the corresponding column values, in exactly the same order */
    		for (i = 0; i < arg2.argc; i++) {
    			snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]);
    			varvalue = pbx_builtin_getvar_helper(chan, varname);
    
    			strncat(buf, varvalue, len - strlen(buf) - 1);
    			strncat(buf, ",", len - strlen(buf) - 1);
    
    		}
    
    		/* Strip trailing comma */
    		buf[strlen(buf) - 1] = '\0';
    	}
    
    	return 0;
    }
    
    static struct ast_custom_function hash_function = {
    	.name = "HASH",
    	.write = hash_write,
    	.read = hash_read,
    };
    
    static struct ast_custom_function hashkeys_function = {
    	.name = "HASHKEYS",
    	.read = hashkeys_read,
    
    	.read2 = hashkeys_read2,
    
    static struct ast_custom_function array_function = {
    
    	.name = "ARRAY",
    
    static int quote(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    {
    	char *bufptr = buf, *dataptr = data;
    
    
    	if (len < 3){ /* at least two for quotes and one for binary zero */
    
    		ast_log(LOG_ERROR, "Not enough buffer\n");
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_WARNING, "No argument specified!\n");
    		ast_copy_string(buf, "\"\"", len);
    		return 0;
    	}
    
    
    	for (; bufptr < buf + len - 3; dataptr++) {
    
    		if (*dataptr == '\\') {
    			*bufptr++ = '\\';
    			*bufptr++ = '\\';
    		} else if (*dataptr == '"') {
    			*bufptr++ = '\\';
    			*bufptr++ = '"';
    		} else if (*dataptr == '\0') {
    			break;
    		} else {
    			*bufptr++ = *dataptr;
    		}
    	}
    	*bufptr++ = '"';
    	*bufptr = '\0';
    
    }
    
    static struct ast_custom_function quote_function = {
    	.name = "QUOTE",
    
    static int csv_quote(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	char *bufptr = buf, *dataptr = data;
    
    
    	if (len < 3) { /* at least two for quotes and one for binary zero */
    
    		ast_log(LOG_ERROR, "Not enough buffer\n");
    
    		return -1;
    	}
    
    	if (ast_strlen_zero(data)) {
    
    		return 0;
    	}
    
    	*bufptr++ = '"';
    	for (; bufptr < buf + len - 3; dataptr++){
    		if (*dataptr == '"') {
    			*bufptr++ = '"';
    			*bufptr++ = '"';
    		} else if (*dataptr == '\0') {
    			break;
    		} else {
    			*bufptr++ = *dataptr;
    		}
    	}
    	*bufptr++ = '"';
    	*bufptr='\0';
    	return 0;
    }
    
    static struct ast_custom_function csv_quote_function = {
    	.name = "CSV_QUOTE",
    	.read = csv_quote,
    };
    
    static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
    
    		length = strlen(data);
    
    	snprintf(buf, buflen, "%d", length);
    
    static struct ast_custom_function len_function = {
    
    	.read_max = 12,
    
    static int acf_strftime(struct ast_channel *chan, const char *cmd, char *parse,
    
    	AST_DECLARE_APP_ARGS(args,
    
    			     AST_APP_ARG(epoch);
    			     AST_APP_ARG(timezone);
    			     AST_APP_ARG(format);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	ast_get_timeval(args.epoch, &when, ast_tvnow(), NULL);
    	ast_localtime(&when, &tm, args.timezone);
    
    	if (ast_strftime(buf, buflen, args.format, &tm) <= 0)
    
    		ast_log(LOG_WARNING, "C function strftime() output nothing?!!\n");
    
    static struct ast_custom_function strftime_function = {
    
    static int acf_strptime(struct ast_channel *chan, const char *cmd, char *data,
    
    {
    	AST_DECLARE_APP_ARGS(args,
    
    			     AST_APP_ARG(timestring);
    			     AST_APP_ARG(timezone);
    			     AST_APP_ARG(format);
    
    	buf[0] = '\0';
    
    	if (!data) {
    
    		ast_log(LOG_ERROR,
    				"Asterisk function STRPTIME() requires an argument.\n");
    		return -1;
    
    	}
    
    	AST_STANDARD_APP_ARGS(args, data);
    
    
    	if (ast_strlen_zero(args.format)) {
    		ast_log(LOG_ERROR,
    
    				"No format supplied to STRPTIME(<timestring>,<timezone>,<format>)");
    
    		return -1;
    
    	if (!ast_strptime(args.timestring, args.format, &tm)) {
    		ast_log(LOG_WARNING, "STRPTIME() found no time specified within the string\n");
    
    		when = ast_mktime(&tm, args.timezone);
    
    		snprintf(buf, buflen, "%d", (int) when.tv_sec);
    
    static struct ast_custom_function strptime_function = {
    
    	.name = "STRPTIME",
    	.read = acf_strptime,
    };
    
    
    static int function_eval(struct ast_channel *chan, const char *cmd, char *data,
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "EVAL requires an argument: EVAL(<string>)\n");
    
    		return -1;
    
    	pbx_substitute_variables_helper(chan, data, buf, buflen - 1);
    
    static int function_eval2(struct ast_channel *chan, const char *cmd, char *data,
    			 struct ast_str **buf, ssize_t buflen)
    {
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_WARNING, "EVAL requires an argument: EVAL(<string>)\n");
    		return -1;
    	}
    
    	ast_str_substitute_variables(buf, buflen, chan, data);
    
    	return 0;
    }
    
    
    static struct ast_custom_function eval_function = {
    
    	.name = "EVAL",
    	.read = function_eval,
    
    	.read2 = function_eval2,
    
    static int keypadhash(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
    
    	for (bufptr = buf, dataptr = data; bufptr < buf + buflen - 1; dataptr++) {
    
    		if (*dataptr == '\0') {
    			*bufptr++ = '\0';
    			break;
    		} else if (*dataptr == '1') {
    
    			*bufptr++ = '1';
    		} else if (strchr("AaBbCc2", *dataptr)) {
    			*bufptr++ = '2';
    		} else if (strchr("DdEeFf3", *dataptr)) {
    			*bufptr++ = '3';
    		} else if (strchr("GgHhIi4", *dataptr)) {
    			*bufptr++ = '4';
    		} else if (strchr("JjKkLl5", *dataptr)) {
    			*bufptr++ = '5';
    		} else if (strchr("MmNnOo6", *dataptr)) {
    			*bufptr++ = '6';
    		} else if (strchr("PpQqRrSs7", *dataptr)) {
    			*bufptr++ = '7';
    		} else if (strchr("TtUuVv8", *dataptr)) {
    			*bufptr++ = '8';
    		} else if (strchr("WwXxYyZz9", *dataptr)) {
    			*bufptr++ = '9';
    		} else if (*dataptr == '0') {
    			*bufptr++ = '0';
    		}
    	}
    
    
    	return 0;
    }
    
    static struct ast_custom_function keypadhash_function = {
    	.name = "KEYPADHASH",
    	.read = keypadhash,
    };
    
    
    static int string_toupper(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
    
    	while ((bufptr < buf + buflen - 1) && (*bufptr++ = toupper(*dataptr++)));
    
    static int string_toupper2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t buflen)
    {
    	char *bufptr, *dataptr = data;
    
    	if (buflen > -1) {
    		ast_str_make_space(buf, buflen > 0 ? buflen : strlen(data) + 1);
    	}
    	bufptr = ast_str_buffer(*buf);
    	while ((bufptr < ast_str_buffer(*buf) + ast_str_size(*buf) - 1) && (*bufptr++ = toupper(*dataptr++)));
    	ast_str_update(*buf);
    
    	return 0;
    }
    
    
    static struct ast_custom_function toupper_function = {
    	.name = "TOUPPER",
    	.read = string_toupper,
    
    	.read2 = string_toupper2,
    
    static int string_tolower(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
    
    	while ((bufptr < buf + buflen - 1) && (*bufptr++ = tolower(*dataptr++)));
    
    static int string_tolower2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t buflen)
    {
    	char *bufptr, *dataptr = data;
    
    	if (buflen > -1) {
    		ast_str_make_space(buf, buflen > 0 ? buflen : strlen(data) + 1);
    	}
    	bufptr = ast_str_buffer(*buf);
    	while ((bufptr < ast_str_buffer(*buf) + ast_str_size(*buf) - 1) && (*bufptr++ = tolower(*dataptr++)));
    	ast_str_update(*buf);
    
    	return 0;
    }
    
    
    static struct ast_custom_function tolower_function = {
    	.name = "TOLOWER",
    	.read = string_tolower,
    
    	.read2 = string_tolower2,
    
    static int shift_pop(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
    
    #define beginning	(cmd[0] == 'S') /* SHIFT */
    	char *after, delimiter[2] = ",", *varsubst;
    	size_t unused;
    	struct ast_str *before = ast_str_thread_get(&result_buf, 16);
    	char *(*search_func)(const char *s, int c) = (beginning ? strchr : strrchr);
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(var);
    		AST_APP_ARG(delimiter);
    	);
    
    
    
    	if (ast_strlen_zero(args.var)) {
    
    		ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
    
    	varsubst = ast_alloca(strlen(args.var) + 4);
    
    	sprintf(varsubst, "${%s}", args.var);
    	ast_str_substitute_variables(&before, 0, chan, varsubst);
    
    	if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
    		ast_get_encoded_char(args.delimiter, delimiter, &unused);
    
    	if (!ast_str_strlen(before)) {
    		/* Nothing to pop */
    		return -1;
    	}
    
    	if (!(after = search_func(ast_str_buffer(before), delimiter[0]))) {
    		/* Only one entry in array */
    		ast_str_set(buf, len, "%s", ast_str_buffer(before));
    
    		pbx_builtin_setvar_helper(chan, args.var, "");
    	} else {
    		*after++ = '\0';
    
    		ast_str_set(buf, len, "%s", beginning ? ast_str_buffer(before) : after);
    		pbx_builtin_setvar_helper(chan, args.var, beginning ? after : ast_str_buffer(before));
    
    }
    
    static struct ast_custom_function shift_function = {
    	.name = "SHIFT",
    
    };
    
    static struct ast_custom_function pop_function = {
    	.name = "POP",
    
    static int unshift_push(struct ast_channel *chan, const char *cmd, char *data, const char *new_value)
    
    #define beginning	(cmd[0] == 'U') /* UNSHIFT */
    	char delimiter[2] = ",", *varsubst;
    	size_t unused;
    	struct ast_str *buf, *previous_value;
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(var);
    		AST_APP_ARG(delimiter);
    	);
    
    	if (!(buf = ast_str_thread_get(&result_buf, 16)) ||
    		!(previous_value = ast_str_thread_get(&tmp_buf, 16))) {
    
    	if (ast_strlen_zero(args.var)) {
    		ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
    
    	if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
    		ast_get_encoded_char(args.delimiter, delimiter, &unused);
    
    	/* UNSHIFT and PUSH act as ways of setting a variable, so we need to be
    	 * sure to skip leading underscores if they appear. However, we only want
    	 * to skip up to two since that is the maximum number that can be used to
    	 * indicate variable inheritance. Any further underscores are part of the
    	 * variable name.
    	 */
    	stripped_var = args.var + MIN(strspn(args.var, "_"), 2);
    	varsubst = ast_alloca(strlen(stripped_var) + 4);
    	sprintf(varsubst, "${%s}", stripped_var);
    
    	ast_str_substitute_variables(&previous_value, 0, chan, varsubst);
    
    	if (!ast_str_strlen(previous_value)) {
    		ast_str_set(&buf, 0, "%s", new_value);
    
    		ast_str_set(&buf, 0, "%s%c%s",
    			beginning ? new_value : ast_str_buffer(previous_value),
    			delimiter[0],
    			beginning ? ast_str_buffer(previous_value) : new_value);
    
    	}
    
    	pbx_builtin_setvar_helper(chan, args.var, ast_str_buffer(buf));
    
    	return 0;
    
    }
    
    static struct ast_custom_function push_function = {
    	.name = "PUSH",
    
    static struct ast_custom_function unshift_function = {
    	.name = "UNSHIFT",
    	.write = unshift_push,
    };
    
    static int passthru(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
    
    static struct ast_custom_function passthru_function = {
    	.name = "PASSTHRU",
    	.read2 = passthru,
    
    #ifdef TEST_FRAMEWORK
    
    AST_TEST_DEFINE(test_FIELDNUM)
    {
    	int i, res = AST_TEST_PASS;
    	struct ast_channel *chan;
    	struct ast_str *str;
    	char expression[256];
    	struct {
    		const char *fields;
    		const char *delim;
    		const char *field;
    		const char *expected;
    	} test_args[] = {
    		{"abc,def,ghi,jkl", "\\,",     "ghi", "3"},
    		{"abc def ghi jkl", " ",       "abc", "1"},
    		{"abc/def/ghi/jkl", "\\\\x2f", "def", "2"},
    		{"abc$def$ghi$jkl", "",        "ghi", "0"},
    		{"abc,def,ghi,jkl", "-",       "",    "0"},
    		{"abc-def-ghi-jkl", "-",       "mno", "0"}
    	};
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "func_FIELDNUM_test";
    		info->category = "/funcs/func_strings/";
    		info->summary = "Test FIELDNUM function";
    		info->description = "Verify FIELDNUM behavior";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(chan = ast_dummy_channel_alloc())) {
    		ast_test_status_update(test, "Unable to allocate dummy channel\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (!(str = ast_str_create(16))) {
    		ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
    		ast_channel_release(chan);
    		return AST_TEST_FAIL;
    	}
    
    	for (i = 0; i < ARRAY_LEN(test_args); i++) {
    		struct ast_var_t *var = ast_var_assign("FIELDS", test_args[i].fields);
    
    		if (!var) {
    			ast_test_status_update(test, "Out of memory\n");
    			res = AST_TEST_FAIL;
    			break;
    		}
    
    
    		AST_LIST_INSERT_HEAD(ast_channel_varshead(chan), var, entries);
    
    
    		snprintf(expression, sizeof(expression), "${FIELDNUM(%s,%s,%s)}", var->name, test_args[i].delim, test_args[i].field);
    		ast_str_substitute_variables(&str, 0, chan, expression);
    
    
    		AST_LIST_REMOVE(ast_channel_varshead(chan), var, entries);
    
    		ast_var_delete(var);
    
    		if (strcasecmp(ast_str_buffer(str), test_args[i].expected)) {
    			ast_test_status_update(test, "Evaluation of '%s' returned '%s' instead of the expected value '%s'\n",
    				expression, ast_str_buffer(str), test_args[i].expected);
    			res = AST_TEST_FAIL;
    			break;
    		}
    	}
    
    	ast_free(str);
    	ast_channel_release(chan);
    
    	return res;
    }