Asterisk - The Open Source Telephony Project GIT-master-8924258
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
ari_websocket_requests.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2025, Sangoma Technologies Corporation
5 *
6 * George Joseph <gjoseph@sangoma.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19#include "asterisk.h"
20
21#include "ari_websockets.h"
22#include "asterisk/ari.h"
23#include "asterisk/json.h"
24#include "asterisk/stasis_app.h"
25
31 char *uri;
34 struct ast_json *body;
35};
36
38{
39 if (!request) {
40 return;
41 }
42
43 ast_free(request->request_type);
44 ast_free(request->transaction_id);
45 ast_free(request->request_id);
46 ast_free(request->uri);
47 ast_free(request->content_type);
48 ast_variables_destroy(request->query_strings);
50
52}
53
54#define SET_RESPONSE_AND_EXIT(_reponse_code, _reponse_text, \
55 _reponse_msg, _remote_addr, _request, _request_msg) \
56({ \
57 RAII_VAR(char *, _msg_str, NULL, ast_json_free); \
58 if (_request_msg) { \
59 _msg_str = ast_json_dump_string_format(_request_msg, AST_JSON_COMPACT); \
60 if (!_msg_str) { \
61 response->response_code = 500; \
62 response->response_text = "Server error. Out of memory"; \
63 } \
64 } \
65 response->message = ast_json_pack("{ s:s }", \
66 "message", _reponse_msg); \
67 response->response_code = _reponse_code; \
68 response->response_text = _reponse_text; \
69 SCOPE_EXIT_LOG_RTN_VALUE(_request, LOG_WARNING, \
70 "%s: %s Request: %s\n", _remote_addr, _reponse_text, S_OR(_msg_str, "<none>")); \
71})
72
74 const char *remote_addr, struct ast_json *request_msg,
75 struct ast_ari_response *response, int debug_app)
76{
78 RAII_VAR(char *, body, NULL, ast_free);
79 enum ast_json_nvp_ast_vars_code nvp_code;
80 char *query_string_start = NULL;
81 SCOPE_ENTER(4, "%s: Parsing RESTRequest message\n", remote_addr);
82
83 response->response_code = 200;
84 response->response_text = "OK";
85
86 if (!request_msg) {
88 "Server error","No message to parse.",
89 remote_addr, request, NULL);
90 }
91
92 request = ast_calloc(1, sizeof(*request));
93 if (!request) {
95 "Server error","Out of memory",
96 remote_addr, request, NULL);
97 }
98
99 /* transaction_id is optional */
100 request->transaction_id = ast_strdup(
102 request_msg, "transaction_id")));
103
104 /* request_id is optional */
105 request->request_id = ast_strdup(
107 request_msg, "request_id")));
108
109 request->request_type = ast_strdup(
110 ast_json_string_get(ast_json_object_get(request_msg, "type")));
111 if (ast_strlen_zero(request->request_type)) {
113 "Bad request","No 'type' property.",
114 remote_addr, request, request_msg);
115 }
116
117 if (!ast_strings_equal(request->request_type, "RESTRequest")) {
119 "Bad request","Unknown request type.",
120 remote_addr, request, request_msg);
121 }
122
123 request->uri = ast_strdup(
124 ast_json_string_get(ast_json_object_get(request_msg, "uri")));
125 if (ast_strlen_zero(request->uri)) {
127 "Bad request","Empty or missing 'uri' property.",
128 remote_addr, request, request_msg);
129 }
130 if ((query_string_start = strchr(request->uri, '?')))
131 {
132 *query_string_start = '\0';
133 query_string_start++;
134 request->query_strings = ast_http_parse_post_form(
135 query_string_start, strlen(query_string_start), "application/x-www-form-urlencoded");
136 }
137
139 ast_json_string_get(ast_json_object_get(request_msg, "method")));
140 if (request->method == AST_HTTP_UNKNOWN) {
142 "Bad request","Unknown or missing 'method' property.",
143 remote_addr, request, request_msg);
144 }
145
146 /* query_strings is optional */
148 ast_json_object_get(request_msg, "query_strings"),
149 &request->query_strings);
150 if (nvp_code != AST_JSON_NVP_AST_VARS_CODE_SUCCESS &&
153 "Bad request","Unable to parse 'query_strings' array.",
154 remote_addr, request, request_msg);
155 }
156
157 request->body = ast_json_null();
158
160 ast_json_object_get(request_msg, "message_body")));
161
162 if (ast_strlen_zero(body)) {
164 "%s: Done parsing RESTRequest message.\n", remote_addr);
165 }
166
167 /* content_type is optional */
168 request->content_type = ast_strdup(
169 ast_json_string_get(ast_json_object_get(request_msg, "content_type")));
170
171 if (ast_strlen_zero(request->content_type)) {
173 "Bad request","No 'content_type' for 'message_body'.",
174 remote_addr, request, request_msg);
175 }
176
177 if (ast_strings_equal(request->content_type, "application/x-www-form-urlencoded")) {
178 struct ast_variable *vars = ast_http_parse_post_form(body, strlen(body),
179 request->content_type);
180 if (!vars) {
182 "Bad request","Unable to parse 'message_body' as 'application/x-www-form-urlencoded'.",
183 remote_addr, request, request_msg);
184 }
185 ast_variable_list_append(&request->query_strings, vars);
186 } else if (ast_strings_equal(request->content_type, "application/json")) {
187 struct ast_json_error error;
188 request->body = ast_json_load_buf(body, strlen(body), &error);
189 if (!request->body) {
191 "Bad request","Unable to parse 'message_body' as 'application/json'.",
192 remote_addr, request, request_msg);
193 }
194 } else {
196 "Bad request","Unknown content type.",
197 remote_addr, request, request_msg);
198 }
199
200 if (TRACE_ATLEAST(3) || debug_app) {
201 struct ast_variable *v = request->query_strings;
202 for (; v; v = v->next) {
203 ast_trace(-1, "Query string: %s=%s\n", v->name, v->value);
204 }
205 }
206
208 "%s: Done parsing RESTRequest message.\n", remote_addr);
209}
210
213 const char *remote_addr, const char *app_name,
215 struct ast_ari_response *response, int debug_app)
216{
217 struct ast_json *app_resp_json = NULL;
218 char *message = NULL;
219 SCOPE_ENTER(4, "%s: Sending REST response %d:%s for uri %s\n",
220 remote_addr, response->response_code, response->response_text,
221 request ? request->uri : "N/A");
222
223 if (response->fd >= 0) {
224 close(response->fd);
225 response->response_code = 406;
226 response->response_text = "Not Acceptable. Use HTTP GET";
227 } else if (response->message && !ast_json_is_null(response->message)) {
229 ast_json_unref(response->message);
230 }
231
232 app_resp_json = ast_json_pack(
233 "{s:s, s:s*, s:s*, s:i, s:s, s:s, s:s*, s:s* }",
234 "type", "RESTResponse",
235 "transaction_id", request ? S_OR(request->transaction_id, "") : "",
236 "request_id", request ? S_OR(request->request_id, "") : "",
237 "status_code", response->response_code,
238 "reason_phrase", response->response_text,
239 "uri", request ? S_OR(request->uri, "") : "",
240 "content_type", message ? "application/json" : NULL,
241 "message_body", message);
242
244 if (!app_resp_json || ast_json_is_null(app_resp_json)) {
246 "%s: Failed to pack JSON response for request %s\n",
247 remote_addr, request ? request->uri : "N/A");
248 }
249
251 app_name, app_resp_json, debug_app);
252
253 ast_json_unref(app_resp_json);
254
255 SCOPE_EXIT("%s: Done. response: %d : %s\n",
256 remote_addr,
257 response->response_code,
258 response->response_text);
259}
260
262 const char *remote_addr, struct ast_variable *upgrade_headers,
263 const char *app_name, struct ast_json *request_msg)
264{
265 int debug_app = stasis_app_get_debug_by_name(app_name);
267 struct ast_ari_response response = { .fd = -1, 0 };
268
269 SCOPE_ENTER(3, "%s: New WebSocket Msg\n", remote_addr);
270
271 if (TRACE_ATLEAST(3) || debug_app) {
272 char *str = ast_json_dump_string_format(request_msg, AST_JSON_PRETTY);
273 /* If we can't allocate a string, we can't respond to the client either. */
274 if (!str) {
275 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed to dump JSON request\n",
276 remote_addr);
277 }
278 ast_verbose("<--- Received ARI message from %s --->\n%s\n",
279 remote_addr, str);
281 }
282
284 parse_rest_request_msg, remote_addr, request_msg, &response, debug_app);
285
286 if (!request || response.response_code != 200) {
288 remote_addr, app_name, request, &response, debug_app);
289 SCOPE_EXIT_RTN_VALUE(0, "%s: Done with message\n", remote_addr);
290 }
291
292 /*
293 * We don't actually use the headers in the response
294 * but we have to allocate it because ast_ari_invoke
295 * and the resource handlers expect it.
296 */
297 response.headers = ast_str_create(80);
298 if (!response.headers) {
299 /* If we can't allocate a string, we can't respond to the client either. */
300 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed allocate headers string\n",
301 remote_addr);
302 }
303
305 NULL, request->uri, request->method, request->query_strings,
306 upgrade_headers, request->body, &response);
307
308 ast_free(response.headers);
309
310 if (response.no_response) {
311 SCOPE_EXIT_RTN_VALUE(0, "No response needed\n");
312 }
313
315 remote_addr, app_name, request, &response, debug_app);
316
317 SCOPE_EXIT_RTN_VALUE(0, "%s: Done with message\n", remote_addr);
318}
319
const char * str
Definition: app_jack.c:150
Asterisk RESTful API hooks.
@ ARI_INVOKE_SOURCE_WEBSOCKET
Definition: ari.h:149
enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *ser, enum ast_ari_invoke_source source, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
Definition: res_ari.c:635
static struct rest_request_msg * parse_rest_request_msg(const char *remote_addr, struct ast_json *request_msg, struct ast_ari_response *response, int debug_app)
int ari_websocket_process_request(struct ari_ws_session *ari_ws_session, const char *remote_addr, struct ast_variable *upgrade_headers, const char *app_name, struct ast_json *request_msg)
static void request_destroy(struct rest_request_msg *request)
#define SET_RESPONSE_AND_EXIT(_reponse_code, _reponse_text, _reponse_msg, _remote_addr, _request, _request_msg)
static void send_rest_response(struct ari_ws_session *ari_ws_session, const char *remote_addr, const char *app_name, struct rest_request_msg *request, struct ast_ari_response *response, int debug_app)
void ari_websocket_send_event(struct ari_ws_session *ari_ws_session, const char *app_name, struct ast_json *message, int debug_app)
Callback handler for Stasis application messages.
Internal API's for websockets.
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
static int request(void *obj)
Definition: chan_pjsip.c:2605
void ast_verbose(const char *fmt,...)
Definition: extconf.c:2206
#define TRACE_ATLEAST(level)
#define SCOPE_EXIT_RTN_VALUE(__return_value,...)
#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level,...)
#define SCOPE_CALL_WITH_RESULT(level, __var, __funcname,...)
#define SCOPE_ENTER(level,...)
#define SCOPE_CALL(level, __funcname,...)
#define SCOPE_EXIT(...)
#define SCOPE_EXIT_LOG_RTN(__log_level,...)
#define ast_trace(level,...)
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:58
@ AST_HTTP_UNKNOWN
Definition: http.h:59
enum ast_http_method ast_get_http_method_from_string(const char *method)
Return http method from string.
Definition: http.c:206
struct ast_variable * ast_http_parse_post_form(char *buf, int content_length, const char *content_type)
Get post variables from an application/x-www-form-urlencoded buffer.
Definition: http.c:1369
#define ast_variable_list_append(head, new_var)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
#define LOG_ERROR
#define LOG_WARNING
Asterisk JSON abstraction layer.
ast_json_nvp_ast_vars_code
Definition: json.h:1140
@ AST_JSON_NVP_AST_VARS_CODE_NO_INPUT
Input was NULL or empty.
Definition: json.h:1151
@ AST_JSON_NVP_AST_VARS_CODE_SUCCESS
Conversion successful.
Definition: json.h:1142
struct ast_json * ast_json_null(void)
Get the JSON null value.
Definition: json.c:248
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
void ast_json_free(void *p)
Asterisk's custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
enum ast_json_nvp_ast_vars_code ast_json_nvp_array_to_ast_variables(struct ast_json *json_array, struct ast_variable **variables)
Convert a ast_json array of name/value pairs into an ast_variable list.
Definition: json.c:864
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
@ AST_JSON_COMPACT
Definition: json.h:793
@ AST_JSON_PRETTY
Definition: json.h:795
struct ast_json * ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error)
Parse buffer with known length into a JSON object or array.
Definition: json.c:585
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
char * ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format)
Encode a JSON value to a string.
Definition: json.c:484
int ast_json_is_null(const struct ast_json *value)
Check if value is JSON null.
Definition: json.c:273
const char * app_name(struct ast_app *app)
Definition: pbx_app.c:463
#define NULL
Definition: resample.c:96
Stasis Application API. See Stasis Application API for detailed documentation.
int stasis_app_get_debug_by_name(const char *app_name)
Get debug status of an application.
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition: strings.c:238
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
struct ast_str * headers
Definition: ari.h:105
struct ast_json * message
Definition: ari.h:103
int response_code
Definition: ari.h:108
const char * response_text
Definition: ari.h:112
unsigned int no_response
Definition: ari.h:114
JSON parsing error information.
Definition: json.h:887
Abstract JSON element (object, array, string, int, ...).
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
struct ast_json * body
struct ast_variable * query_strings
enum ast_http_method method
int error(const char *format,...)
Definition: utils/frame.c:999
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941