2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2005-2006, Digium, Inc.
5 * Portions Copyright (C) 2005, Tilghman Lesher. All rights reserved.
6 * Portions Copyright (C) 2005, Anthony Minessale II
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief String manipulation dialplan functions
23 * \author Tilghman Lesher
24 * \author Anothony Minessale II
29 <support_level>core</support_level>
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39 #include "asterisk/module.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/app.h"
44 #include "asterisk/localtime.h"
45 #include "asterisk/test.h"
47 AST_THREADSTORAGE(result_buf);
48 AST_THREADSTORAGE(tmp_buf);
51 <function name="FIELDQTY" language="en_US">
53 Count the fields with an arbitrary delimiter
56 <parameter name="varname" required="true" />
57 <parameter name="delim" required="true" />
60 <para>The delimiter may be specified as a special or extended ASCII character, by encoding it. The characters
61 <literal>\n</literal>, <literal>\r</literal>, and <literal>\t</literal> are all recognized as the newline,
62 carriage return, and tab characters, respectively. Also, octal and hexadecimal specifications are recognized
63 by the patterns <literal>\0nnn</literal> and <literal>\xHH</literal>, respectively. For example, if you wanted
64 to encode a comma as the delimiter, you could use either <literal>\054</literal> or <literal>\x2C</literal>.</para>
65 <para>Example: If ${example} contains <literal>ex-amp-le</literal>, then ${FIELDQTY(example,-)} returns 3.</para>
68 <function name="FIELDNUM" language="en_US">
70 Return the 1-based offset of a field in a list
73 <parameter name="varname" required="true" />
74 <parameter name="delim" required="true" />
75 <parameter name="value" required="true" />
78 <para>Search the variable named <replaceable>varname</replaceable> for the string <replaceable>value</replaceable>
79 delimited by <replaceable>delim</replaceable> and return a 1-based offset as to its location. If not found
80 or an error occured, return <literal>0</literal>.</para>
81 <para>The delimiter may be specified as a special or extended ASCII character, by encoding it. The characters
82 <literal>\n</literal>, <literal>\r</literal>, and <literal>\t</literal> are all recognized as the newline,
83 carriage return, and tab characters, respectively. Also, octal and hexadecimal specifications are recognized
84 by the patterns <literal>\0nnn</literal> and <literal>\xHH</literal>, respectively. For example, if you wanted
85 to encode a comma as the delimiter, you could use either <literal>\054</literal> or <literal>\x2C</literal>.</para>
86 <para>Example: If ${example} contains <literal>ex-amp-le</literal>, then ${FIELDNUM(example,-,amp)} returns 2.</para>
89 <function name="LISTFILTER" language="en_US">
90 <synopsis>Remove an item from a list, by name.</synopsis>
92 <parameter name="varname" required="true" />
93 <parameter name="delim" required="true" default="," />
94 <parameter name="value" required="true" />
97 <para>Remove <replaceable>value</replaceable> from the list contained in the <replaceable>varname</replaceable>
98 variable, where the list delimiter is specified by the <replaceable>delim</replaceable> parameter. This is
99 very useful for removing a single channel name from a list of channels, for example.</para>
102 <function name="FILTER" language="en_US">
104 Filter the string to include only the allowed characters
107 <parameter name="allowed-chars" required="true" />
108 <parameter name="string" required="true" />
111 <para>Permits all characters listed in <replaceable>allowed-chars</replaceable>,
112 filtering all others outs. In addition to literally listing the characters,
113 you may also use ranges of characters (delimited by a <literal>-</literal></para>
114 <para>Hexadecimal characters started with a <literal>\x</literal>(i.e. \x20)</para>
115 <para>Octal characters started with a <literal>\0</literal> (i.e. \040)</para>
116 <para>Also <literal>\t</literal>,<literal>\n</literal> and <literal>\r</literal> are recognized.</para>
117 <note><para>If you want the <literal>-</literal> character it needs to be prefixed with a
118 <literal>\</literal></para></note>
121 <function name="REPLACE" language="en_US">
123 Replace a set of characters in a given string with another character.
126 <parameter name="varname" required="true" />
127 <parameter name="find-chars" required="true" />
128 <parameter name="replace-char" required="false" />
131 <para>Iterates through a string replacing all the <replaceable>find-chars</replaceable> with
132 <replaceable>replace-char</replaceable>. <replaceable>replace-char</replaceable> may be either
133 empty or contain one character. If empty, all <replaceable>find-chars</replaceable> will be
134 deleted from the output.</para>
135 <note><para>The replacement only occurs in the output. The original variable is not
136 altered.</para></note>
139 <function name="STRREPLACE" language="en_US">
141 Replace instances of a substring within a string with another string.
144 <parameter name="varname" required="true" />
145 <parameter name="find-string" required="true" />
146 <parameter name="replace-string" required="false" />
147 <parameter name="max-replacements" required="false" />
150 <para>Searches for all instances of the <replaceable>find-string</replaceable> in provided variable and
151 replaces them with <replaceable>replace-string</replaceable>. If <replaceable>replace-string</replaceable>
152 is an empty string, this will effecively delete that substring. If <replaceable>max-replacements</replaceable>
153 is specified, this function will stop after performing replacements <replaceable>max-replacements</replaceable> times.</para>
154 <note><para>The replacement only occurs in the output. The original variable is not altered.</para></note>
157 <function name="PASSTHRU" language="en_US">
159 Pass the given argument back as a value.
162 <parameter name="string" required="false" />
165 <para>Literally returns the given <replaceable>string</replaceable>. The intent is to permit
166 other dialplan functions which take a variable name as an argument to be able to take a literal
167 string, instead.</para>
170 <function name="REGEX" language="en_US">
172 Check string against a regular expression.
175 <parameter name=""regular expression"" required="true" />
176 <parameter name="string" required="true" />
179 <para>Return <literal>1</literal> on regular expression match or <literal>0</literal> otherwise</para>
180 <para>Please note that the space following the double quotes separating the
181 regex from the data is optional and if present, is skipped. If a space is
182 desired at the beginning of the data, then put two spaces there; the second
183 will not be skipped.</para>
186 <application name="ClearHash" language="en_US">
188 Clear the keys from a specified hashname.
191 <parameter name="hashname" required="true" />
194 <para>Clears all keys out of the specified <replaceable>hashname</replaceable>.</para>
197 <function name="HASH" language="en_US">
199 Implementation of a dialplan associative array
202 <parameter name="hashname" required="true" />
203 <parameter name="hashkey" />
206 <para>In two arguments mode, gets and sets values to corresponding keys within
207 a named associative array. The single-argument mode will only work when assigned
208 to from a function defined by func_odbc</para>
211 <function name="HASHKEYS" language="en_US">
213 Retrieve the keys of the HASH() function.
216 <parameter name="hashname" required="true" />
219 <para>Returns a comma-delimited list of the current keys of the associative array
220 defined by the HASH() function. Note that if you iterate over the keys of
221 the result, adding keys during iteration will cause the result of the HASHKEYS()
222 function to change.</para>
225 <function name="KEYPADHASH" language="en_US">
227 Hash the letters in string into equivalent keypad numbers.
230 <parameter name="string" required="true" />
233 <para>Example: ${KEYPADHASH(Les)} returns "537"</para>
236 <function name="ARRAY" language="en_US">
238 Allows setting multiple variables at once.
241 <parameter name="var1" required="true" />
242 <parameter name="var2" required="false" multiple="true" />
243 <parameter name="varN" required="false" />
246 <para>The comma-delimited list passed as a value to which the function is set will
247 be interpreted as a set of values to which the comma-delimited list of
248 variable names in the argument should be set.</para>
249 <para>Example: Set(ARRAY(var1,var2)=1,2) will set var1 to 1 and var2 to 2</para>
252 <function name="STRPTIME" language="en_US">
254 Returns the epoch of the arbitrary date/time string structured as described by the format.
257 <parameter name="datetime" required="true" />
258 <parameter name="timezone" required="true" />
259 <parameter name="format" required="true" />
262 <para>This is useful for converting a date into <literal>EPOCH</literal> time,
263 possibly to pass to an application like SayUnixTime or to calculate the difference
264 between the two date strings</para>
265 <para>Example: ${STRPTIME(2006-03-01 07:30:35,America/Chicago,%Y-%m-%d %H:%M:%S)} returns 1141219835</para>
268 <function name="STRFTIME" language="en_US">
270 Returns the current date/time in the specified format.
273 <parameter name="epoch" />
274 <parameter name="timezone" />
275 <parameter name="format" />
278 <para>STRFTIME supports all of the same formats as the underlying C function
279 <emphasis>strftime(3)</emphasis>.
280 It also supports the following format: <literal>%[n]q</literal> - fractions of a second,
281 with leading zeros.</para>
282 <para>Example: <literal>%3q</literal> will give milliseconds and <literal>%1q</literal>
283 will give tenths of a second. The default is set at milliseconds (n=3).
284 The common case is to use it in combination with %S, as in <literal>%S.%3q</literal>.</para>
287 <ref type="manpage">strftime(3)</ref>
290 <function name="EVAL" language="en_US">
292 Evaluate stored variables
295 <parameter name="variable" required="true" />
298 <para>Using EVAL basically causes a string to be evaluated twice.
299 When a variable or expression is in the dialplan, it will be
300 evaluated at runtime. However, if the results of the evaluation
301 is in fact another variable or expression, using EVAL will have it
302 evaluated a second time.</para>
303 <para>Example: If the <variable>MYVAR</variable> contains
304 <variable>OTHERVAR</variable>, then the result of ${EVAL(
305 <variable>MYVAR</variable>)} in the dialplan will be the
306 contents of <variable>OTHERVAR</variable>. Normally just
307 putting <variable>MYVAR</variable> in the dialplan the result
308 would be <variable>OTHERVAR</variable>.</para>
311 <function name="TOUPPER" language="en_US">
313 Convert string to all uppercase letters.
316 <parameter name="string" required="true" />
319 <para>Example: ${TOUPPER(Example)} returns "EXAMPLE"</para>
322 <function name="TOLOWER" language="en_US">
324 Convert string to all lowercase letters.
327 <parameter name="string" required="true" />
330 <para>Example: ${TOLOWER(Example)} returns "example"</para>
333 <function name="LEN" language="en_US">
335 Return the length of the string given.
338 <parameter name="string" required="true" />
341 <para>Example: ${LEN(example)} returns 7</para>
344 <function name="QUOTE" language="en_US">
346 Quotes a given string, escaping embedded quotes as necessary
349 <parameter name="string" required="true" />
352 <para>Example: ${QUOTE(ab"c"de)} will return "abcde"</para>
355 <function name="CSV_QUOTE" language="en_US">
357 Quotes a given string for use in a CSV file, escaping embedded quotes as necessary
360 <parameter name="string" required="true" />
363 <para>Example: ${CSV_QUOTE("a,b" 123)} will return """a,b"" 123"</para>
366 <function name="SHIFT" language="en_US">
368 Removes and returns the first item off of a variable containing delimited text
371 <parameter name="varname" required="true" />
372 <parameter name="delimiter" required="false" default="," />
375 <para>Example:</para>
376 <para>exten => s,1,Set(array=one,two,three)</para>
377 <para>exten => s,n,While($["${SET(var=${SHIFT(array)})}" != ""])</para>
378 <para>exten => s,n,NoOp(var is ${var})</para>
379 <para>exten => s,n,EndWhile</para>
380 <para>This would iterate over each value in array, left to right, and
381 would result in NoOp(var is one), NoOp(var is two), and
382 NoOp(var is three) being executed.
386 <function name="POP" language="en_US">
388 Removes and returns the last item off of a variable containing delimited text
391 <parameter name="varname" required="true" />
392 <parameter name="delimiter" required="false" default="," />
395 <para>Example:</para>
396 <para>exten => s,1,Set(array=one,two,three)</para>
397 <para>exten => s,n,While($["${SET(var=${POP(array)})}" != ""])</para>
398 <para>exten => s,n,NoOp(var is ${var})</para>
399 <para>exten => s,n,EndWhile</para>
400 <para>This would iterate over each value in array, right to left, and
401 would result in NoOp(var is three), NoOp(var is two), and
402 NoOp(var is one) being executed.
406 <function name="PUSH" language="en_US">
408 Appends one or more values to the end of a variable containing delimited text
411 <parameter name="varname" required="true" />
412 <parameter name="delimiter" required="false" default="," />
415 <para>Example: Set(PUSH(array)=one,two,three) would append one,
416 two, and three to the end of the values stored in the variable
421 <function name="UNSHIFT" language="en_US">
423 Inserts one or more values to the beginning of a variable containing delimited text
426 <parameter name="varname" required="true" />
427 <parameter name="delimiter" required="false" default="," />
430 <para>Example: Set(UNSHIFT(array)=one,two,three) would insert one,
431 two, and three before the values stored in the variable
438 static int function_fieldqty_helper(struct ast_channel *chan, const char *cmd,
439 char *parse, char *buf, struct ast_str **sbuf, ssize_t len)
442 struct ast_str *str = ast_str_thread_get(&result_buf, 16);
444 AST_DECLARE_APP_ARGS(args,
445 AST_APP_ARG(varname);
455 AST_STANDARD_APP_ARGS(args, parse);
457 ast_get_encoded_char(args.delim, delim, &delim_used);
459 varsubst = alloca(strlen(args.varname) + 4);
461 sprintf(varsubst, "${%s}", args.varname);
462 ast_str_substitute_variables(&str, 0, chan, varsubst);
463 if (ast_str_strlen(str) == 0) {
466 char *varval = ast_str_buffer(str);
467 while (strsep(&varval, delim)) {
475 ast_str_set(sbuf, len, "%d", fieldcount);
477 snprintf(buf, len, "%d", fieldcount);
483 static int function_fieldqty(struct ast_channel *chan, const char *cmd,
484 char *parse, char *buf, size_t len)
486 return function_fieldqty_helper(chan, cmd, parse, buf, NULL, len);
489 static int function_fieldqty_str(struct ast_channel *chan, const char *cmd,
490 char *parse, struct ast_str **buf, ssize_t len)
492 return function_fieldqty_helper(chan, cmd, parse, NULL, buf, len);
495 static struct ast_custom_function fieldqty_function = {
497 .read = function_fieldqty,
498 .read2 = function_fieldqty_str,
501 static int function_fieldnum_helper(struct ast_channel *chan, const char *cmd,
502 char *parse, char *buf, struct ast_str **sbuf, ssize_t len)
504 char *varsubst, *field;
505 struct ast_str *str = ast_str_thread_get(&result_buf, 16);
506 int fieldindex = 0, res = 0;
507 AST_DECLARE_APP_ARGS(args,
508 AST_APP_ARG(varname);
519 AST_STANDARD_APP_ARGS(args, parse);
522 ast_log(LOG_ERROR, "Usage: FIELDNUM(<listname>,<delimiter>,<fieldvalue>)\n");
525 varsubst = alloca(strlen(args.varname) + 4);
526 sprintf(varsubst, "${%s}", args.varname);
528 ast_str_substitute_variables(&str, 0, chan, varsubst);
530 if (ast_str_strlen(str) == 0 || ast_strlen_zero(args.delim)) {
532 } else if (ast_get_encoded_char(args.delim, delim, &delim_used) == -1) {
535 char *varval = ast_str_buffer(str);
537 while ((field = strsep(&varval, delim)) != NULL) {
540 if (!strcasecmp(field, args.field)) {
554 ast_str_set(sbuf, len, "%d", fieldindex);
556 snprintf(buf, len, "%d", fieldindex);
562 static int function_fieldnum(struct ast_channel *chan, const char *cmd,
563 char *parse, char *buf, size_t len)
565 return function_fieldnum_helper(chan, cmd, parse, buf, NULL, len);
568 static int function_fieldnum_str(struct ast_channel *chan, const char *cmd,
569 char *parse, struct ast_str **buf, ssize_t len)
571 return function_fieldnum_helper(chan, cmd, parse, NULL, buf, len);
574 static struct ast_custom_function fieldnum_function = {
576 .read = function_fieldnum,
577 .read2 = function_fieldnum_str,
580 static int listfilter(struct ast_channel *chan, const char *cmd, char *parse, char *buf, struct ast_str **bufstr, ssize_t len)
582 AST_DECLARE_APP_ARGS(args,
583 AST_APP_ARG(listname);
584 AST_APP_ARG(delimiter);
585 AST_APP_ARG(fieldvalue);
588 struct ast_str *orig_list = ast_str_thread_get(&tmp_buf, 16);
589 const char *begin, *cur, *next;
590 int dlen, flen, first = 1;
591 struct ast_str *result, **result_ptr = &result;
592 char *delim, *varsubst;
594 AST_STANDARD_APP_ARGS(args, parse);
597 if (!(result = ast_str_thread_get(&result_buf, 16))) {
601 /* Place the result directly into the output buffer */
606 ast_log(LOG_ERROR, "Usage: LISTFILTER(<listname>,<delimiter>,<fieldvalue>)\n");
610 varsubst = alloca(strlen(args.listname) + 4);
611 sprintf(varsubst, "${%s}", args.listname);
613 /* If we don't lock the channel, the variable could disappear out from underneath us. */
615 ast_channel_lock(chan);
617 ast_str_substitute_variables(&orig_list, 0, chan, varsubst);
618 if (!ast_str_strlen(orig_list)) {
619 ast_log(LOG_ERROR, "List variable '%s' not found\n", args.listname);
621 ast_channel_unlock(chan);
626 /* If the string isn't there, just copy out the string and be done with it. */
627 if (!(ptr = strstr(ast_str_buffer(orig_list), args.fieldvalue))) {
629 ast_copy_string(buf, ast_str_buffer(orig_list), len);
631 ast_str_set(result_ptr, len, "%s", ast_str_buffer(orig_list));
634 ast_channel_unlock(chan);
639 dlen = strlen(args.delimiter);
640 delim = alloca(dlen + 1);
641 ast_get_encoded_str(args.delimiter, delim, dlen + 1);
643 if ((dlen = strlen(delim)) == 0) {
648 flen = strlen(args.fieldvalue);
650 ast_str_reset(*result_ptr);
651 /* Enough space for any result */
653 ast_str_make_space(result_ptr, len ? len : ast_str_strlen(orig_list) + 1);
656 begin = ast_str_buffer(orig_list);
657 next = strstr(begin, delim);
660 /* Find next boundary */
663 next = strstr(cur + dlen, delim);
665 cur = strchr(begin + dlen, '\0');
668 if (flen == cur - begin && !strncmp(begin, args.fieldvalue, flen)) {
670 begin += flen + dlen;
672 /* Copy field to output */
674 ast_str_append(result_ptr, len, "%s", delim);
677 ast_str_append_substr(result_ptr, len, begin, cur - begin);
681 } while (*cur != '\0');
683 ast_channel_unlock(chan);
687 ast_copy_string(buf, ast_str_buffer(result), len);
693 static int listfilter_read(struct ast_channel *chan, const char *cmd, char *parse, char *buf, size_t len)
695 return listfilter(chan, cmd, parse, buf, NULL, len);
698 static int listfilter_read2(struct ast_channel *chan, const char *cmd, char *parse, struct ast_str **buf, ssize_t len)
700 return listfilter(chan, cmd, parse, NULL, buf, len);
703 static struct ast_custom_function listfilter_function = {
704 .name = "LISTFILTER",
705 .read = listfilter_read,
706 .read2 = listfilter_read2,
709 static int filter(struct ast_channel *chan, const char *cmd, char *parse, char *buf,
712 AST_DECLARE_APP_ARGS(args,
713 AST_APP_ARG(allowed);
718 char allowed[256] = "";
719 size_t allowedlen = 0;
720 int32_t bitfield[8] = { 0, }; /* 256 bits */
722 AST_STANDARD_RAW_ARGS(args, parse);
725 ast_log(LOG_ERROR, "Usage: FILTER(<allowed-chars>,<string>)\n");
729 if (args.allowed[0] == '"' && !ast_opt_dont_warn) {
730 ast_log(LOG_WARNING, "FILTER allowed characters includes the quote (\") character. This may not be what you want.\n");
734 for (; *(args.allowed);) {
738 if (ast_get_encoded_char(args.allowed, &c1, &consumed))
740 args.allowed += consumed;
742 if (*(args.allowed) == '-') {
743 if (ast_get_encoded_char(args.allowed + 1, &c2, &consumed))
745 args.allowed += consumed + 1;
747 if ((unsigned char) c2 < (unsigned char) c1 && !ast_opt_dont_warn) {
748 ast_log(LOG_WARNING, "Range wrapping in FILTER(%s,%s). This may not be what you want.\n", parse, args.string);
752 * Looks a little strange, until you realize that we can overflow
753 * the size of a char.
755 for (ac = (unsigned char) c1; ac != (unsigned char) c2; ac++) {
756 bitfield[ac / 32] |= 1 << (ac % 32);
758 bitfield[ac / 32] |= 1 << (ac % 32);
760 ast_debug(4, "c1=%d, c2=%d\n", c1, c2);
762 ac = (unsigned char) c1;
763 ast_debug(4, "c1=%d, consumed=%d, args.allowed=%s\n", c1, (int) consumed, args.allowed - consumed);
764 bitfield[ac / 32] |= 1 << (ac % 32);
768 for (ac = 1; ac != 0; ac++) {
769 if (bitfield[ac / 32] & (1 << (ac % 32))) {
770 allowed[allowedlen++] = ac;
774 ast_debug(1, "Allowed: %s\n", allowed);
776 for (; *(args.string) && (buf + len - 1 > outbuf); (args.string)++) {
777 if (strchr(allowed, *(args.string)))
778 *outbuf++ = *(args.string);
785 static struct ast_custom_function filter_function = {
790 static int replace(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
792 AST_DECLARE_APP_ARGS(args,
793 AST_APP_ARG(varname);
795 AST_APP_ARG(replace);
797 char *strptr, *varsubst;
798 struct ast_str *str = ast_str_thread_get(&result_buf, 16);
799 char find[256]; /* Only 256 characters possible */
800 char replace[2] = "";
803 AST_STANDARD_APP_ARGS(args, data);
810 ast_log(LOG_ERROR, "Usage: %s(<varname>,<search-chars>[,<replace-char>])\n", cmd);
815 ast_get_encoded_str(args.find, find, sizeof(find));
816 ast_get_encoded_char(args.replace, replace, &unused);
818 if (ast_strlen_zero(find) || ast_strlen_zero(args.varname)) {
819 ast_log(LOG_ERROR, "The characters to search for and the variable name must not be empty.\n");
823 varsubst = alloca(strlen(args.varname) + 4);
824 sprintf(varsubst, "${%s}", args.varname);
825 ast_str_substitute_variables(&str, 0, chan, varsubst);
827 if (!ast_str_strlen(str)) {
828 /* Blank, nothing to replace */
832 ast_debug(3, "String to search: (%s)\n", ast_str_buffer(str));
833 ast_debug(3, "Characters to find: (%s)\n", find);
834 ast_debug(3, "Character to replace with: (%s)\n", replace);
836 for (strptr = ast_str_buffer(str); *strptr; strptr++) {
837 /* buf is already a mutable buffer, so we construct the result
839 if (strchr(find, *strptr)) {
840 if (ast_strlen_zero(replace)) {
841 /* Remove character */
842 strcpy(strptr, strptr + 1); /* SAFE */
845 /* Replace character */
851 ast_str_set(buf, len, "%s", ast_str_buffer(str));
855 static struct ast_custom_function replace_function = {
860 static int strreplace(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
862 char *varsubstr; /* substring for input var */
863 char *start; /* Starting pos of substring search. */
864 char *end; /* Ending pos of substring search. */
865 int find_size; /* length of given find-string */
866 unsigned max_matches; /* number of matches we find before terminating search */
867 unsigned count; /* loop counter */
868 struct ast_str *str = ast_str_thread_get(&result_buf, 16); /* Holds the data obtained from varname */
870 AST_DECLARE_APP_ARGS(args,
871 AST_APP_ARG(varname);
872 AST_APP_ARG(find_string);
873 AST_APP_ARG(replace_string);
874 AST_APP_ARG(max_replacements);
875 AST_APP_ARG(other); /* Any remining unused arguments */
878 /* Guarantee output string is empty to start with. */
882 /* We failed to allocate str, forget it. We failed. */
886 /* Parse the arguments. */
887 AST_STANDARD_APP_ARGS(args, data);
890 /* Didn't receive enough arguments to do anything */
892 "Usage: %s(<varname>,<find-string>[,<replace-string>,[<max-replacements>]])\n",
897 /* No var name specified. Return failure, string is already empty. */
898 if (ast_strlen_zero(args.varname)) {
902 /* Zero length find strings are a no-no. Kill the function if we run into one. */
903 if (ast_strlen_zero(args.find_string)) {
904 ast_log(LOG_ERROR, "No <find-string> specified\n");
907 find_size = strlen(args.find_string);
909 /* set varsubstr to the matching variable */
910 varsubstr = alloca(strlen(args.varname) + 4);
911 sprintf(varsubstr, "${%s}", args.varname);
912 ast_str_substitute_variables(&str, 0, chan, varsubstr);
914 /* Determine how many replacements are allowed. */
915 if (!args.max_replacements
916 || (max_matches = atoi(args.max_replacements)) <= 0) {
917 /* Unlimited replacements are allowed. */
921 /* Generate the search and replaced string. */
922 start = ast_str_buffer(str);
923 for (count = 0; count < max_matches; ++count) {
924 end = strstr(start, args.find_string);
926 /* Did not find a matching substring in the remainder. */
930 /* Replace the found substring. */
932 ast_str_append(buf, len, "%s", start);
933 if (args.replace_string) {
934 /* Append the replacement string */
935 ast_str_append(buf, len, "%s", args.replace_string);
937 start = end + find_size;
939 ast_str_append(buf, len, "%s", start);
944 static struct ast_custom_function strreplace_function = {
945 .name = "STRREPLACE",
949 static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf,
952 AST_DECLARE_APP_ARGS(args,
962 AST_NONSTANDARD_APP_ARGS(args, parse, '"');
964 if (args.argc != 3) {
965 ast_log(LOG_ERROR, "Unexpected arguments: should have been in the form '\"<regex>\" <string>'\n");
968 if ((*args.str == ' ') || (*args.str == '\t'))
971 ast_debug(1, "FUNCTION REGEX (%s)(%s)\n", args.reg, args.str);
973 if ((errcode = regcomp(®exbuf, args.reg, REG_EXTENDED | REG_NOSUB))) {
974 regerror(errcode, ®exbuf, buf, len);
975 ast_log(LOG_WARNING, "Malformed input %s(%s): %s\n", cmd, parse, buf);
979 strcpy(buf, regexec(®exbuf, args.str, 0, NULL, 0) ? "0" : "1");
986 static struct ast_custom_function regex_function = {
991 #define HASH_PREFIX "~HASH~%s~"
992 #define HASH_FORMAT HASH_PREFIX "%s~"
994 static char *app_clearhash = "ClearHash";
996 /* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */
997 static void clearvar_prefix(struct ast_channel *chan, const char *prefix)
999 struct ast_var_t *var;
1000 int len = strlen(prefix);
1001 AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->varshead, var, entries) {
1002 if (strncasecmp(prefix, ast_var_name(var), len) == 0) {
1003 AST_LIST_REMOVE_CURRENT(entries);
1007 AST_LIST_TRAVERSE_SAFE_END
1010 static int exec_clearhash(struct ast_channel *chan, const char *data)
1013 snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null");
1014 clearvar_prefix(chan, prefix);
1018 static int array(struct ast_channel *chan, const char *cmd, char *var,
1021 AST_DECLARE_APP_ARGS(arg1,
1022 AST_APP_ARG(var)[100];
1024 AST_DECLARE_APP_ARGS(arg2,
1025 AST_APP_ARG(val)[100];
1027 char *origvar = "", *value2, varname[256];
1030 value2 = ast_strdupa(value);
1031 if (!var || !value2)
1034 if (!strcmp(cmd, "HASH")) {
1035 const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~");
1038 var = ast_strdupa(var2);
1041 ast_autoservice_stop(chan);
1047 /* The functions this will generally be used with are SORT and ODBC_*, which
1048 * both return comma-delimited lists. However, if somebody uses literal lists,
1049 * their commas will be translated to vertical bars by the load, and I don't
1050 * want them to be surprised by the result. Hence, we prefer commas as the
1051 * delimiter, but we'll fall back to vertical bars if commas aren't found.
1053 ast_debug(1, "array (%s=%s)\n", var, S_OR(value2, ""));
1054 AST_STANDARD_APP_ARGS(arg1, var);
1056 AST_STANDARD_APP_ARGS(arg2, value2);
1058 for (i = 0; i < arg1.argc; i++) {
1059 ast_debug(1, "array set value (%s=%s)\n", arg1.var[i],
1060 S_OR(arg2.val[i], ""));
1061 if (i < arg2.argc) {
1063 if (origvar[0] == '_') {
1064 if (origvar[1] == '_') {
1065 snprintf(varname, sizeof(varname), "__" HASH_FORMAT, origvar + 2, arg1.var[i]);
1067 snprintf(varname, sizeof(varname), "_" HASH_FORMAT, origvar + 1, arg1.var[i]);
1070 snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
1073 pbx_builtin_setvar_helper(chan, varname, arg2.val[i]);
1075 pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]);
1078 /* We could unset the variable, by passing a NULL, but due to
1079 * pushvar semantics, that could create some undesired behavior. */
1081 snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]);
1082 pbx_builtin_setvar_helper(chan, varname, "");
1084 pbx_builtin_setvar_helper(chan, arg1.var[i], "");
1092 static int hashkeys_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1094 struct ast_var_t *newvar;
1095 struct ast_str *prefix = ast_str_alloca(80);
1097 ast_str_set(&prefix, -1, HASH_PREFIX, data);
1098 memset(buf, 0, len);
1100 AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) {
1101 if (strncasecmp(ast_str_buffer(prefix), ast_var_name(newvar), ast_str_strlen(prefix)) == 0) {
1102 /* Copy everything after the prefix */
1103 strncat(buf, ast_var_name(newvar) + ast_str_strlen(prefix), len - strlen(buf) - 1);
1104 /* Trim the trailing ~ */
1105 buf[strlen(buf) - 1] = ',';
1108 /* Trim the trailing comma */
1109 buf[strlen(buf) - 1] = '\0';
1113 static int hashkeys_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1115 struct ast_var_t *newvar;
1116 struct ast_str *prefix = ast_str_alloca(80);
1119 ast_str_set(&prefix, -1, HASH_PREFIX, data);
1121 AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) {
1122 if (strncasecmp(ast_str_buffer(prefix), ast_var_name(newvar), ast_str_strlen(prefix)) == 0) {
1123 /* Copy everything after the prefix */
1124 ast_str_append(buf, len, "%s", ast_var_name(newvar) + ast_str_strlen(prefix));
1125 /* Trim the trailing ~ */
1126 tmp = ast_str_buffer(*buf);
1127 tmp[ast_str_strlen(*buf) - 1] = ',';
1130 /* Trim the trailing comma */
1131 tmp = ast_str_buffer(*buf);
1132 tmp[ast_str_strlen(*buf) - 1] = '\0';
1136 static int hash_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
1139 AST_DECLARE_APP_ARGS(arg,
1140 AST_APP_ARG(hashname);
1141 AST_APP_ARG(hashkey);
1144 if (!strchr(var, ',')) {
1145 /* Single argument version */
1146 return array(chan, "HASH", var, value);
1149 AST_STANDARD_APP_ARGS(arg, var);
1150 if (arg.hashname[0] == '_') {
1151 if (arg.hashname[1] == '_') {
1152 snprintf(varname, sizeof(varname), "__" HASH_FORMAT, arg.hashname + 2, arg.hashkey);
1154 snprintf(varname, sizeof(varname), "_" HASH_FORMAT, arg.hashname + 1, arg.hashkey);
1157 snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
1159 pbx_builtin_setvar_helper(chan, varname, value);
1164 static int hash_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1167 const char *varvalue;
1168 AST_DECLARE_APP_ARGS(arg,
1169 AST_APP_ARG(hashname);
1170 AST_APP_ARG(hashkey);
1173 AST_STANDARD_APP_ARGS(arg, data);
1174 if (arg.argc == 2) {
1175 snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey);
1176 varvalue = pbx_builtin_getvar_helper(chan, varname);
1178 ast_copy_string(buf, varvalue, len);
1181 } else if (arg.argc == 1) {
1182 char colnames[4096];
1184 AST_DECLARE_APP_ARGS(arg2,
1185 AST_APP_ARG(col)[100];
1188 /* Get column names, in no particular order */
1189 hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames));
1190 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames);
1192 AST_STANDARD_APP_ARGS(arg2, colnames);
1195 /* Now get the corresponding column values, in exactly the same order */
1196 for (i = 0; i < arg2.argc; i++) {
1197 snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]);
1198 varvalue = pbx_builtin_getvar_helper(chan, varname);
1199 strncat(buf, varvalue, len - strlen(buf) - 1);
1200 strncat(buf, ",", len - strlen(buf) - 1);
1203 /* Strip trailing comma */
1204 buf[strlen(buf) - 1] = '\0';
1210 static struct ast_custom_function hash_function = {
1212 .write = hash_write,
1216 static struct ast_custom_function hashkeys_function = {
1218 .read = hashkeys_read,
1219 .read2 = hashkeys_read2,
1222 static struct ast_custom_function array_function = {
1227 static int quote(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1229 char *bufptr = buf, *dataptr = data;
1231 if (len < 3){ /* at least two for quotes and one for binary zero */
1232 ast_log(LOG_ERROR, "Not enough buffer");
1236 if (ast_strlen_zero(data)) {
1237 ast_log(LOG_WARNING, "No argument specified!\n");
1238 ast_copy_string(buf, "\"\"", len);
1243 for (; bufptr < buf + len - 3; dataptr++) {
1244 if (*dataptr == '\\') {
1247 } else if (*dataptr == '"') {
1250 } else if (*dataptr == '\0') {
1253 *bufptr++ = *dataptr;
1261 static struct ast_custom_function quote_function = {
1266 static int csv_quote(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1268 char *bufptr = buf, *dataptr = data;
1270 if (len < 3) { /* at least two for quotes and one for binary zero */
1271 ast_log(LOG_ERROR, "Not enough buffer");
1275 if (ast_strlen_zero(data)) {
1276 ast_copy_string(buf, "\"\"", len);
1281 for (; bufptr < buf + len - 3; dataptr++){
1282 if (*dataptr == '"') {
1285 } else if (*dataptr == '\0') {
1288 *bufptr++ = *dataptr;
1296 static struct ast_custom_function csv_quote_function = {
1297 .name = "CSV_QUOTE",
1301 static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
1306 length = strlen(data);
1308 snprintf(buf, buflen, "%d", length);
1313 static struct ast_custom_function len_function = {
1319 static int acf_strftime(struct ast_channel *chan, const char *cmd, char *parse,
1320 char *buf, size_t buflen)
1322 AST_DECLARE_APP_ARGS(args,
1324 AST_APP_ARG(timezone);
1325 AST_APP_ARG(format);
1327 struct timeval when;
1332 AST_STANDARD_APP_ARGS(args, parse);
1334 ast_get_timeval(args.epoch, &when, ast_tvnow(), NULL);
1335 ast_localtime(&when, &tm, args.timezone);
1340 if (ast_strftime(buf, buflen, args.format, &tm) <= 0)
1341 ast_log(LOG_WARNING, "C function strftime() output nothing?!!\n");
1343 buf[buflen - 1] = '\0';
1348 static struct ast_custom_function strftime_function = {
1350 .read = acf_strftime,
1353 static int acf_strptime(struct ast_channel *chan, const char *cmd, char *data,
1354 char *buf, size_t buflen)
1356 AST_DECLARE_APP_ARGS(args,
1357 AST_APP_ARG(timestring);
1358 AST_APP_ARG(timezone);
1359 AST_APP_ARG(format);
1367 "Asterisk function STRPTIME() requires an argument.\n");
1371 AST_STANDARD_APP_ARGS(args, data);
1373 if (ast_strlen_zero(args.format)) {
1375 "No format supplied to STRPTIME(<timestring>,<timezone>,<format>)");
1379 if (!ast_strptime(args.timestring, args.format, &tm)) {
1380 ast_log(LOG_WARNING, "STRPTIME() found no time specified within the string\n");
1382 struct timeval when;
1383 when = ast_mktime(&tm, args.timezone);
1384 snprintf(buf, buflen, "%d", (int) when.tv_sec);
1390 static struct ast_custom_function strptime_function = {
1392 .read = acf_strptime,
1395 static int function_eval(struct ast_channel *chan, const char *cmd, char *data,
1396 char *buf, size_t buflen)
1398 if (ast_strlen_zero(data)) {
1399 ast_log(LOG_WARNING, "EVAL requires an argument: EVAL(<string>)\n");
1403 pbx_substitute_variables_helper(chan, data, buf, buflen - 1);
1408 static int function_eval2(struct ast_channel *chan, const char *cmd, char *data,
1409 struct ast_str **buf, ssize_t buflen)
1411 if (ast_strlen_zero(data)) {
1412 ast_log(LOG_WARNING, "EVAL requires an argument: EVAL(<string>)\n");
1416 ast_str_substitute_variables(buf, buflen, chan, data);
1421 static struct ast_custom_function eval_function = {
1423 .read = function_eval,
1424 .read2 = function_eval2,
1427 static int keypadhash(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
1429 char *bufptr, *dataptr;
1431 for (bufptr = buf, dataptr = data; bufptr < buf + buflen - 1; dataptr++) {
1432 if (*dataptr == '\0') {
1435 } else if (*dataptr == '1') {
1437 } else if (strchr("AaBbCc2", *dataptr)) {
1439 } else if (strchr("DdEeFf3", *dataptr)) {
1441 } else if (strchr("GgHhIi4", *dataptr)) {
1443 } else if (strchr("JjKkLl5", *dataptr)) {
1445 } else if (strchr("MmNnOo6", *dataptr)) {
1447 } else if (strchr("PpQqRrSs7", *dataptr)) {
1449 } else if (strchr("TtUuVv8", *dataptr)) {
1451 } else if (strchr("WwXxYyZz9", *dataptr)) {
1453 } else if (*dataptr == '0') {
1457 buf[buflen - 1] = '\0';
1462 static struct ast_custom_function keypadhash_function = {
1463 .name = "KEYPADHASH",
1467 static int string_toupper(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
1469 char *bufptr = buf, *dataptr = data;
1471 while ((bufptr < buf + buflen - 1) && (*bufptr++ = toupper(*dataptr++)));
1476 static int string_toupper2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t buflen)
1478 char *bufptr, *dataptr = data;
1481 ast_str_make_space(buf, buflen > 0 ? buflen : strlen(data) + 1);
1483 bufptr = ast_str_buffer(*buf);
1484 while ((bufptr < ast_str_buffer(*buf) + ast_str_size(*buf) - 1) && (*bufptr++ = toupper(*dataptr++)));
1485 ast_str_update(*buf);
1490 static struct ast_custom_function toupper_function = {
1492 .read = string_toupper,
1493 .read2 = string_toupper2,
1496 static int string_tolower(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
1498 char *bufptr = buf, *dataptr = data;
1500 while ((bufptr < buf + buflen - 1) && (*bufptr++ = tolower(*dataptr++)));
1505 static int string_tolower2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t buflen)
1507 char *bufptr, *dataptr = data;
1510 ast_str_make_space(buf, buflen > 0 ? buflen : strlen(data) + 1);
1512 bufptr = ast_str_buffer(*buf);
1513 while ((bufptr < ast_str_buffer(*buf) + ast_str_size(*buf) - 1) && (*bufptr++ = tolower(*dataptr++)));
1514 ast_str_update(*buf);
1519 static struct ast_custom_function tolower_function = {
1521 .read = string_tolower,
1522 .read2 = string_tolower2,
1525 static int shift_pop(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1527 #define beginning (cmd[0] == 'S') /* SHIFT */
1528 char *after, delimiter[2] = ",", *varsubst;
1530 struct ast_str *before = ast_str_thread_get(&result_buf, 16);
1531 char *(*search_func)(const char *s, int c) = (beginning ? strchr : strrchr);
1532 AST_DECLARE_APP_ARGS(args,
1534 AST_APP_ARG(delimiter);
1541 AST_STANDARD_APP_ARGS(args, data);
1543 if (ast_strlen_zero(args.var)) {
1544 ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
1548 varsubst = alloca(strlen(args.var) + 4);
1549 sprintf(varsubst, "${%s}", args.var);
1550 ast_str_substitute_variables(&before, 0, chan, varsubst);
1552 if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
1553 ast_get_encoded_char(args.delimiter, delimiter, &unused);
1556 if (!ast_str_strlen(before)) {
1557 /* Nothing to pop */
1561 if (!(after = search_func(ast_str_buffer(before), delimiter[0]))) {
1562 /* Only one entry in array */
1563 ast_str_set(buf, len, "%s", ast_str_buffer(before));
1564 pbx_builtin_setvar_helper(chan, args.var, "");
1567 ast_str_set(buf, len, "%s", beginning ? ast_str_buffer(before) : after);
1568 pbx_builtin_setvar_helper(chan, args.var, beginning ? after : ast_str_buffer(before));
1575 static struct ast_custom_function shift_function = {
1580 static struct ast_custom_function pop_function = {
1585 static int unshift_push(struct ast_channel *chan, const char *cmd, char *data, const char *new_value)
1587 #define beginning (cmd[0] == 'U') /* UNSHIFT */
1588 char delimiter[2] = ",", *varsubst;
1590 struct ast_str *buf, *previous_value;
1591 AST_DECLARE_APP_ARGS(args,
1593 AST_APP_ARG(delimiter);
1596 if (!(buf = ast_str_thread_get(&result_buf, 16)) ||
1597 !(previous_value = ast_str_thread_get(&tmp_buf, 16))) {
1601 AST_STANDARD_APP_ARGS(args, data);
1603 if (ast_strlen_zero(args.var)) {
1604 ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
1608 if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
1609 ast_get_encoded_char(args.delimiter, delimiter, &unused);
1612 varsubst = alloca(strlen(args.var) + 4);
1613 sprintf(varsubst, "${%s}", args.var);
1614 ast_str_substitute_variables(&previous_value, 0, chan, varsubst);
1616 if (!ast_str_strlen(previous_value)) {
1617 ast_str_set(&buf, 0, "%s", new_value);
1619 ast_str_set(&buf, 0, "%s%c%s",
1620 beginning ? new_value : ast_str_buffer(previous_value),
1622 beginning ? ast_str_buffer(previous_value) : new_value);
1625 pbx_builtin_setvar_helper(chan, args.var, ast_str_buffer(buf));
1631 static struct ast_custom_function push_function = {
1633 .write = unshift_push,
1636 static struct ast_custom_function unshift_function = {
1638 .write = unshift_push,
1641 static int passthru(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1643 ast_str_set(buf, len, "%s", data);
1647 static struct ast_custom_function passthru_function = {
1652 #ifdef TEST_FRAMEWORK
1653 AST_TEST_DEFINE(test_FIELDNUM)
1655 int i, res = AST_TEST_PASS;
1656 struct ast_channel *chan;
1657 struct ast_str *str;
1658 char expression[256];
1663 const char *expected;
1665 {"abc,def,ghi,jkl", "\\,", "ghi", "3"},
1666 {"abc def ghi jkl", " ", "abc", "1"},
1667 {"abc/def/ghi/jkl", "\\\\x2f", "def", "2"},
1668 {"abc$def$ghi$jkl", "", "ghi", "0"},
1669 {"abc,def,ghi,jkl", "-", "", "0"},
1670 {"abc-def-ghi-jkl", "-", "mno", "0"}
1675 info->name = "func_FIELDNUM_test";
1676 info->category = "/funcs/func_strings/";
1677 info->summary = "Test FIELDNUM function";
1678 info->description = "Verify FIELDNUM behavior";
1679 return AST_TEST_NOT_RUN;
1684 if (!(chan = ast_dummy_channel_alloc())) {
1685 ast_test_status_update(test, "Unable to allocate dummy channel\n");
1686 return AST_TEST_FAIL;
1689 if (!(str = ast_str_create(16))) {
1690 ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
1691 ast_channel_release(chan);
1692 return AST_TEST_FAIL;
1695 for (i = 0; i < ARRAY_LEN(test_args); i++) {
1696 struct ast_var_t *var = ast_var_assign("FIELDS", test_args[i].fields);
1697 AST_LIST_INSERT_HEAD(&chan->varshead, var, entries);
1699 snprintf(expression, sizeof(expression), "${FIELDNUM(%s,%s,%s)}", var->name, test_args[i].delim, test_args[i].field);
1700 ast_str_substitute_variables(&str, 0, chan, expression);
1702 AST_LIST_REMOVE(&chan->varshead, var, entries);
1703 ast_var_delete(var);
1705 if (strcasecmp(ast_str_buffer(str), test_args[i].expected)) {
1706 ast_test_status_update(test, "Evaluation of '%s' returned '%s' instead of the expected value '%s'\n",
1707 expression, ast_str_buffer(str), test_args[i].expected);
1708 res = AST_TEST_FAIL;
1714 ast_channel_release(chan);
1719 AST_TEST_DEFINE(test_FILTER)
1721 int i, res = AST_TEST_PASS;
1722 const char *test_strings[][2] = {
1725 {"\\x41-R", "DAHDI"},
1726 {"0-9A-Ca-c", "0042133333A12212"},
1727 {"0-9a-cA-C_+\\-", "0042133333A12212"},
1733 info->name = "func_FILTER_test";
1734 info->category = "/funcs/func_strings/";
1735 info->summary = "Test FILTER function";
1736 info->description = "Verify FILTER behavior";
1737 return AST_TEST_NOT_RUN;
1742 for (i = 0; test_strings[i][0]; i++) {
1743 char tmp[256], tmp2[256] = "";
1744 snprintf(tmp, sizeof(tmp), "${FILTER(%s,0042133333&DAHDI/g1/2212)}", test_strings[i][0]);
1745 pbx_substitute_variables_helper(NULL, tmp, tmp2, sizeof(tmp2) - 1);
1746 if (strcmp(test_strings[i][1], tmp2)) {
1747 ast_test_status_update(test, "Format string '%s' substituted to '%s'. Expected '%s'.\n", test_strings[i][0], tmp2, test_strings[i][1]);
1748 res = AST_TEST_FAIL;
1754 AST_TEST_DEFINE(test_STRREPLACE)
1756 int i, res = AST_TEST_PASS;
1757 struct ast_channel *chan; /* dummy channel */
1758 struct ast_str *str; /* fancy string for holding comparing value */
1760 const char *test_strings[][5] = {
1761 {"Weasels have eaten my telephone system", "have eaten my", "are eating our", "", "Weasels are eating our telephone system"}, /*Test normal conditions */
1762 {"Did you know twenty plus two is twenty-two?", "twenty", "thirty", NULL, "Did you know thirty plus two is thirty-two?"}, /* Test no third comma */
1763 {"foofoofoofoofoofoofoo", "foofoo", "bar", NULL, "barbarbarfoo"}, /* Found string within previous match */
1764 {"My pet dog once ate a dog who sat on a dog while eating a corndog.", "dog", "cat", "3", "My pet cat once ate a cat who sat on a cat while eating a corndog."},
1765 {"One and one and one is three", "and", "plus", "1", "One plus one and one is three"}, /* Test <max-replacements> = 1*/
1766 {"", "fhqwagads", "spelunker", NULL, ""}, /* Empty primary string */
1767 {"Part of this string is missing.", "missing", NULL, NULL, "Part of this string is ."}, /* Empty replace string */
1768 {"'Accidentally' left off a bunch of stuff.", NULL, NULL, NULL, ""}, /* Deliberate error test from too few args */
1769 {"This test will also error.", "", "", "", ""}, /* Deliberate error test from blank find string */
1770 {"This is an \"escape character\" test.", "\\\"escape character\\\"", "evil", NULL, "This is an evil test."}
1775 info->name = "func_STRREPLACE_test";
1776 info->category = "/funcs/func_strings/";
1777 info->summary = "Test STRREPLACE function";
1778 info->description = "Verify STRREPLACE behavior";
1779 return AST_TEST_NOT_RUN;
1784 if (!(chan = ast_dummy_channel_alloc())) {
1785 ast_test_status_update(test, "Unable to allocate dummy channel\n");
1786 return AST_TEST_FAIL;
1789 if (!(str = ast_str_create(64))) {
1790 ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
1791 ast_channel_release(chan);
1792 return AST_TEST_FAIL;
1795 for (i = 0; i < ARRAY_LEN(test_strings); i++) {
1796 char tmp[512], tmp2[512] = "";
1798 struct ast_var_t *var = ast_var_assign("test_string", test_strings[i][0]);
1799 AST_LIST_INSERT_HEAD(&chan->varshead, var, entries);
1801 if (test_strings[i][3]) {
1802 snprintf(tmp, sizeof(tmp), "${STRREPLACE(%s,%s,%s,%s)}", "test_string", test_strings[i][1], test_strings[i][2], test_strings[i][3]);
1803 } else if (test_strings[i][2]) {
1804 snprintf(tmp, sizeof(tmp), "${STRREPLACE(%s,%s,%s)}", "test_string", test_strings[i][1], test_strings[i][2]);
1805 } else if (test_strings[i][1]) {
1806 snprintf(tmp, sizeof(tmp), "${STRREPLACE(%s,%s)}", "test_string", test_strings[i][1]);
1808 snprintf(tmp, sizeof(tmp), "${STRREPLACE(%s)}", "test_string");
1810 ast_str_substitute_variables(&str, 0, chan, tmp);
1811 if (strcmp(test_strings[i][4], ast_str_buffer(str))) {
1812 ast_test_status_update(test, "Format string '%s' substituted to '%s'. Expected '%s'.\n", test_strings[i][0], tmp2, test_strings[i][4]);
1813 res = AST_TEST_FAIL;
1818 ast_channel_release(chan);
1824 static int unload_module(void)
1828 AST_TEST_UNREGISTER(test_FIELDNUM);
1829 AST_TEST_UNREGISTER(test_FILTER);
1830 AST_TEST_UNREGISTER(test_STRREPLACE);
1831 res |= ast_custom_function_unregister(&fieldqty_function);
1832 res |= ast_custom_function_unregister(&fieldnum_function);
1833 res |= ast_custom_function_unregister(&filter_function);
1834 res |= ast_custom_function_unregister(&replace_function);
1835 res |= ast_custom_function_unregister(&strreplace_function);
1836 res |= ast_custom_function_unregister(&listfilter_function);
1837 res |= ast_custom_function_unregister(®ex_function);
1838 res |= ast_custom_function_unregister(&array_function);
1839 res |= ast_custom_function_unregister("e_function);
1840 res |= ast_custom_function_unregister(&csv_quote_function);
1841 res |= ast_custom_function_unregister(&len_function);
1842 res |= ast_custom_function_unregister(&strftime_function);
1843 res |= ast_custom_function_unregister(&strptime_function);
1844 res |= ast_custom_function_unregister(&eval_function);
1845 res |= ast_custom_function_unregister(&keypadhash_function);
1846 res |= ast_custom_function_unregister(&hashkeys_function);
1847 res |= ast_custom_function_unregister(&hash_function);
1848 res |= ast_unregister_application(app_clearhash);
1849 res |= ast_custom_function_unregister(&toupper_function);
1850 res |= ast_custom_function_unregister(&tolower_function);
1851 res |= ast_custom_function_unregister(&shift_function);
1852 res |= ast_custom_function_unregister(&pop_function);
1853 res |= ast_custom_function_unregister(&push_function);
1854 res |= ast_custom_function_unregister(&unshift_function);
1855 res |= ast_custom_function_unregister(&passthru_function);
1860 static int load_module(void)
1864 AST_TEST_REGISTER(test_FIELDNUM);
1865 AST_TEST_REGISTER(test_FILTER);
1866 AST_TEST_REGISTER(test_STRREPLACE);
1867 res |= ast_custom_function_register(&fieldqty_function);
1868 res |= ast_custom_function_register(&fieldnum_function);
1869 res |= ast_custom_function_register(&filter_function);
1870 res |= ast_custom_function_register(&replace_function);
1871 res |= ast_custom_function_register(&strreplace_function);
1872 res |= ast_custom_function_register(&listfilter_function);
1873 res |= ast_custom_function_register(®ex_function);
1874 res |= ast_custom_function_register(&array_function);
1875 res |= ast_custom_function_register("e_function);
1876 res |= ast_custom_function_register(&csv_quote_function);
1877 res |= ast_custom_function_register(&len_function);
1878 res |= ast_custom_function_register(&strftime_function);
1879 res |= ast_custom_function_register(&strptime_function);
1880 res |= ast_custom_function_register(&eval_function);
1881 res |= ast_custom_function_register(&keypadhash_function);
1882 res |= ast_custom_function_register(&hashkeys_function);
1883 res |= ast_custom_function_register(&hash_function);
1884 res |= ast_register_application_xml(app_clearhash, exec_clearhash);
1885 res |= ast_custom_function_register(&toupper_function);
1886 res |= ast_custom_function_register(&tolower_function);
1887 res |= ast_custom_function_register(&shift_function);
1888 res |= ast_custom_function_register(&pop_function);
1889 res |= ast_custom_function_register(&push_function);
1890 res |= ast_custom_function_register(&unshift_function);
1891 res |= ast_custom_function_register(&passthru_function);
1896 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "String handling dialplan functions");