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