func_evalexten: Add EVAL_SUB function.

This adds an EVAL_SUB function, which is similar to the existing
EVAL_EXTEN function but significantly more powerful, as it allows
executing arbitrary dialplan and capturing its return value as
the function's output. While EVAL_EXTEN should be preferred if it
is possible to use it, EVAL_SUB can be used in a wider variety
of cases and allows arbitrary computation to be performed in
a dialplan function call, leveraging the dialplan.

Resolves: #951
pull/980/head
Naveen Albert 6 months ago committed by asterisk-org-access-app[bot]
parent d5a0626889
commit 7173c92d9f

@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2021, Naveen Albert
* Copyright (C) 2021, 2024, Naveen Albert
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@ -81,9 +81,64 @@
<para>A limitation of this function is that the application at the specified
extension isn't actually executed, and thus unlike a Gosub, you can't pass
arguments in the EVAL_EXTEN function.</para>
<para>If you need the ability to evaluate more complex logic that cannot be done
purely using functions, see <literal>EVAL_SUB</literal>.</para>
</description>
<see-also>
<ref type="function">EVAL</ref>
<ref type="function">EVAL_SUB</ref>
</see-also>
</function>
<function name="EVAL_SUB" language="en_US">
<synopsis>
Executes a Gosub and provides its return value as a string
</synopsis>
<syntax>
<parameter name="context" />
<parameter name="extensions" />
<parameter name="priority" required="true" />
</syntax>
<description>
<para>The EVAL_SUB function executes up a dialplan location by context,extension,priority, with optional arguments
and returns the contents of the Return statement. The arguments to <literal>EVAL_SUB</literal>
are exactly like they are with <literal>Gosub</literal>.</para>
<para>This function is complementary to <literal>EVAL_EXTEN</literal>. However, it is more powerful,
since it allows executing arbitrary dialplan and capturing some outcome as a dialplan function's
return value, allowing it to be used in a variety of scenarios that do not allow executing dialplan
directly but allow variables and functions to be used, and where using <literal>EVAL_EXTEN</literal>
would be difficult or impossible.</para>
<para>Consequently, this function also allows you to implement your own arbitrary functions
in dialplan, which can then be wrapped using the Asterisk function interface using <literal>EVAL_SUB</literal>.</para>
<para>While this function is primarily intended to be used for executing Gosub routines that are quick
and do not interact with the channel, it is safe to execute arbitrary, even blocking, dialplan in the
called subroutine. That said, this kind of usage is not recommended.</para>
<para>This function will always return, even if the channel is hung up.</para>
<example title="Record whether a PSTN call is local">
[islocal]
exten => _X!,1,ExecIf($[${LEN(${EXTEN})}&lt;10]?Return(1))
same => n,Set(LOCAL(npanxx)=${EXTEN:-10:6})
same => n,ReturnIf(${EXISTS(${DB(localcall/${npanxx})})}?${DB(localcall/${npanxx})})
same => n,Set(LOCAL(islocal)=${SHELL(curl "https://example.com/islocal?npanxx=${EXTEN:-10:6}")})
same => n,Set(LOCAL(islocal)=${FILTER(A-Z,${islocal})})
same => n,Set(DB(localcall/${npanxx})=${islocal})
same => n,Return(${islocal})
[outgoing]
exten => _1NXXNXXXXXX,1,Set(CDR(toll)=${IF($["${EVAL_SUB(islocal,${EXTEN},1)}"="Y"]?0:1)})
same => n,Dial(DAHDI/1/${EXTEN})
same => n,Hangup()
</example>
<para>This example illustrates an example of logic that would be difficult to capture
in a way that a single call to <literal>EVAL_EXTEN</literal> would return the same result. For one, conditionals
are involved, and due to the way Asterisk parses dialplan, all functions in an application call are evaluated all the
time, which may be undesirable if they cause side effects (e.g. making a cURL request) that should only happen in certain circumstances.</para>
<para>The above example, of course, does not require the use of this function, as it could have been invoked
using the Gosub application directly. However, if constrained to just using variables or functions,
<literal>EVAL_SUB</literal> would be required.</para>
</description>
<see-also>
<ref type="function">EVAL_EXTEN</ref>
<ref type="application">Return</ref>
</see-also>
</function>
***/
@ -129,19 +184,57 @@ static int eval_exten_read(struct ast_channel *chan, const char *cmd, char *data
return 0;
}
static int eval_sub_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
int gosub_res;
const char *retval;
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "The EVAL_SUB function requires an extension\n");
*buf = '\0';
return -1;
}
/* Ignore hangups since we want to retrieve a value, and this function could be called at hangup time */
gosub_res = ast_app_exec_sub(NULL, chan, data, 1);
if (gosub_res) {
ast_log(LOG_WARNING, "Failed to execute Gosub(%s)\n", data);
*buf = '\0';
return -1;
}
ast_channel_lock(chan);
retval = pbx_builtin_getvar_helper(chan, "GOSUB_RETVAL");
ast_copy_string(buf, S_OR(retval, ""), len); /* Overwrite, even if empty, to ensure a stale GOSUB_RETVAL isn't returned as our value */
ast_channel_unlock(chan);
return 0;
}
static struct ast_custom_function eval_exten_function = {
.name = "EVAL_EXTEN",
.read = eval_exten_read,
};
static struct ast_custom_function eval_sub_function = {
.name = "EVAL_SUB",
.read = eval_sub_read,
};
static int unload_module(void)
{
return ast_custom_function_unregister(&eval_exten_function);
int res = 0;
res |= ast_custom_function_unregister(&eval_exten_function);
res |= ast_custom_function_unregister(&eval_sub_function);
return res;
}
static int load_module(void)
{
return ast_custom_function_register(&eval_exten_function);
int res = 0;
res |= ast_custom_function_register(&eval_exten_function);
res |= ast_custom_function_register(&eval_sub_function);
return res;
}
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Extension evaluation function");

Loading…
Cancel
Save