chan_websocket: Allow additional URI parameters to be added to the outgoing URI.

* Added a new option to the WebSocket dial string to capture the additional
  URI parameters.
* Added a new API ast_uri_verify_encoded() that verifies that a string
  either doesn't need URI encoding or that it has already been encoded.
* Added a new API ast_websocket_client_add_uri_params() to add the params
  to the client websocket session.
* Added XML documentation that will show up with `core show application Dial`
  that shows how to use it.

Resolves: #1352

UserNote: A new WebSocket channel driver option `v` has been added to the
Dial application that allows you to specify additional URI parameters on
outgoing connections. Run `core show application Dial` from the Asterisk CLI
to see how to use it.
This commit is contained in:
George Joseph
2025-08-13 13:22:38 -06:00
parent 09802fbef2
commit 5d822d64ef
5 changed files with 214 additions and 2 deletions

View File

@@ -31,6 +31,61 @@
<support_level>core</support_level>
***/
/*** DOCUMENTATION
<info name="Dial_Resource" language="en_US" tech="WebSocket">
<para>WebSocket Dial Strings:</para>
<para><literal>Dial(WebSocket/connectionid[/websocket_options])</literal></para>
<para>WebSocket Parameters:</para>
<enumlist>
<enum name="connectionid">
<para>For outgoing WebSockets, this is the ID of the connection
in websocket_client.conf to use for the call. To accept incoming
WebSocket connections use the literal <literal>INCOMING</literal></para>
</enum>
<enum name="websocket_options">
<para>Options to control how the WebSocket channel behaves.</para>
<enumlist>
<enum name="c(codec) - Specify the codec to use in the channel">
<para></para>
<para> If not specified, the first codec from the caller's channel will be used.
</para>
</enum>
<enum name="n - Don't auto answer">
<para>Normally, the WebSocket channel will be answered when
connection is established with the remote app. If this
option is specified however, the channel will not be
answered until the <literal>ANSWER</literal> command is
received from the remote app or the remote app calls the
/channels/answer ARI endpoint.
</para>
</enum>
<enum name="v(uri_parameters) - Add parameters to the outbound URI">
<para>This option allows you to add additional parameters to the
outbound URI. The format is:
<literal>v(param1=value1,param2=value2...)</literal>
</para>
<para>You must ensure that no parameter name or value contains
characters not valid in a URL. The easiest way to do this is to
use the URIENCODE() dialplan function to encode them. Be aware
though that each name and value must be encoded separately. You
can't simply encode the whole string.</para>
</enum>
</enumlist>
</enum>
</enumlist>
<para>Examples:
</para>
<example title="Make an outbound WebSocket connection using connection 'connection1' and the 'sln16' codec.">
same => n,Dial(WebSocket/connection1/c(sln16))
</example>
<example title="Listen for an incoming WebSocket connection and don't auto-answer it.">
same => n,Dial(WebSocket/INCOMING/n)
</example>
<example title="Add URI parameters.">
same => n,Dial(WebSocket/connection1/v(${URIENCODE(vari able)}=${URIENCODE(${CHANNEL})},variable2=$(URIENCODE(${EXTEN})}))
</example>
</info>
***/
#include "asterisk.h"
#include "asterisk/app.h"
@@ -69,6 +124,7 @@ struct websocket_pvt {
pthread_t outbound_read_thread;
size_t bytes_read;
size_t leftover_len;
char *uri_params;
char *leftover_data;
int no_auto_answer;
int optimal_frame_size;
@@ -827,6 +883,10 @@ static int webchan_call(struct ast_channel *ast, const char *dest,
ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
ast_channel_name(ast), dest, instance->connection_id);
if (!ast_strlen_zero(instance->uri_params)) {
ast_websocket_client_add_uri_params(instance->client, instance->uri_params);
}
instance->websocket = ast_websocket_client_connect(instance->client,
instance, ast_channel_name(ast), &result);
if (!instance->websocket || result != WS_OK) {
@@ -909,6 +969,8 @@ static void websocket_destructor(void *data)
ast_free(instance->leftover_data);
instance->leftover_data = NULL;
}
ast_free(instance->uri_params);
}
struct instance_proxy {
@@ -1099,20 +1161,50 @@ static int set_channel_variables(struct websocket_pvt *instance)
return 0;
}
static int validate_uri_parameters(const char *uri_params)
{
char *params = ast_strdupa(uri_params);
char *nvp = NULL;
char *nv = NULL;
/*
* uri_params should be a comma-separated list of key=value pairs.
* For example:
* name1=value1,name2=value2
* We're verifying that each name and value either doesn't need
* to be encoded or that it already is.
*/
while((nvp = ast_strsep(&params, ',', 0))) {
/* nvp will be name1=value1 */
while((nv = ast_strsep(&nvp, '=', 0))) {
/* nv will be either name1 or value1 */
if (!ast_uri_verify_encoded(nv)) {
return 0;
}
}
}
return 1;
}
enum {
OPT_WS_CODEC = (1 << 0),
OPT_WS_NO_AUTO_ANSWER = (1 << 1),
OPT_WS_URI_PARAM = (1 << 2),
};
enum {
OPT_ARG_WS_CODEC,
OPT_ARG_WS_NO_AUTO_ANSWER,
OPT_ARG_WS_URI_PARAM,
OPT_ARG_ARRAY_SIZE
};
AST_APP_OPTIONS(websocket_options, BEGIN_OPTIONS
AST_APP_OPTION_ARG('c', OPT_WS_CODEC, OPT_ARG_WS_CODEC),
AST_APP_OPTION('n', OPT_WS_NO_AUTO_ANSWER),
AST_APP_OPTION_ARG('v', OPT_WS_URI_PARAM, OPT_ARG_WS_URI_PARAM),
END_OPTIONS );
static struct ast_channel *webchan_request(const char *type,
@@ -1187,6 +1279,42 @@ static struct ast_channel *webchan_request(const char *type,
instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
if (ast_test_flag(&opts, OPT_WS_URI_PARAM)
&& !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
char *comma;
if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
ast_log(LOG_ERROR,
"%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
requestor_name);
goto failure;
}
ast_debug(3, "%s: Using URI parameters '%s'\n",
requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
if (!validate_uri_parameters(opt_args[OPT_ARG_WS_URI_PARAM])) {
ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
args.connection_id);
goto failure;
}
instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
comma = instance->uri_params;
/*
* The normal separator for query string components is an
* ampersand ('&') but the Dial app interprets them as additional
* channels to dial in parallel so we instruct users to separate
* the parameters with commas (',') instead. We now have to
* convert those commas back to ampersands.
*/
while ((comma = strchr(comma,','))) {
*comma = '&';
}
ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
}
chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
if (!chan) {
@@ -1246,7 +1374,6 @@ failure:
return NULL;
}
/*!
* \internal
*