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