mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-19 03:08:45 +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:
@@ -31,6 +31,61 @@
|
|||||||
<support_level>core</support_level>
|
<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.h"
|
||||||
|
|
||||||
#include "asterisk/app.h"
|
#include "asterisk/app.h"
|
||||||
@@ -69,6 +124,7 @@ struct websocket_pvt {
|
|||||||
pthread_t outbound_read_thread;
|
pthread_t outbound_read_thread;
|
||||||
size_t bytes_read;
|
size_t bytes_read;
|
||||||
size_t leftover_len;
|
size_t leftover_len;
|
||||||
|
char *uri_params;
|
||||||
char *leftover_data;
|
char *leftover_data;
|
||||||
int no_auto_answer;
|
int no_auto_answer;
|
||||||
int optimal_frame_size;
|
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_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
|
||||||
ast_channel_name(ast), dest, instance->connection_id);
|
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->websocket = ast_websocket_client_connect(instance->client,
|
||||||
instance, ast_channel_name(ast), &result);
|
instance, ast_channel_name(ast), &result);
|
||||||
if (!instance->websocket || result != WS_OK) {
|
if (!instance->websocket || result != WS_OK) {
|
||||||
@@ -909,6 +969,8 @@ static void websocket_destructor(void *data)
|
|||||||
ast_free(instance->leftover_data);
|
ast_free(instance->leftover_data);
|
||||||
instance->leftover_data = NULL;
|
instance->leftover_data = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ast_free(instance->uri_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct instance_proxy {
|
struct instance_proxy {
|
||||||
@@ -1099,20 +1161,50 @@ static int set_channel_variables(struct websocket_pvt *instance)
|
|||||||
return 0;
|
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 {
|
enum {
|
||||||
OPT_WS_CODEC = (1 << 0),
|
OPT_WS_CODEC = (1 << 0),
|
||||||
OPT_WS_NO_AUTO_ANSWER = (1 << 1),
|
OPT_WS_NO_AUTO_ANSWER = (1 << 1),
|
||||||
|
OPT_WS_URI_PARAM = (1 << 2),
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
OPT_ARG_WS_CODEC,
|
OPT_ARG_WS_CODEC,
|
||||||
OPT_ARG_WS_NO_AUTO_ANSWER,
|
OPT_ARG_WS_NO_AUTO_ANSWER,
|
||||||
|
OPT_ARG_WS_URI_PARAM,
|
||||||
OPT_ARG_ARRAY_SIZE
|
OPT_ARG_ARRAY_SIZE
|
||||||
};
|
};
|
||||||
|
|
||||||
AST_APP_OPTIONS(websocket_options, BEGIN_OPTIONS
|
AST_APP_OPTIONS(websocket_options, BEGIN_OPTIONS
|
||||||
AST_APP_OPTION_ARG('c', OPT_WS_CODEC, OPT_ARG_WS_CODEC),
|
AST_APP_OPTION_ARG('c', OPT_WS_CODEC, OPT_ARG_WS_CODEC),
|
||||||
AST_APP_OPTION('n', OPT_WS_NO_AUTO_ANSWER),
|
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 );
|
END_OPTIONS );
|
||||||
|
|
||||||
static struct ast_channel *webchan_request(const char *type,
|
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);
|
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,
|
chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
|
||||||
requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
|
requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
|
||||||
if (!chan) {
|
if (!chan) {
|
||||||
@@ -1246,7 +1374,6 @@ failure:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \internal
|
* \internal
|
||||||
*
|
*
|
||||||
|
@@ -414,6 +414,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);
|
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
|
/*! ast_xml_escape
|
||||||
\brief Escape reserved characters for use in XML.
|
\brief Escape reserved characters for use in XML.
|
||||||
|
|
||||||
|
@@ -74,6 +74,7 @@ struct ast_websocket_client {
|
|||||||
int tls_enabled; /*!< TLS enabled */
|
int tls_enabled; /*!< TLS enabled */
|
||||||
int verify_server_cert; /*!< Verify server certificate */
|
int verify_server_cert; /*!< Verify server certificate */
|
||||||
int verify_server_hostname; /*!< Verify server hostname */
|
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,
|
struct ast_websocket *ast_websocket_client_connect(struct ast_websocket_client *wc,
|
||||||
void *lock_obj, const char *display_name, enum ast_websocket_result *result);
|
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.
|
* \brief Force res_websocket_client to reload its configuration.
|
||||||
* \return 0 on success, -1 on failure.
|
* \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';
|
*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)
|
char *ast_escape_quoted(const char *string, char *outbuf, int buflen)
|
||||||
{
|
{
|
||||||
const char *ptr = string;
|
const char *ptr = string;
|
||||||
|
@@ -237,19 +237,40 @@ verify_server_hostname = no
|
|||||||
|
|
||||||
static struct ast_sorcery *sorcery = NULL;
|
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,
|
struct ast_websocket *ast_websocket_client_connect(struct ast_websocket_client *wc,
|
||||||
void *lock_obj, const char *display_name, enum ast_websocket_result *result)
|
void *lock_obj, const char *display_name, enum ast_websocket_result *result)
|
||||||
{
|
{
|
||||||
int reconnect_counter = wc->reconnect_attempts;
|
int reconnect_counter = wc->reconnect_attempts;
|
||||||
|
char *uri = NULL;
|
||||||
|
|
||||||
if (ast_strlen_zero(display_name)) {
|
if (ast_strlen_zero(display_name)) {
|
||||||
display_name = ast_sorcery_object_get_id(wc);
|
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) {
|
while (1) {
|
||||||
struct ast_websocket *astws = NULL;
|
struct ast_websocket *astws = NULL;
|
||||||
struct ast_websocket_client_options options = {
|
struct ast_websocket_client_options options = {
|
||||||
.uri = wc->uri,
|
.uri = S_OR(uri, wc->uri),
|
||||||
.protocols = wc->protocols,
|
.protocols = wc->protocols,
|
||||||
.username = wc->username,
|
.username = wc->username,
|
||||||
.password = wc->password,
|
.password = wc->password,
|
||||||
@@ -357,6 +378,11 @@ static void *wc_alloc(const char *id)
|
|||||||
return NULL;
|
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);
|
ast_debug(2, "%s: Allocated websocket client config\n", id);
|
||||||
return wc;
|
return wc;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user