diff --git a/channels/chan_websocket.c b/channels/chan_websocket.c
index 2f28b82d8f..a1c2aba924 100644
--- a/channels/chan_websocket.c
+++ b/channels/chan_websocket.c
@@ -31,6 +31,61 @@
core
***/
+/*** DOCUMENTATION
+
+ WebSocket Dial Strings:
+ Dial(WebSocket/connectionid[/websocket_options])
+ WebSocket Parameters:
+
+
+ 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 INCOMING
+
+
+ Options to control how the WebSocket channel behaves.
+
+
+
+ If not specified, the first codec from the caller's channel will be used.
+
+
+
+ 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 ANSWER command is
+ received from the remote app or the remote app calls the
+ /channels/answer ARI endpoint.
+
+
+
+ This option allows you to add additional parameters to the
+ outbound URI. The format is:
+ v(param1=value1,param2=value2...)
+
+ 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.
+
+
+
+
+ Examples:
+
+
+ same => n,Dial(WebSocket/connection1/c(sln16))
+
+
+ same => n,Dial(WebSocket/INCOMING/n)
+
+
+ same => n,Dial(WebSocket/connection1/v(${URIENCODE(vari able)}=${URIENCODE(${CHANNEL})},variable2=$(URIENCODE(${EXTEN})}))
+
+
+***/
#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
*
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index c4c37e3f67..50e20fb8f6 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -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.
diff --git a/include/asterisk/websocket_client.h b/include/asterisk/websocket_client.h
index b8a14d3a9f..f62907407f 100644
--- a/include/asterisk/websocket_client.h
+++ b/include/asterisk/websocket_client.h
@@ -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.
diff --git a/main/utils.c b/main/utils.c
index 03c0216ac2..5451ddc49d 100644
--- a/main/utils.c
+++ b/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;
diff --git a/res/res_websocket_client.c b/res/res_websocket_client.c
index 8ee0aecc7d..290021b6c1 100644
--- a/res/res_websocket_client.c
+++ b/res/res_websocket_client.c
@@ -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;
}