]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
func_evalexten: Add EVAL_SUB function.
authorNaveen Albert <asterisk@phreaknet.org>
Thu, 17 Oct 2024 13:18:45 +0000 (09:18 -0400)
committerNaveen Albert <asterisk@phreaknet.org>
Tue, 12 Nov 2024 19:35:50 +0000 (19:35 +0000)
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

funcs/func_evalexten.c

index 6a7d28bc902c78ef377f58b5f87cf17d4b70bb62..df7d96411d76cae1f4ccd4802ff896e433b51dbb 100644 (file)
@@ -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
                        <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");