Shuffle RESTful URL's around.
[asterisk/asterisk.git] / res / res_stasis_http.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012 - 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.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 /*! \file
20  *
21  * \brief HTTP binding for the Stasis API
22  * \author David M. Lee, II <dlee@digium.com>
23  *
24  * The API itself is documented using <a
25  * href="https://developers.helloreverb.com/swagger/">Swagger</a>, a lightweight
26  * mechanism for documenting RESTful API's using JSON. This allows us to use <a
27  * href="https://github.com/wordnik/swagger-ui">swagger-ui</a> to provide
28  * executable documentation for the API, generate client bindings in different
29  * <a href="https://github.com/asterisk/asterisk_rest_libraries">languages</a>,
30  * and generate a lot of the boilerplate code for implementing the RESTful
31  * bindings. The API docs live in the \c rest-api/ directory.
32  *
33  * The RESTful bindings are generated from the Swagger API docs using a set of
34  * <a href="http://mustache.github.io/mustache.5.html">Mustache</a> templates.
35  * The code generator is written in Python, and uses the Python implementation
36  * <a href="https://github.com/defunkt/pystache">pystache</a>. Pystache has no
37  * dependencies, and be installed easily using \c pip. Code generation code
38  * lives in \c rest-api-templates/.
39  *
40  * The generated code reduces a lot of boilerplate when it comes to handling
41  * HTTP requests. It also helps us have greater consistency in the REST API.
42  *
43  * The structure of the generated code is:
44  *
45  *  - res/stasis_http/resource_{resource}.h
46  *    - For each operation in the resouce, a generated argument structure
47  *      (holding the parsed arguments from the request) and function
48  *      declarations (to implement in res/stasis_http/resource_{resource}.c)
49  *  - res_stasis_http_{resource}.c
50  *    - A set of \ref stasis_rest_callback functions, which glue the two
51  *      together. They parse out path variables and request parameters to
52  *      populate a specific \c *_args which is passed to the specific request
53  *      handler (in res/stasis_http/resource_{resource}.c)
54  *    - A tree of \ref stasis_rest_handlers for routing requests to its
55  *      \ref stasis_rest_callback
56  *
57  * The basic flow of an HTTP request is:
58  *
59  *  - stasis_http_callback()
60  *    1. Initial request validation
61  *    2. Routes as either a doc request (stasis_http_get_docs) or API
62  *       request (stasis_http_invoke)
63  *       - stasis_http_invoke()
64  *         1. Further request validation
65  *         2. Routes the request through the tree of generated
66  *            \ref stasis_rest_handlers.
67  *         3. Dispatch to the generated callback
68  *            - \c stasis_http_*_cb
69  *              1. Populate \c *_args struct with path and get params
70  *              2. Invoke the request handler
71  *    3. Validates and sends response
72  */
73
74 /*** MODULEINFO
75         <depend type="module">res_http_websocket</depend>
76         <support_level>core</support_level>
77  ***/
78
79 /*** DOCUMENTATION
80         <configInfo name="res_stasis_http" language="en_US">
81                 <synopsis>HTTP binding for the Stasis API</synopsis>
82                 <configFile name="stasis_http.conf">
83                         <configObject name="global">
84                                 <synopsis>Global configuration settings</synopsis>
85                                 <configOption name="enabled">
86                                         <synopsis>Enable/disable the stasis-http module</synopsis>
87                                 </configOption>
88                                 <configOption name="pretty">
89                                         <synopsis>Responses from stasis-http are formatted to be human readable</synopsis>
90                                 </configOption>
91                         </configObject>
92                 </configFile>
93         </configInfo>
94 ***/
95
96 #include "asterisk.h"
97
98 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
99
100 #include "asterisk/module.h"
101 #include "asterisk/paths.h"
102 #include "asterisk/stasis_http.h"
103 #include "asterisk/config_options.h"
104
105 #include <string.h>
106 #include <sys/stat.h>
107 #include <unistd.h>
108
109 /*! \brief Global configuration options for stasis http. */
110 struct conf_global_options {
111         /*! Enabled by default, disabled if false. */
112         int enabled:1;
113         /*! Encoding format used during output (default compact). */
114         enum ast_json_encoding_format format;
115 };
116
117 /*! \brief All configuration options for stasis http. */
118 struct conf {
119         /*! The general section configuration options. */
120         struct conf_global_options *global;
121 };
122
123 /*! \brief Locking container for safe configuration access. */
124 static AO2_GLOBAL_OBJ_STATIC(confs);
125
126 /*! \brief Mapping of the stasis http conf struct's globals to the
127  *         general context in the config file. */
128 static struct aco_type global_option = {
129         .type = ACO_GLOBAL,
130         .name = "global",
131         .item_offset = offsetof(struct conf, global),
132         .category = "^general$",
133         .category_match = ACO_WHITELIST
134 };
135
136 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
137
138 /*! \brief Disposes of the stasis http conf object */
139 static void conf_destructor(void *obj)
140 {
141     struct conf *cfg = obj;
142     ao2_cleanup(cfg->global);
143 }
144
145 /*! \brief Creates the statis http conf object. */
146 static void *conf_alloc(void)
147 {
148     struct conf *cfg;
149
150     if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
151         return NULL;
152     }
153
154     if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) {
155         ao2_ref(cfg, -1);
156         return NULL;
157     }
158     return cfg;
159 }
160
161 /*! \brief The conf file that's processed for the module. */
162 static struct aco_file conf_file = {
163         /*! The config file name. */
164         .filename = "stasis_http.conf",
165         /*! The mapping object types to be processed. */
166         .types = ACO_TYPES(&global_option),
167 };
168
169 CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
170                      .files = ACO_FILES(&conf_file));
171
172 /*! \brief Bitfield handler since it is not possible to take address. */
173 static int conf_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
174 {
175         struct conf_global_options *global = obj;
176
177         if (!strcasecmp(var->name, "enabled")) {
178                 global->enabled = ast_true(var->value);
179         } else {
180                 return -1;
181         }
182
183         return 0;
184 }
185
186 /*! \brief Encoding format handler converts from boolean to enum. */
187 static int encoding_format_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
188 {
189         struct conf_global_options *global = obj;
190
191         if (!strcasecmp(var->name, "pretty")) {
192                 global->format = ast_true(var->value) ? AST_JSON_PRETTY : AST_JSON_COMPACT;
193         } else {
194                 return -1;
195         }
196
197         return 0;
198 }
199
200 /*! \brief Helper function to check if module is enabled. */
201 static char is_enabled(void)
202 {
203         RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
204
205         return cfg->global->enabled;
206 }
207
208 /*! Lock for \ref root_handler */
209 static ast_mutex_t root_handler_lock;
210
211 /*! Handler for root RESTful resource. */
212 static struct stasis_rest_handlers *root_handler;
213
214 /*! Pre-defined message for allocation failures. */
215 static struct ast_json *oom_json;
216
217 struct ast_json *ari_oom_json(void)
218 {
219         return oom_json;
220 }
221
222 int stasis_http_add_handler(struct stasis_rest_handlers *handler)
223 {
224         RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
225         size_t old_size, new_size;
226
227         SCOPED_MUTEX(lock, &root_handler_lock);
228
229         old_size = sizeof(*new_handler) +
230                 root_handler->num_children * sizeof(handler);
231         new_size = old_size + sizeof(handler);
232
233         new_handler = ao2_alloc(new_size, NULL);
234         if (!new_handler) {
235                 return -1;
236         }
237         memcpy(new_handler, root_handler, old_size);
238         new_handler->children[new_handler->num_children++] = handler;
239
240         ao2_cleanup(root_handler);
241         ao2_ref(new_handler, +1);
242         root_handler = new_handler;
243         ast_module_ref(ast_module_info->self);
244         return 0;
245 }
246
247 int stasis_http_remove_handler(struct stasis_rest_handlers *handler)
248 {
249         RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
250         size_t size, i, j;
251
252         ast_assert(root_handler != NULL);
253
254         ast_mutex_lock(&root_handler_lock);
255         size = sizeof(*new_handler) +
256                 root_handler->num_children * sizeof(handler);
257
258         new_handler = ao2_alloc(size, NULL);
259         if (!new_handler) {
260                 return -1;
261         }
262         memcpy(new_handler, root_handler, sizeof(*new_handler));
263
264         for (i = 0, j = 0; i < root_handler->num_children; ++i) {
265                 if (root_handler->children[i] == handler) {
266                         ast_module_unref(ast_module_info->self);
267                         continue;
268                 }
269                 new_handler->children[j++] = root_handler->children[i];
270         }
271         new_handler->num_children = j;
272
273         ao2_cleanup(root_handler);
274         ao2_ref(new_handler, +1);
275         root_handler = new_handler;
276         ast_mutex_unlock(&root_handler_lock);
277         return 0;
278 }
279
280 static struct stasis_rest_handlers *get_root_handler(void)
281 {
282         SCOPED_MUTEX(lock, &root_handler_lock);
283         ao2_ref(root_handler, +1);
284         return root_handler;
285 }
286
287 static struct stasis_rest_handlers *root_handler_create(void)
288 {
289         RAII_VAR(struct stasis_rest_handlers *, handler, NULL, ao2_cleanup);
290
291         handler = ao2_alloc(sizeof(*handler), NULL);
292         if (!handler) {
293                 return NULL;
294         }
295         handler->path_segment = "ari";
296
297         ao2_ref(handler, +1);
298         return handler;
299 }
300
301 void stasis_http_response_error(struct stasis_http_response *response,
302                                 int response_code,
303                                 const char *response_text,
304                                 const char *message_fmt, ...)
305 {
306         RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
307         va_list ap;
308
309         va_start(ap, message_fmt);
310         message = ast_json_vstringf(message_fmt, ap);
311         response->message = ast_json_pack("{s: o}",
312                                           "message", ast_json_ref(message));
313         response->response_code = response_code;
314         response->response_text = response_text;
315 }
316
317 void stasis_http_response_ok(struct stasis_http_response *response,
318                              struct ast_json *message)
319 {
320         response->message = ast_json_ref(message);
321         response->response_code = 200;
322         response->response_text = "OK";
323 }
324
325 void stasis_http_response_no_content(struct stasis_http_response *response)
326 {
327         response->message = NULL;
328         response->response_code = 204;
329         response->response_text = "No Content";
330 }
331
332 void stasis_http_response_alloc_failed(struct stasis_http_response *response)
333 {
334         response->message = ast_json_ref(oom_json);
335         response->response_code = 500;
336         response->response_text = "Internal Server Error";
337 }
338
339 void stasis_http_response_created(struct stasis_http_response *response,
340         const char *url, struct ast_json *message)
341 {
342         response->message = ast_json_ref(message);
343         response->response_code = 201;
344         response->response_text = "Created";
345         ast_str_append(&response->headers, 0, "Location: %s\r\n", url);
346 }
347
348 static void add_allow_header(struct stasis_rest_handlers *handler,
349                              struct stasis_http_response *response)
350 {
351         enum ast_http_method m;
352         ast_str_append(&response->headers, 0,
353                        "Allow: OPTIONS");
354         for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
355                 if (handler->callbacks[m] != NULL) {
356                         ast_str_append(&response->headers, 0,
357                                        ",%s", ast_get_http_method(m));
358                 }
359         }
360         ast_str_append(&response->headers, 0, "\r\n");
361 }
362
363 #define ACR_METHOD "Access-Control-Request-Method"
364 #define ACR_HEADERS "Access-Control-Request-Headers"
365 #define ACA_METHODS "Access-Control-Allow-Methods"
366 #define ACA_HEADERS "Access-Control-Allow-Headers"
367
368 /*!
369  * \brief Handle OPTIONS request, mainly for CORS preflight requests.
370  *
371  * Some browsers will send this prior to non-simple methods (i.e. DELETE).
372  * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2.
373  */
374 static void handle_options(struct stasis_rest_handlers *handler,
375                            struct ast_variable *headers,
376                            struct stasis_http_response *response)
377 {
378         struct ast_variable *header;
379         char const *acr_method = NULL;
380         char const *acr_headers = NULL;
381         char const *origin = NULL;
382
383         RAII_VAR(struct ast_str *, allow, NULL, ast_free);
384         enum ast_http_method m;
385         int allowed = 0;
386
387         /* Regular OPTIONS response */
388         add_allow_header(handler, response);
389         response->response_code = 204;
390         response->response_text = "No Content";
391         response->message = NULL;
392
393         /* Parse CORS headers */
394         for (header = headers; header != NULL; header = header->next) {
395                 if (strcmp(ACR_METHOD, header->name) == 0) {
396                         acr_method = header->value;
397                 } else if (strcmp(ACR_HEADERS, header->name) == 0) {
398                         acr_headers = header->value;
399                 } else if (strcmp("Origin", header->name) == 0) {
400                         origin = header->value;
401                 }
402         }
403
404         /* CORS 6.2, #1 - "If the Origin header is not present terminate this
405          * set of steps.
406          */
407         if (origin == NULL) {
408                 return;
409         }
410
411         /* CORS 6.2, #2 - "If the value of the Origin header is not a
412          * case-sensitive match for any of the values in list of origins do not
413          * set any additional headers and terminate this set of steps.
414          *
415          * "Always matching is acceptable since the list of origins can be
416          * unbounded.
417          *
418          * "The Origin header can only contain a single origin as the user agent
419          * will not follow redirects.
420          *
421          * TODO - pull list of allowed origins from config
422          */
423
424         /* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header
425          * or if parsing failed, do not set any additional headers and terminate
426          * this set of steps."
427          */
428         if (acr_method == NULL) {
429                 return;
430         }
431
432         /* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers
433          * headers let header field-names be the empty list."
434          */
435         if (acr_headers == NULL) {
436                 acr_headers = "";
437         }
438
439         /* CORS 6.2, #5 - "If method is not a case-sensitive match for any of
440          * the values in list of methods do not set any additional headers and
441          * terminate this set of steps."
442          */
443         allow = ast_str_create(20);
444
445         if (!allow) {
446                 stasis_http_response_alloc_failed(response);
447                 return;
448         }
449
450         /* Go ahead and build the ACA_METHODS header at the same time */
451         for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
452                 if (handler->callbacks[m] != NULL) {
453                         char const *m_str = ast_get_http_method(m);
454                         if (strcmp(m_str, acr_method) == 0) {
455                                 allowed = 1;
456                         }
457                         ast_str_append(&allow, 0, ",%s", m_str);
458                 }
459         }
460
461         if (!allowed) {
462                 return;
463         }
464
465         /* CORS 6.2 #6 - "If any of the header field-names is not a ASCII
466          * case-insensitive match for any of the values in list of headers do
467          * not set any additional headers and terminate this set of steps.
468          *
469          * "Note: Always matching is acceptable since the list of headers can be
470          * unbounded."
471          */
472
473         /* CORS 6.2 #7 - "If the resource supports credentials add a single
474          * Access-Control-Allow-Origin header, with the value of the Origin
475          * header as value, and add a single Access-Control-Allow-Credentials
476          * header with the case-sensitive string "true" as value."
477          *
478          * Added by process_cors_request() earlier in the request.
479          */
480
481         /* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age
482          * header..."
483          */
484
485         /* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers
486          * consisting of (a subset of) the list of methods."
487          */
488         ast_str_append(&response->headers, 0, "%s: OPTIONS,%s\r\n",
489                        ACA_METHODS, ast_str_buffer(allow));
490
491
492         /* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers
493          * consisting of (a subset of) the list of headers.
494          *
495          * "Since the list of headers can be unbounded simply returning headers
496          * can be enough."
497          */
498         if (!ast_strlen_zero(acr_headers)) {
499                 ast_str_append(&response->headers, 0, "%s: %s\r\n",
500                                ACA_HEADERS, acr_headers);
501         }
502 }
503
504 void stasis_http_invoke(struct ast_tcptls_session_instance *ser,
505         const char *uri, enum ast_http_method method,
506         struct ast_variable *get_params, struct ast_variable *headers,
507         struct stasis_http_response *response)
508 {
509         RAII_VAR(char *, response_text, NULL, ast_free);
510         RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup);
511         struct stasis_rest_handlers *handler;
512         struct ast_variable *path_vars = NULL;
513         char *path = ast_strdupa(uri);
514         char *path_segment;
515         stasis_rest_callback callback;
516
517         root = handler = get_root_handler();
518         ast_assert(root != NULL);
519
520         while ((path_segment = strsep(&path, "/")) && (strlen(path_segment) > 0)) {
521                 struct stasis_rest_handlers *found_handler = NULL;
522                 int i;
523                 ast_uri_decode(path_segment, ast_uri_http_legacy);
524                 ast_debug(3, "Finding handler for %s\n", path_segment);
525                 for (i = 0; found_handler == NULL && i < handler->num_children; ++i) {
526                         struct stasis_rest_handlers *child = handler->children[i];
527
528                         ast_debug(3, "  Checking %s\n", child->path_segment);
529                         if (child->is_wildcard) {
530                                 /* Record the path variable */
531                                 struct ast_variable *path_var = ast_variable_new(child->path_segment, path_segment, __FILE__);
532                                 path_var->next = path_vars;
533                                 path_vars = path_var;
534                                 found_handler = child;
535                         } else if (strcmp(child->path_segment, path_segment) == 0) {
536                                 found_handler = child;
537                         }
538                 }
539
540                 if (found_handler == NULL) {
541                         /* resource not found */
542                         ast_debug(3, "  Handler not found\n");
543                         stasis_http_response_error(
544                                 response, 404, "Not Found",
545                                 "Resource not found");
546                         return;
547                 } else {
548                         ast_debug(3, "  Got it!\n");
549                         handler = found_handler;
550                 }
551         }
552
553         ast_assert(handler != NULL);
554         if (method == AST_HTTP_OPTIONS) {
555                 handle_options(handler, headers, response);
556                 return;
557         }
558
559         if (method < 0 || method >= AST_HTTP_MAX_METHOD) {
560                 add_allow_header(handler, response);
561                 stasis_http_response_error(
562                         response, 405, "Method Not Allowed",
563                         "Invalid method");
564                 return;
565         }
566
567         if (handler->ws_server && method == AST_HTTP_GET) {
568                 /* WebSocket! */
569                 struct ast_http_uri fake_urih = {
570                         .data = handler->ws_server,
571                 };
572                 ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
573                         headers);
574                 /* Since the WebSocket code handles the connection, we shouldn't
575                  * do anything else; setting no_response */
576                 response->no_response = 1;
577                 return;
578         }
579
580         callback = handler->callbacks[method];
581         if (callback == NULL) {
582                 add_allow_header(handler, response);
583                 stasis_http_response_error(
584                         response, 405, "Method Not Allowed",
585                         "Invalid method");
586                 return;
587         }
588
589         callback(get_params, path_vars, headers, response);
590         if (response->message == NULL && response->response_code == 0) {
591                 /* Really should not happen */
592                 ast_assert(0);
593                 stasis_http_response_error(
594                         response, 418, "I'm a teapot",
595                         "Method not implemented");
596         }
597 }
598
599 void stasis_http_get_docs(const char *uri, struct ast_variable *headers,
600                           struct stasis_http_response *response)
601 {
602         RAII_VAR(struct ast_str *, absolute_path_builder, NULL, ast_free);
603         RAII_VAR(char *, absolute_api_dirname, NULL, free);
604         RAII_VAR(char *, absolute_filename, NULL, free);
605         struct ast_json *obj = NULL;
606         struct ast_variable *host = NULL;
607         struct ast_json_error error = {};
608         struct stat file_stat;
609
610         ast_debug(3, "%s(%s)\n", __func__, uri);
611
612         absolute_path_builder = ast_str_create(80);
613         if (absolute_path_builder == NULL) {
614                 stasis_http_response_alloc_failed(response);
615                 return;
616         }
617
618         /* absolute path to the rest-api directory */
619         ast_str_append(&absolute_path_builder, 0, "%s", ast_config_AST_DATA_DIR);
620         ast_str_append(&absolute_path_builder, 0, "/rest-api/");
621         absolute_api_dirname = realpath(ast_str_buffer(absolute_path_builder), NULL);
622         if (absolute_api_dirname == NULL) {
623                 ast_log(LOG_ERROR, "Error determining real directory for rest-api\n");
624                 stasis_http_response_error(
625                         response, 500, "Internal Server Error",
626                         "Cannot find rest-api directory");
627                 return;
628         }
629
630         /* absolute path to the requested file */
631         ast_str_append(&absolute_path_builder, 0, "%s", uri);
632         absolute_filename = realpath(ast_str_buffer(absolute_path_builder), NULL);
633         if (absolute_filename == NULL) {
634                 switch (errno) {
635                 case ENAMETOOLONG:
636                 case ENOENT:
637                 case ENOTDIR:
638                         stasis_http_response_error(
639                                 response, 404, "Not Found",
640                                 "Resource not found");
641                         break;
642                 case EACCES:
643                         stasis_http_response_error(
644                                 response, 403, "Forbidden",
645                                 "Permission denied");
646                         break;
647                 default:
648                         ast_log(LOG_ERROR,
649                                 "Error determining real path for uri '%s': %s\n",
650                                 uri, strerror(errno));
651                         stasis_http_response_error(
652                                 response, 500, "Internal Server Error",
653                                 "Cannot find file");
654                         break;
655                 }
656                 return;
657         }
658
659         if (!ast_begins_with(absolute_filename, absolute_api_dirname)) {
660                 /* HACKERZ! */
661                 ast_log(LOG_ERROR,
662                         "Invalid attempt to access '%s' (not in %s)\n",
663                         absolute_filename, absolute_api_dirname);
664                 stasis_http_response_error(
665                         response, 404, "Not Found",
666                         "Resource not found");
667                 return;
668         }
669
670         if (stat(absolute_filename, &file_stat) == 0) {
671                 if (!(file_stat.st_mode & S_IFREG)) {
672                         /* Not a file */
673                         stasis_http_response_error(
674                                 response, 403, "Forbidden",
675                                 "Invalid access");
676                         return;
677                 }
678         } else {
679                 /* Does not exist */
680                 stasis_http_response_error(
681                         response, 404, "Not Found",
682                         "Resource not found");
683                 return;
684         }
685
686         /* Load resource object from file */
687         obj = ast_json_load_new_file(absolute_filename, &error);
688         if (obj == NULL) {
689                 ast_log(LOG_ERROR, "Error parsing resource file: %s:%d(%d) %s\n",
690                         error.source, error.line, error.column, error.text);
691                 stasis_http_response_error(
692                         response, 500, "Internal Server Error",
693                         "Yikes! Cannot parse resource");
694                 return;
695         }
696
697         /* Update the basePath properly */
698         if (ast_json_object_get(obj, "basePath") != NULL) {
699                 for (host = headers; host; host = host->next) {
700                         if (strcasecmp(host->name, "Host") == 0) {
701                                 break;
702                         }
703                 }
704                 if (host != NULL) {
705                         ast_json_object_set(
706                                 obj, "basePath",
707                                 ast_json_stringf("http://%s/ari", host->value));
708                 } else {
709                         /* Without the host, we don't have the basePath */
710                         ast_json_object_del(obj, "basePath");
711                 }
712         }
713
714         stasis_http_response_ok(response, obj);
715 }
716
717 static void remove_trailing_slash(const char *uri,
718                                   struct stasis_http_response *response)
719 {
720         char *slashless = ast_strdupa(uri);
721         slashless[strlen(slashless) - 1] = '\0';
722
723         /* While it's tempting to redirect the client to the slashless URL,
724          * that is problematic. A 302 Found is the most appropriate response,
725          * but most clients issue a GET on the location you give them,
726          * regardless of the method of the original request.
727          *
728          * While there are some ways around this, it gets into a lot of client
729          * specific behavior and corner cases in the HTTP standard. There's also
730          * very little practical benefit of redirecting; only GET and HEAD can
731          * be redirected automagically; all other requests "MUST NOT
732          * automatically redirect the request unless it can be confirmed by the
733          * user, since this might change the conditions under which the request
734          * was issued."
735          *
736          * Given all of that, a 404 with a nice message telling them what to do
737          * is probably our best bet.
738          */
739         stasis_http_response_error(response, 404, "Not Found",
740                 "ARI URLs do not end with a slash. Try /ari/%s", slashless);
741 }
742
743 /*!
744  * \brief Handle CORS headers for simple requests.
745  *
746  * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1.
747  */
748 static void process_cors_request(struct ast_variable *headers,
749                                  struct stasis_http_response *response)
750 {
751         char const *origin = NULL;
752         struct ast_variable *header;
753
754         /* Parse CORS headers */
755         for (header = headers; header != NULL; header = header->next) {
756                 if (strcmp("Origin", header->name) == 0) {
757                         origin = header->value;
758                 }
759         }
760
761         /* CORS 6.1, #1 - "If the Origin header is not present terminate this
762          * set of steps."
763          */
764         if (origin == NULL) {
765                 return;
766         }
767
768         /* CORS 6.1, #2 - "If the value of the Origin header is not a
769          * case-sensitive match for any of the values in list of origins, do not
770          * set any additional headers and terminate this set of steps.
771          *
772          * "Note: Always matching is acceptable since the list of origins can be
773          * unbounded."
774          *
775          * TODO - pull list of allowed origins from config
776          */
777
778         /* CORS 6.1, #3 - "If the resource supports credentials add a single
779          * Access-Control-Allow-Origin header, with the value of the Origin
780          * header as value, and add a single Access-Control-Allow-Credentials
781          * header with the case-sensitive string "true" as value.
782          *
783          * "Otherwise, add a single Access-Control-Allow-Origin header, with
784          * either the value of the Origin header or the string "*" as value."
785          *
786          * TODO - when we add authentication, this will change to
787          * Access-Control-Allow-Credentials.
788          */
789         ast_str_append(&response->headers, 0,
790                        "Access-Control-Allow-Origin: %s\r\n", origin);
791
792         /* CORS 6.1, #4 - "If the list of exposed headers is not empty add one
793          * or more Access-Control-Expose-Headers headers, with as values the
794          * header field names given in the list of exposed headers."
795          *
796          * No exposed headers; skipping
797          */
798 }
799
800
801 /*!
802  * \internal
803  * \brief Stasis HTTP handler.
804  *
805  * This handler takes the HTTP request and turns it into the appropriate
806  * RESTful request (conversion to JSON, routing, etc.)
807  *
808  * \param ser TCP session.
809  * \param urih URI handler.
810  * \param uri URI requested.
811  * \param method HTTP method.
812  * \param get_params HTTP \c GET params.
813  * \param headers HTTP headers.
814  */
815 static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
816                                 const struct ast_http_uri *urih,
817                                 const char *uri,
818                                 enum ast_http_method method,
819                                 struct ast_variable *get_params,
820                                 struct ast_variable *headers)
821 {
822         RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
823         RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free);
824         RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free);
825         struct stasis_http_response response = {};
826         int ret = 0;
827
828         if (!response_headers || !response_body) {
829                 return -1;
830         }
831
832         response.headers = ast_str_create(40);
833
834         process_cors_request(headers, &response);
835
836         if (ast_ends_with(uri, "/")) {
837                 remove_trailing_slash(uri, &response);
838         } else if (ast_begins_with(uri, "api-docs/")) {
839                 /* Serving up API docs */
840                 if (method != AST_HTTP_GET) {
841                         response.message =
842                                 ast_json_pack("{s: s}",
843                                               "message", "Unsupported method");
844                         response.response_code = 405;
845                         response.response_text = "Method Not Allowed";
846                 } else {
847                         /* Skip the api-docs prefix */
848                         stasis_http_get_docs(strchr(uri, '/') + 1, headers, &response);
849                 }
850         } else {
851                 /* Other RESTful resources */
852                 stasis_http_invoke(ser, uri, method, get_params, headers,
853                         &response);
854         }
855
856         if (response.no_response) {
857                 /* The handler indicates no further response is necessary.
858                  * Probably because it already handled it */
859                 return 0;
860         }
861
862         /* Leaving message unset is only allowed for 204 (No Content).
863          * If you explicitly want to have no content for a different return
864          * code, set message to ast_json_null().
865          */
866         ast_assert(response.response_code == 204 || response.message != NULL);
867         ast_assert(response.response_code > 0);
868
869         ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers));
870
871         /* response.message could be NULL, in which case the empty response_body
872          * is correct
873          */
874         if (response.message && !ast_json_is_null(response.message)) {
875                 ast_str_append(&response_headers, 0,
876                                "Content-type: application/json\r\n");
877                 if (ast_json_dump_str_format(response.message, &response_body, cfg->global->format) != 0) {
878                         /* Error encoding response */
879                         response.response_code = 500;
880                         response.response_text = "Internal Server Error";
881                         ast_str_set(&response_body, 0, "%s", "");
882                         ast_str_set(&response_headers, 0, "%s", "");
883                         ret = -1;
884                 }
885         }
886
887         ast_http_send(ser, method, response.response_code,
888                       response.response_text, response_headers, response_body,
889                       0, 0);
890         /* ast_http_send takes ownership, so we don't have to free them */
891         response_headers = NULL;
892         response_body = NULL;
893
894         ast_json_unref(response.message);
895         return ret;
896 }
897
898 static struct ast_http_uri http_uri = {
899         .callback = stasis_http_callback,
900         .description = "Asterisk RESTful API",
901         .uri = "ari",
902
903         .has_subtree = 1,
904         .data = NULL,
905         .key = __FILE__,
906         .no_decode_uri = 1,
907 };
908
909 static int load_module(void)
910 {
911         oom_json = ast_json_pack(
912                 "{s: s}", "error", "AllocationFailed");
913
914         if (!oom_json) {
915                 /* Ironic */
916                 return AST_MODULE_LOAD_FAILURE;
917         }
918
919         ast_mutex_init(&root_handler_lock);
920
921         root_handler = root_handler_create();
922         if (!root_handler) {
923                 return AST_MODULE_LOAD_FAILURE;
924         }
925
926         if (aco_info_init(&cfg_info)) {
927                 aco_info_destroy(&cfg_info);
928                 return AST_MODULE_LOAD_DECLINE;
929         }
930
931         aco_option_register_custom(&cfg_info, "enabled", ACO_EXACT, global_options,
932                                    "yes", conf_bitfield_handler, 0);
933         aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT, global_options,
934                                    "no",  encoding_format_handler, 0);
935
936         if (aco_process_config(&cfg_info, 0)) {
937                 aco_info_destroy(&cfg_info);
938                 return AST_MODULE_LOAD_DECLINE;
939         }
940
941         if (is_enabled()) {
942                 ast_http_uri_link(&http_uri);
943         }
944
945         return AST_MODULE_LOAD_SUCCESS;
946 }
947
948 static int unload_module(void)
949 {
950         ast_json_unref(oom_json);
951         oom_json = NULL;
952
953         if (is_enabled()) {
954                 ast_http_uri_unlink(&http_uri);
955         }
956
957         aco_info_destroy(&cfg_info);
958         ao2_global_obj_release(confs);
959
960         ao2_cleanup(root_handler);
961         root_handler = NULL;
962         ast_mutex_destroy(&root_handler_lock);
963
964         return 0;
965 }
966
967 static int reload_module(void)
968 {
969         char was_enabled = is_enabled();
970
971         if (aco_process_config(&cfg_info, 1)) {
972                 return AST_MODULE_LOAD_DECLINE;
973         }
974
975         if (was_enabled && !is_enabled()) {
976                 ast_http_uri_unlink(&http_uri);
977         } else if (!was_enabled && is_enabled()) {
978                 ast_http_uri_link(&http_uri);
979         }
980
981         return AST_MODULE_LOAD_SUCCESS;
982 }
983
984 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk RESTful Interface",
985         .load = load_module,
986         .unload = unload_module,
987         .reload = reload_module,
988         .nonoptreq = "res_stasis,res_http_websocket",
989         .load_pri = AST_MODPRI_APP_DEPEND,
990         );