typo: serer -> server
[asterisk/asterisk.git] / main / http.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@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 /*!
20  * \file 
21  * \brief http server for AMI access
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * This program implements a tiny http server supporting the "get" method
25  * only and was inspired by micro-httpd by Jef Poskanzer 
26  * 
27  * \ref AstHTTP - AMI over the http protocol
28  */
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include <sys/types.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <time.h>
39 #include <string.h>
40 #include <netinet/in.h>
41 #include <sys/time.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/signal.h>
45 #include <arpa/inet.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <pthread.h>
49
50 #include "asterisk/cli.h"
51 #include "asterisk/http.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/strings.h"
54 #include "asterisk/options.h"
55 #include "asterisk/config.h"
56
57 #define MAX_PREFIX 80
58 #define DEFAULT_PREFIX "/asterisk"
59
60 struct ast_http_server_instance {
61         FILE *f;
62         int fd;
63         struct sockaddr_in requestor;
64         ast_http_callback callback;
65 };
66
67 static struct ast_http_uri *uris;
68
69 static int httpfd = -1;
70 static pthread_t master = AST_PTHREADT_NULL;
71 static char prefix[MAX_PREFIX];
72 static int prefix_len = 0;
73 static struct sockaddr_in oldsin;
74 static int enablestatic=0;
75
76 /*! \brief Limit the kinds of files we're willing to serve up */
77 static struct {
78         char *ext;
79         char *mtype;
80 } mimetypes[] = {
81         { "png", "image/png" },
82         { "jpg", "image/jpeg" },
83         { "js", "application/x-javascript" },
84         { "wav", "audio/x-wav" },
85         { "mp3", "audio/mpeg" },
86 };
87
88 static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
89 {
90         int x;
91         if (ftype) {
92                 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
93                         if (!strcasecmp(ftype, mimetypes[x].ext))
94                                 return mimetypes[x].mtype;
95                 }
96         }
97         snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
98         return wkspace;
99 }
100
101 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
102 {
103         char result[4096];
104         char *c=result;
105         char *path;
106         char *ftype, *mtype;
107         char wkspace[80];
108         struct stat st;
109         int len;
110         int fd;
111         void *blob;
112
113         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
114            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
115         if (!enablestatic || ast_strlen_zero(uri))
116                 goto out403;
117         /* Disallow any funny filenames at all */
118         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
119                 goto out403;
120         if (strstr(uri, "/.."))
121                 goto out403;
122                 
123         if ((ftype = strrchr(uri, '.')))
124                 ftype++;
125         mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
126         
127         /* Cap maximum length */
128         len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
129         if (len > 1024)
130                 goto out403;
131                 
132         path = alloca(len);
133         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
134         if (stat(path, &st))
135                 goto out404;
136         if (S_ISDIR(st.st_mode))
137                 goto out404;
138         fd = open(path, O_RDONLY);
139         if (fd < 0)
140                 goto out403;
141         
142         len = st.st_size + strlen(mtype) + 40;
143         
144         blob = malloc(len);
145         if (blob) {
146                 c = blob;
147                 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
148                 c += strlen(c);
149                 *contentlength = read(fd, c, st.st_size);
150                 if (*contentlength < 0) {
151                         close(fd);
152                         free(blob);
153                         goto out403;
154                 }
155         }
156         close(fd);
157         return blob;
158
159 out404:
160         *status = 404;
161         *title = strdup("Not Found");
162         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
163
164 out403:
165         *status = 403;
166         *title = strdup("Access Denied");
167         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
168 }
169
170
171 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
172 {
173         char result[4096];
174         size_t reslen = sizeof(result);
175         char *c=result;
176         struct ast_variable *v;
177
178         ast_build_string(&c, &reslen,
179                 "\r\n"
180                 "<title>Asterisk HTTP Status</title>\r\n"
181                 "<body bgcolor=\"#ffffff\">\r\n"
182                 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
183                 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
184
185         ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
186         ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
187                         ast_inet_ntoa(oldsin.sin_addr));
188         ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
189                         ntohs(oldsin.sin_port));
190         ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
191         v = vars;
192         while(v) {
193                 if (strncasecmp(v->name, "cookie_", 7))
194                         ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
195                 v = v->next;
196         }
197         ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
198         v = vars;
199         while(v) {
200                 if (!strncasecmp(v->name, "cookie_", 7))
201                         ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
202                 v = v->next;
203         }
204         ast_build_string(&c, &reslen, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
205         return strdup(result);
206 }
207
208 static struct ast_http_uri statusuri = {
209         .callback = httpstatus_callback,
210         .description = "Asterisk HTTP General Status",
211         .uri = "httpstatus",
212         .has_subtree = 0,
213 };
214         
215 static struct ast_http_uri staticuri = {
216         .callback = static_callback,
217         .description = "Asterisk HTTP Static Delivery",
218         .uri = "static",
219         .has_subtree = 1,
220 };
221         
222 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
223 {
224         char *c = NULL;
225         asprintf(&c,
226                 "Content-type: text/html\r\n"
227                 "%s"
228                 "\r\n"
229                 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
230                 "<html><head>\r\n"
231                 "<title>%d %s</title>\r\n"
232                 "</head><body>\r\n"
233                 "<h1>%s</h1>\r\n"
234                 "<p>%s</p>\r\n"
235                 "<hr />\r\n"
236                 "<address>Asterisk Server</address>\r\n"
237                 "</body></html>\r\n",
238                         (extra_header ? extra_header : ""), status, title, title, text);
239         return c;
240 }
241
242 int ast_http_uri_link(struct ast_http_uri *urih)
243 {
244         struct ast_http_uri *prev=uris;
245         if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
246                 urih->next = uris;
247                 uris = urih;
248         } else {
249                 while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
250                         prev = prev->next;
251                 /* Insert it here */
252                 urih->next = prev->next;
253                 prev->next = urih;
254         }
255         return 0;
256 }       
257
258 void ast_http_uri_unlink(struct ast_http_uri *urih)
259 {
260         struct ast_http_uri *prev = uris;
261         if (!uris)
262                 return;
263         if (uris == urih) {
264                 uris = uris->next;
265         }
266         while(prev->next) {
267                 if (prev->next == urih) {
268                         prev->next = urih->next;
269                         break;
270                 }
271                 prev = prev->next;
272         }
273 }
274
275 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
276 {
277         char *c;
278         char *turi;
279         char *params;
280         char *var;
281         char *val;
282         struct ast_http_uri *urih=NULL;
283         int len;
284         struct ast_variable *vars=NULL, *v, *prev = NULL;
285         
286         
287         params = strchr(uri, '?');
288         if (params) {
289                 *params = '\0';
290                 params++;
291                 while ((var = strsep(&params, "&"))) {
292                         val = strchr(var, '=');
293                         if (val) {
294                                 *val = '\0';
295                                 val++;
296                                 ast_uri_decode(val);
297                         } else 
298                                 val = "";
299                         ast_uri_decode(var);
300                         if ((v = ast_variable_new(var, val))) {
301                                 if (vars)
302                                         prev->next = v;
303                                 else
304                                         vars = v;
305                                 prev = v;
306                         }
307                 }
308         }
309         if (prev)
310                 prev->next = *cookies;
311         else
312                 vars = *cookies;
313         *cookies = NULL;
314         ast_uri_decode(uri);
315         if (!strncasecmp(uri, prefix, prefix_len)) {
316                 uri += prefix_len;
317                 if (!*uri || (*uri == '/')) {
318                         if (*uri == '/')
319                                 uri++;
320                         urih = uris;
321                         while(urih) {
322                                 len = strlen(urih->uri);
323                                 if (!strncasecmp(urih->uri, uri, len)) {
324                                         if (!uri[len] || uri[len] == '/') {
325                                                 turi = uri + len;
326                                                 if (*turi == '/')
327                                                         turi++;
328                                                 if (!*turi || urih->has_subtree) {
329                                                         uri = turi;
330                                                         break;
331                                                 }
332                                         }
333                                 }
334                                 urih = urih->next;
335                         }
336                 }
337         }
338         if (urih) {
339                 c = urih->callback(sin, uri, vars, status, title, contentlength);
340                 ast_variables_destroy(vars);
341         } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
342                 /* Special case: If no prefix, and no URI, send to /static/index.html */
343                 c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
344                 *status = 302;
345                 *title = strdup("Moved Temporarily");
346         } else {
347                 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
348                 *status = 404;
349                 *title = strdup("Not Found");
350         }
351         return c;
352 }
353
354 static void *ast_httpd_helper_thread(void *data)
355 {
356         char buf[4096];
357         char cookie[4096];
358         char timebuf[256];
359         struct ast_http_server_instance *ser = data;
360         struct ast_variable *var, *prev=NULL, *vars=NULL;
361         char *uri, *c, *title=NULL;
362         char *vname, *vval;
363         int status = 200, contentlength = 0;
364         time_t t;
365
366         if (fgets(buf, sizeof(buf), ser->f)) {
367                 /* Skip method */
368                 uri = buf;
369                 while(*uri && (*uri > 32))
370                         uri++;
371                 if (*uri) {
372                         *uri = '\0';
373                         uri++;
374                 }
375
376                 /* Skip white space */
377                 while (*uri && (*uri < 33))
378                         uri++;
379
380                 if (*uri) {
381                         c = uri;
382                         while (*c && (*c > 32))
383                                  c++;
384                         if (*c) {
385                                 *c = '\0';
386                         }
387                 }
388
389                 while (fgets(cookie, sizeof(cookie), ser->f)) {
390                         /* Trim trailing characters */
391                         while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
392                                 cookie[strlen(cookie) - 1] = '\0';
393                         }
394                         if (ast_strlen_zero(cookie))
395                                 break;
396                         if (!strncasecmp(cookie, "Cookie: ", 8)) {
397
398                                 /* TODO - The cookie parsing code below seems to work   
399                                    in IE6 and FireFox 1.5.  However, it is not entirely 
400                                    correct, and therefore may not work in all           
401                                    circumstances.                                       
402                                       For more details see RFC 2109 and RFC 2965        */
403                         
404                                 /* FireFox cookie strings look like:                    
405                                      Cookie: mansession_id="********"                   
406                                    InternetExplorer's look like:                        
407                                      Cookie: $Version="1"; mansession_id="********"     */
408                                 
409                                 /* If we got a FireFox cookie string, the name's right  
410                                     after "Cookie: "                                    */
411                                 vname = cookie + 8;
412                                 
413                                 /* If we got an IE cookie string, we need to skip to    
414                                     past the version to get to the name                 */
415                                 if (*vname == '$') {
416                                         vname = strchr(vname, ';');
417                                         if (vname) { 
418                                                 vname++;
419                                                 if (*vname == ' ')
420                                                         vname++;
421                                         }
422                                 }
423                                 
424                                 if (vname) {
425                                         vval = strchr(vname, '=');
426                                         if (vval) {
427                                                 /* Ditch the = and the quotes */
428                                                 *vval++ = '\0';
429                                                 if (*vval)
430                                                         vval++;
431                                                 if (strlen(vval))
432                                                         vval[strlen(vval) - 1] = '\0';
433                                                 var = ast_variable_new(vname, vval);
434                                                 if (var) {
435                                                         if (prev)
436                                                                 prev->next = var;
437                                                         else
438                                                                 vars = var;
439                                                         prev = var;
440                                                 }
441                                         }
442                                 }
443                         }
444                 }
445
446                 if (*uri) {
447                         if (!strcasecmp(buf, "get")) 
448                                 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
449                         else 
450                                 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
451                 } else 
452                         c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
453
454                 /* If they aren't mopped up already, clean up the cookies */
455                 if (vars)
456                         ast_variables_destroy(vars);
457
458                 if (!c)
459                         c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
460                 if (c) {
461                         time(&t);
462                         strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
463                         ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
464                         ast_cli(ser->fd, "Server: Asterisk\r\n");
465                         ast_cli(ser->fd, "Date: %s\r\n", timebuf);
466                         ast_cli(ser->fd, "Connection: close\r\n");
467                         if (contentlength) {
468                                 char *tmp;
469                                 tmp = strstr(c, "\r\n\r\n");
470                                 if (tmp) {
471                                         ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
472                                         write(ser->fd, c, (tmp + 4 - c));
473                                         write(ser->fd, tmp + 4, contentlength);
474                                 }
475                         } else
476                                 ast_cli(ser->fd, "%s", c);
477                         free(c);
478                 }
479                 if (title)
480                         free(title);
481         }
482         fclose(ser->f);
483         free(ser);
484         return NULL;
485 }
486
487 static void *http_root(void *data)
488 {
489         int fd;
490         struct sockaddr_in sin;
491         socklen_t sinlen;
492         struct ast_http_server_instance *ser;
493         pthread_t launched;
494         pthread_attr_t attr;
495         
496         for (;;) {
497                 ast_wait_for_input(httpfd, -1);
498                 sinlen = sizeof(sin);
499                 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
500                 if (fd < 0) {
501                         if ((errno != EAGAIN) && (errno != EINTR))
502                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
503                         continue;
504                 }
505                 ser = ast_calloc(1, sizeof(*ser));
506                 if (ser) {
507                         int flags = fcntl(fd, F_GETFL);
508                         fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
509                         ser->fd = fd;
510                         memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
511                         if ((ser->f = fdopen(ser->fd, "w+"))) {
512                                 pthread_attr_init(&attr);
513                                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
514                                 
515                                 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
516                                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
517                                         fclose(ser->f);
518                                         free(ser);
519                                 }
520                         } else {
521                                 ast_log(LOG_WARNING, "fdopen failed!\n");
522                                 close(ser->fd);
523                                 free(ser);
524                         }
525                 }
526         }
527         return NULL;
528 }
529
530 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
531 {
532         char *c;
533         c = buf;
534         ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
535         if (expires)
536                 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
537         ast_build_string(&c, &buflen, "\r\n");
538         return buf;
539 }
540
541
542 static void http_server_start(struct sockaddr_in *sin)
543 {
544         int flags;
545         int x = 1;
546         
547         /* Do nothing if nothing has changed */
548         if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
549                 if (option_debug)
550                         ast_log(LOG_DEBUG, "Nothing changed in http\n");
551                 return;
552         }
553         
554         memcpy(&oldsin, sin, sizeof(oldsin));
555         
556         /* Shutdown a running server if there is one */
557         if (master != AST_PTHREADT_NULL) {
558                 pthread_cancel(master);
559                 pthread_kill(master, SIGURG);
560                 pthread_join(master, NULL);
561         }
562         
563         if (httpfd != -1)
564                 close(httpfd);
565
566         /* If there's no new server, stop here */
567         if (!sin->sin_family)
568                 return;
569         
570         
571         httpfd = socket(AF_INET, SOCK_STREAM, 0);
572         if (httpfd < 0) {
573                 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
574                 return;
575         }
576         
577         setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
578         if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
579                 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
580                         ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
581                         strerror(errno));
582                 close(httpfd);
583                 httpfd = -1;
584                 return;
585         }
586         if (listen(httpfd, 10)) {
587                 ast_log(LOG_NOTICE, "Unable to listen!\n");
588                 close(httpfd);
589                 httpfd = -1;
590                 return;
591         }
592         flags = fcntl(httpfd, F_GETFL);
593         fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
594         if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
595                 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
596                                 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
597                                 strerror(errno));
598                 close(httpfd);
599                 httpfd = -1;
600         }
601 }
602
603 static int __ast_http_load(int reload)
604 {
605         struct ast_config *cfg;
606         struct ast_variable *v;
607         int enabled=0;
608         int newenablestatic=0;
609         struct sockaddr_in sin;
610         struct hostent *hp;
611         struct ast_hostent ahp;
612         char newprefix[MAX_PREFIX];
613
614         memset(&sin, 0, sizeof(sin));
615         sin.sin_port = 8088;
616         strcpy(newprefix, DEFAULT_PREFIX);
617         cfg = ast_config_load("http.conf");
618         if (cfg) {
619                 v = ast_variable_browse(cfg, "general");
620                 while(v) {
621                         if (!strcasecmp(v->name, "enabled"))
622                                 enabled = ast_true(v->value);
623                         else if (!strcasecmp(v->name, "enablestatic"))
624                                 newenablestatic = ast_true(v->value);
625                         else if (!strcasecmp(v->name, "bindport"))
626                                 sin.sin_port = ntohs(atoi(v->value));
627                         else if (!strcasecmp(v->name, "bindaddr")) {
628                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
629                                         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
630                                 } else {
631                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
632                                 }
633                         } else if (!strcasecmp(v->name, "prefix")) {
634                                 if (!ast_strlen_zero(v->value)) {
635                                         newprefix[0] = '/';
636                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
637                                 } else {
638                                         newprefix[0] = '\0';
639                                 }
640                                         
641                         }
642                         v = v->next;
643                 }
644                 ast_config_destroy(cfg);
645         }
646         if (enabled)
647                 sin.sin_family = AF_INET;
648         if (strcmp(prefix, newprefix)) {
649                 ast_copy_string(prefix, newprefix, sizeof(prefix));
650                 prefix_len = strlen(prefix);
651         }
652         enablestatic = newenablestatic;
653         http_server_start(&sin);
654         return 0;
655 }
656
657 static int handle_show_http(int fd, int argc, char *argv[])
658 {
659         struct ast_http_uri *urih;
660         if (argc != 3)
661                 return RESULT_SHOWUSAGE;
662         ast_cli(fd, "HTTP Server Status:\n");
663         ast_cli(fd, "Prefix: %s\n", prefix);
664         if (oldsin.sin_family)
665                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
666                         ast_inet_ntoa(oldsin.sin_addr),
667                         ntohs(oldsin.sin_port));
668         else
669                 ast_cli(fd, "Server Disabled\n\n");
670         ast_cli(fd, "Enabled URI's:\n");
671         urih = uris;
672         while(urih){
673                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
674                 urih = urih->next;
675         }
676         if (!uris)
677                 ast_cli(fd, "None.\n");
678         return RESULT_SUCCESS;
679 }
680
681 int ast_http_reload(void)
682 {
683         return __ast_http_load(1);
684 }
685
686 static char show_http_help[] =
687 "Usage: http list status\n"
688 "       Lists status of internal HTTP engine\n";
689
690 static struct ast_cli_entry cli_http[] = {
691         { { "http", "list", "status", NULL },
692         handle_show_http, "Display HTTP server status",
693         show_http_help },
694 };
695
696 int ast_http_init(void)
697 {
698         ast_http_uri_link(&statusuri);
699         ast_http_uri_link(&staticuri);
700         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
701         return __ast_http_load(0);
702 }