return ast_ari_validate_variable;
}
+int ast_ari_validate_variables(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_variables = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("variables", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_variables = 1;
+ prop_is_valid = ast_ari_validate_object(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Variables field variables failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI Variables has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_variables) {
+ ast_log(LOG_ERROR, "ARI Variables missing required field variables\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_variables_fn(void)
+{
+ return ast_ari_validate_variables;
+}
+
int ast_ari_validate_endpoint(struct ast_json *json)
{
int res = 1;
*/
ari_validator ast_ari_validate_variable_fn(void);
+/*!
+ * \brief Validator for Variables.
+ *
+ * A dictionary of channel variables
+ *
+ * \param json JSON object to validate.
+ * \retval True (non-zero) if valid.
+ * \retval False (zero) if invalid.
+ */
+int ast_ari_validate_variables(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_variables().
+ */
+ari_validator ast_ari_validate_variables_fn(void);
+
/*!
* \brief Validator for Endpoint.
*
* - version: string (required)
* Variable
* - value: string (required)
+ * Variables
+ * - variables: object (required)
* Endpoint
* - channel_ids: List[string] (required)
* - resource: string (required)
ast_ari_response_ok(response, ast_json_ref(json));
}
+void ast_ari_channels_get_channel_vars(struct ast_variable *headers,
+ struct ast_ari_channels_get_channel_vars_args *args,
+ struct ast_ari_response *response)
+{
+ int res;
+ RAII_VAR(struct ast_json *, json, ast_json_object_create(), ast_json_unref);
+ RAII_VAR(struct ast_json *, inner_json, ast_json_object_create(), ast_json_unref);
+ RAII_VAR(struct ast_str *, value, ast_str_create(32), ast_free);
+ RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup);
+
+ ast_assert(response != NULL);
+
+ if (!json || !inner_json || !value) {
+ ast_ari_response_alloc_failed(response);
+ return;
+ }
+
+ if (args->variables_count == 0) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "At least one variable name is required");
+ return;
+ }
+
+ if (ast_strlen_zero(args->channel_id)) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "Channel ID is required");
+ return;
+ }
+
+ channel = ast_channel_get_by_name(args->channel_id);
+ if (!channel) {
+ ast_ari_response_error(
+ response, 404, "Channel Not Found",
+ "Provided channel was not found");
+ return;
+ }
+
+ for (int i = 0; i < args->variables_count; i++) {
+ struct ast_json *json_str;
+ char buf[strlen(args->variables[i]) + 1];
+ char *variable;
+
+ strcpy(buf, args->variables[i]);
+ variable = ast_strip(buf);
+ if (ast_strlen_zero(variable)) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "Variable names are required");
+ return;
+ }
+
+ if (variable[strlen(variable) - 1] == ')') {
+ if (ast_func_read2(channel, variable, &value, 0)) {
+ ast_ari_response_error(
+ response, 500, "Error With Function",
+ "Unable to read provided function");
+ return;
+ }
+ } else {
+ if (!ast_str_retrieve_variable(&value, 0, channel, NULL, variable)) {
+ ast_ari_response_error(
+ response, 404, "Variable Not Found",
+ "Provided variable was not found");
+ return;
+ }
+ }
+
+ json_str = ast_json_string_create(ast_str_buffer(value));
+ if (!json_str) {
+ ast_ari_response_alloc_failed(response);
+ return;
+ }
+
+ res = ast_json_object_set(inner_json, variable, json_str);
+ if (res) {
+ ast_ari_response_alloc_failed(response);
+ ast_json_unref(json_str);
+ return;
+ }
+ }
+
+ res = ast_json_object_set(json, "variables", ast_json_ref(inner_json));
+ if (res) {
+ ast_ari_response_alloc_failed(response);
+ return;
+ }
+
+ ast_ari_response_ok(response, ast_json_ref(json));
+}
+
void ast_ari_channels_set_channel_var(struct ast_variable *headers,
struct ast_ari_channels_set_channel_var_args *args,
struct ast_ari_response *response)
ast_ari_response_no_content(response);
}
+void ast_ari_channels_set_channel_vars(struct ast_variable *headers,
+ struct ast_ari_channels_set_channel_vars_args *args,
+ struct ast_ari_response *response)
+{
+ struct ast_json *json_variables;
+ struct ast_json_iter *it_json_var;
+ struct ast_variable *var = NULL;
+ RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+ RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup);
+ RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+ ast_assert(response != NULL);
+
+ if (!args->variables) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "The 'variables' field is required");
+ return;
+ }
+
+ channel = ast_channel_get_by_name(args->channel_id);
+ if (!channel) {
+ ast_ari_response_error(
+ response, 404, "Channel Not Found",
+ "Provided channel was not found");
+ return;
+ }
+
+ control = find_control(response, args->channel_id);
+ if (control == NULL) {
+ /* response filled in by find_control */
+ return;
+ }
+
+ json_variables = ast_json_object_get(args->variables, "variables");
+ for (it_json_var = ast_json_object_iter(json_variables); it_json_var;
+ it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) {
+ const char *key = ast_json_object_iter_key(it_json_var);
+ char buf[strlen(key) + 1];
+ char *stripped_key;
+ struct ast_json *json_value;
+ const char *value;
+ struct ast_variable *new_var;
+
+ strcpy(buf, key);
+ stripped_key = ast_strip(buf);
+ if (ast_strlen_zero(stripped_key)) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "Variable names are required");
+ return;
+ }
+
+ json_value = ast_json_object_iter_value(it_json_var);
+ if (ast_json_typeof(json_value) != AST_JSON_STRING) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "Variable values must be strings");
+ return;
+ }
+
+ value = ast_json_string_get(json_value);
+ if (!value) {
+ ast_ari_response_error(
+ response, 500, "Internal Server Error",
+ "Could not get string value from JSON string");
+ return;
+ }
+
+ new_var = ast_variable_new(stripped_key, value, "");
+ if (!new_var) {
+ ast_ari_response_error(
+ response, 500, "Internal Server Error",
+ "Could not create internal variable");
+ return;
+ }
+
+ /* Append to the tail */
+ var = ast_variable_list_append_hint(&variables, var, new_var);
+ }
+
+ /* We loop twice to preserve variable state if something goes wrong. If something
+ * goes wrong in this loop, something went VERY wrong.
+ */
+ for (var = variables; var; var = var->next) {
+ if (stasis_app_control_set_channel_var(control, var->name, var->value)) {
+ ast_ari_response_error(
+ response, 400, "Bad Request",
+ "Failed to execute function");
+ return;
+ }
+ }
+
+ ast_ari_response_no_content(response);
+}
+
static void ari_channels_handle_snoop_channel(
const char *args_channel_id,
const char *args_spy,
* \param[out] response HTTP response
*/
void ast_ari_channels_set_channel_var(struct ast_variable *headers, struct ast_ari_channels_set_channel_var_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_get_channel_vars() */
+struct ast_ari_channels_get_channel_vars_args {
+ /*! Channel's id */
+ const char *channel_id;
+ /*! Array of The channel variables or functions to get */
+ const char **variables;
+ /*! Length of variables array. */
+ size_t variables_count;
+ /*! Parsing context for variables. */
+ char *variables_parse;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/variables.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_get_channel_vars_parse_body(
+ struct ast_json *body,
+ struct ast_ari_channels_get_channel_vars_args *args);
+
+/*!
+ * \brief Get the value of multiple channel variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_get_channel_vars(struct ast_variable *headers, struct ast_ari_channels_get_channel_vars_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_set_channel_vars() */
+struct ast_ari_channels_set_channel_vars_args {
+ /*! Channel's id */
+ const char *channel_id;
+ /*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Ex. { "variables": { "CALLERID(name)": "Alice" } } */
+ struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/variables.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_set_channel_vars_parse_body(
+ struct ast_json *body,
+ struct ast_ari_channels_set_channel_vars_args *args);
+
+/*!
+ * \brief Set the values of multiple channel variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_set_channel_vars(struct ast_variable *headers, struct ast_ari_channels_set_channel_vars_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_channels_snoop_channel() */
struct ast_ari_channels_snoop_channel_args {
/*! Channel's id */
}
#endif /* AST_DEVMODE */
+fin: __attribute__((unused))
+ return;
+}
+int ast_ari_channels_get_channel_vars_parse_body(
+ struct ast_json *body,
+ struct ast_ari_channels_get_channel_vars_args *args)
+{
+ struct ast_json *field;
+ /* Parse query parameters out of it */
+ field = ast_json_object_get(body, "variables");
+ if (field) {
+ /* If they were silly enough to both pass in a query param and a
+ * JSON body, free up the query value.
+ */
+ ast_free(args->variables);
+ if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+ /* Multiple param passed as array */
+ size_t i;
+ args->variables_count = ast_json_array_size(field);
+ args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+
+ if (!args->variables) {
+ return -1;
+ }
+
+ for (i = 0; i < args->variables_count; ++i) {
+ args->variables[i] = ast_json_string_get(ast_json_array_get(field, i));
+ }
+ } else {
+ /* Multiple param passed as single value */
+ args->variables_count = 1;
+ args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+ if (!args->variables) {
+ return -1;
+ }
+ args->variables[0] = ast_json_string_get(field);
+ }
+ }
+ return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variables.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_get_channel_vars_cb(
+ struct ast_tcptls_session_instance *ser,
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+ struct ast_ari_channels_get_channel_vars_args args = {};
+ struct ast_variable *i;
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = get_params; i; i = i->next) {
+ if (strcmp(i->name, "variables") == 0) {
+ /* Parse comma separated list */
+ char *vals[MAX_VALS];
+ size_t j;
+
+ args.variables_parse = ast_strdup(i->value);
+ if (!args.variables_parse) {
+ ast_ari_response_alloc_failed(response);
+ goto fin;
+ }
+
+ if (strlen(args.variables_parse) == 0) {
+ /* ast_app_separate_args can't handle "" */
+ args.variables_count = 1;
+ vals[0] = args.variables_parse;
+ } else {
+ args.variables_count = ast_app_separate_args(
+ args.variables_parse, ',', vals,
+ ARRAY_LEN(vals));
+ }
+
+ if (args.variables_count == 0) {
+ ast_ari_response_alloc_failed(response);
+ goto fin;
+ }
+
+ if (args.variables_count >= MAX_VALS) {
+ ast_ari_response_error(response, 400,
+ "Bad Request",
+ "Too many values for variables");
+ goto fin;
+ }
+
+ args.variables = ast_malloc(sizeof(*args.variables) * args.variables_count);
+ if (!args.variables) {
+ ast_ari_response_alloc_failed(response);
+ goto fin;
+ }
+
+ for (j = 0; j < args.variables_count; ++j) {
+ args.variables[j] = (vals[j]);
+ }
+ } else
+ {}
+ }
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "channelId") == 0) {
+ args.channel_id = (i->value);
+ } else
+ {}
+ }
+ if (ast_ari_channels_get_channel_vars_parse_body(body, &args)) {
+ ast_ari_response_alloc_failed(response);
+ goto fin;
+ }
+ ast_ari_channels_get_channel_vars(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 0: /* Implementation is still a stub, or the code wasn't set */
+ is_valid = response->message == NULL;
+ break;
+ case 500: /* Internal Server Error */
+ case 501: /* Not Implemented */
+ case 400: /* Missing variables parameter. */
+ case 404: /* Channel or variable not found */
+ case 409: /* Channel not in a Stasis application */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_variables(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/variables\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/variables\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ ast_free(args.variables_parse);
+ ast_free(args.variables);
+ return;
+}
+int ast_ari_channels_set_channel_vars_parse_body(
+ struct ast_json *body,
+ struct ast_ari_channels_set_channel_vars_args *args)
+{
+ /* Parse query parameters out of it */
+ return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variables.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_set_channel_vars_cb(
+ struct ast_tcptls_session_instance *ser,
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+ struct ast_ari_channels_set_channel_vars_args args = {};
+ struct ast_variable *i;
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "channelId") == 0) {
+ args.channel_id = (i->value);
+ } else
+ {}
+ }
+ args.variables = body;
+ ast_ari_channels_set_channel_vars(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 0: /* Implementation is still a stub, or the code wasn't set */
+ is_valid = response->message == NULL;
+ break;
+ case 500: /* Internal Server Error */
+ case 501: /* Not Implemented */
+ case 400: /* Missing variables parameter. */
+ case 404: /* Channel not found */
+ case 409: /* Channel not in a Stasis application */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_void(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/variables\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/variables\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
fin: __attribute__((unused))
return;
}
.children = { }
};
/*! \brief REST handler for /api-docs/channels.json */
+static struct stasis_rest_handlers channels_channelId_variables = {
+ .path_segment = "variables",
+ .callbacks = {
+ [AST_HTTP_GET] = ast_ari_channels_get_channel_vars_cb,
+ [AST_HTTP_POST] = ast_ari_channels_set_channel_vars_cb,
+ },
+ .num_children = 0,
+ .children = { }
+};
+/*! \brief REST handler for /api-docs/channels.json */
static struct stasis_rest_handlers channels_channelId_snoop_snoopId = {
.path_segment = "snoopId",
.is_wildcard = 1,
[AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,
[AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,
},
- .num_children = 18,
- .children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_progress,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
+ .num_children = 19,
+ .children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_progress,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_variables,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
};
/*! \brief REST handler for /api-docs/channels.json */
static struct stasis_rest_handlers channels_externalMedia = {
}
}
},
+ "Variables": {
+ "id": "Variables",
+ "description": "A dictionary of channel variables",
+ "properties": {
+ "variables": {
+ "required": true,
+ "type": "object",
+ "description": "A dictionary of channel variables"
+ }
+ }
+ },
"ConfigTuple": {
"id": "ConfigTuple",
"description": "A key/value pair that makes up part of a configuration object.",
}
]
},
+ {
+ "path": "/channels/{channelId}/variables",
+ "description": "Multiple variables on a channel",
+ "operations": [
+ {
+ "httpMethod": "GET",
+ "since": [
+ "20.20.0",
+ "22.10.0",
+ "23.4.0"
+ ],
+ "summary": "Get the value of multiple channel variables or functions.",
+ "nickname": "getChannelVars",
+ "responseClass": "Variables",
+ "parameters": [
+ {
+ "name": "channelId",
+ "description": "Channel's id",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ },
+ {
+ "name": "variables",
+ "description": "The channel variables or functions to get",
+ "paramType": "query",
+ "required": true,
+ "allowMultiple": true,
+ "dataType": "string"
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 400,
+ "reason": "Missing variables parameter."
+ },
+ {
+ "code": 404,
+ "reason": "Channel or variable not found"
+ },
+ {
+ "code": 409,
+ "reason": "Channel not in a Stasis application"
+ }
+ ]
+ },
+ {
+ "httpMethod": "POST",
+ "since": [
+ "20.20.0",
+ "22.10.0",
+ "23.4.0"
+ ],
+ "summary": "Set the values of multiple channel variables or functions.",
+ "nickname": "setChannelVars",
+ "responseClass": "void",
+ "parameters": [
+ {
+ "name": "channelId",
+ "description": "Channel's id",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ },
+ {
+ "name": "variables",
+ "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+ "paramType": "body",
+ "required": false,
+ "dataType": "containers",
+ "allowMultiple": false
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 400,
+ "reason": "Missing variables parameter."
+ },
+ {
+ "code": 404,
+ "reason": "Channel not found"
+ },
+ {
+ "code": 409,
+ "reason": "Channel not in a Stasis application"
+ }
+ ]
+
+ }
+ ]
+ },
{
"path": "/channels/{channelId}/snoop",
"description": "Snoop (spy/whisper) on a channel",