<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"
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;
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) {
ast_free(instance->leftover_data);
instance->leftover_data = NULL;
}
+
+ ast_free(instance->uri_params);
}
struct instance_proxy {
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,
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) {
return NULL;
}
-
/*!
* \internal
*
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,
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;
}