mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +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]](/avatar/af2ab225b7c0eec44a8d0eba6b5c869a?size=40) github-actions[bot]
						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