mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-02 11:06:31 +00:00
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:
committed by
github-actions[bot]
parent
c1065d3444
commit
e33185dedb
@@ -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(¶ms, ',', 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
|
||||
*
|
||||
|
@@ -419,6 +419,19 @@ char *ast_uri_encode(const char *string, char *outbuf, int buflen, struct ast_fl
|
||||
*/
|
||||
void ast_uri_decode(char *s, struct ast_flags spec);
|
||||
|
||||
/*!
|
||||
* \brief Verify if a string is valid as a URI component
|
||||
*
|
||||
* This function checks if the string either doesn't need encoding
|
||||
* or is already properly URI encoded.
|
||||
* Valid characters are 'a-zA-Z0-9.+_-' and '%xx' escape sequences.
|
||||
*
|
||||
* \param string String to be checked
|
||||
* \retval 1 if the string is valid
|
||||
* \retval 0 if the string is not valid
|
||||
*/
|
||||
int ast_uri_verify_encoded(const char *string);
|
||||
|
||||
/*! ast_xml_escape
|
||||
\brief Escape reserved characters for use in XML.
|
||||
|
||||
|
@@ -74,6 +74,7 @@ struct ast_websocket_client {
|
||||
int tls_enabled; /*!< TLS enabled */
|
||||
int verify_server_cert; /*!< Verify server certificate */
|
||||
int verify_server_hostname; /*!< Verify server hostname */
|
||||
AST_STRING_FIELD_EXTENDED(uri_params); /*!< Additional URI parameters */
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -137,6 +138,15 @@ void ast_websocket_client_observer_remove(
|
||||
struct ast_websocket *ast_websocket_client_connect(struct ast_websocket_client *wc,
|
||||
void *lock_obj, const char *display_name, enum ast_websocket_result *result);
|
||||
|
||||
/*!
|
||||
* \brief Add additional parameters to the URI.
|
||||
*
|
||||
* \param wc A pointer to the ast_websocket_structure
|
||||
* \param uri_params A string containing URLENCODED parameters to append to the URI.
|
||||
*/
|
||||
void ast_websocket_client_add_uri_params(struct ast_websocket_client *wc,
|
||||
const char *uri_params);
|
||||
|
||||
/*!
|
||||
* \brief Force res_websocket_client to reload its configuration.
|
||||
* \return 0 on success, -1 on failure.
|
||||
|
36
main/utils.c
36
main/utils.c
@@ -778,6 +778,42 @@ void ast_uri_decode(char *s, struct ast_flags spec)
|
||||
*o = '\0';
|
||||
}
|
||||
|
||||
int ast_uri_verify_encoded(const char *string)
|
||||
{
|
||||
const char *ptr = string;
|
||||
size_t length;
|
||||
char *endl;
|
||||
|
||||
if (!string) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
length = strlen(string);
|
||||
endl = (char *)string + length;
|
||||
|
||||
while (*ptr) {
|
||||
if (*ptr == '%') {
|
||||
unsigned int tmp;
|
||||
/* Make sure there are at least 2 characters left to decode */
|
||||
if (ptr + 2 >= endl) {
|
||||
return 0;
|
||||
}
|
||||
/* Try to parse the next two characters as hex */
|
||||
if (sscanf(ptr + 1, "%2x", &tmp) != 1) {
|
||||
return 0;
|
||||
}
|
||||
/* All good, move past the '%' and the two hex digits */
|
||||
ptr += 3;
|
||||
} else if (!isalnum((unsigned char ) *ptr) && !strchr("-_.+", *ptr)) {
|
||||
return 0;
|
||||
} else {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
return 1; /* all characters are valid */
|
||||
}
|
||||
|
||||
char *ast_escape_quoted(const char *string, char *outbuf, int buflen)
|
||||
{
|
||||
const char *ptr = string;
|
||||
|
@@ -237,19 +237,40 @@ verify_server_hostname = no
|
||||
|
||||
static struct ast_sorcery *sorcery = NULL;
|
||||
|
||||
void ast_websocket_client_add_uri_params(struct ast_websocket_client *wc,
|
||||
const char *uri_params)
|
||||
{
|
||||
ast_string_field_set(wc, uri_params, uri_params);
|
||||
}
|
||||
|
||||
struct ast_websocket *ast_websocket_client_connect(struct ast_websocket_client *wc,
|
||||
void *lock_obj, const char *display_name, enum ast_websocket_result *result)
|
||||
{
|
||||
int reconnect_counter = wc->reconnect_attempts;
|
||||
char *uri = NULL;
|
||||
|
||||
if (ast_strlen_zero(display_name)) {
|
||||
display_name = ast_sorcery_object_get_id(wc);
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(wc->uri_params)) {
|
||||
/*
|
||||
* If the configured URI doesn't already contain parameters, we append the
|
||||
* new ones to the URI path component with '?'. If it does, we append the
|
||||
* new ones to the existing ones with a '&'.
|
||||
*/
|
||||
char sep = '?';
|
||||
uri = ast_alloca(strlen(wc->uri) + strlen(wc->uri_params) + 2);
|
||||
if (strchr(wc->uri, '?')) {
|
||||
sep = '&';
|
||||
}
|
||||
sprintf(uri, "%s%c%s", wc->uri, sep, wc->uri_params); /*Safe */
|
||||
}
|
||||
|
||||
while (1) {
|
||||
struct ast_websocket *astws = NULL;
|
||||
struct ast_websocket_client_options options = {
|
||||
.uri = wc->uri,
|
||||
.uri = S_OR(uri, wc->uri),
|
||||
.protocols = wc->protocols,
|
||||
.username = wc->username,
|
||||
.password = wc->password,
|
||||
@@ -357,6 +378,11 @@ static void *wc_alloc(const char *id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ast_string_field_init_extended(wc, uri_params) != 0) {
|
||||
ao2_cleanup(wc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_debug(2, "%s: Allocated websocket client config\n", id);
|
||||
return wc;
|
||||
}
|
||||
|
Reference in New Issue
Block a user