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