staload "prelude/DATS/array.dats" staload "libc/SATS/signal.sats" %{^ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/stat.h> #include <sys/socket.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <event2/event.h> #include <event2/http.h> #include <event2/buffer.h> #include <event2/util.h> #include <event2/keyvalq_struct.h> char uri_root[512]; static const struct table_entry { const char *extension; const char *content_type; } content_type_table[] = { { "txt", "text/plain" }, { "c", "text/plain" }, { "h", "text/plain" }, { "html", "text/html" }, { "htm", "text/htm" }, { "css", "text/css" }, { "gif", "image/gif" }, { "jpg", "image/jpeg" }, { "jpeg", "image/jpeg" }, { "png", "image/png" }, { "pdf", "application/pdf" }, { "ps", "application/postsript" }, { NULL, NULL }, }; void dump_request_cb(struct evhttp_request *req, void *arg); void send_document_cb(struct evhttp_request *req, void *arg); %} %{ /* Try to guess a good content-type for 'path' */ static const char * guess_content_type(const char *path) { const char *last_period, *extension; const struct table_entry *ent; last_period = strrchr(path, '.'); if (!last_period || strchr(last_period, '/')) goto not_found; /* no exension */ extension = last_period + 1; for (ent = &content_type_table[0]; ent->extension; ++ent) { if (!evutil_ascii_strcasecmp(ent->extension, extension)) return ent->content_type; } not_found: return "application/misc"; } /* Callback used for the /dump URI, and for every non-GET request: * dumps all information to stdout and gives back a trivial 200 ok */ void dump_request_cb(struct evhttp_request *req, void *arg) { const char *cmdtype; struct evkeyvalq *headers; struct evkeyval *header; struct evbuffer *buf; switch (evhttp_request_get_command(req)) { case EVHTTP_REQ_GET: cmdtype = "GET"; break; case EVHTTP_REQ_POST: cmdtype = "POST"; break; case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break; case EVHTTP_REQ_PUT: cmdtype = "PUT"; break; case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break; case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break; case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break; case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break; case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break; default: cmdtype = "unknown"; break; } printf("Received a %s request for %s\nHeaders:\n", cmdtype, evhttp_request_get_uri(req)); headers = evhttp_request_get_input_headers(req); for (header = headers->tqh_first; header; header = header->next.tqe_next) { printf(" %s: %s\n", header->key, header->value); } buf = evhttp_request_get_input_buffer(req); puts("Input data: <<<"); while (evbuffer_get_length(buf)) { int n; char cbuf[128]; n = evbuffer_remove(buf, cbuf, sizeof(buf)-1); fwrite(cbuf, 1, n, stdout); } puts(">>>"); evhttp_send_reply(req, 200, "OK", NULL); } /* This callback gets invoked when we get any http request that doesn't match * any other callback. Like any evhttp server callback, it has a simple job: * it must eventually call evhttp_send_error() or evhttp_send_reply(). */ void send_document_cb(struct evhttp_request *req, void *arg) { struct evbuffer *evb = NULL; const char *docroot = arg; const char *uri = evhttp_request_get_uri(req); struct evhttp_uri *decoded = NULL; const char *path; char *decoded_path; char *whole_path = NULL; size_t len; int fd = -1; struct stat st; if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) { dump_request_cb(req, arg); return; } printf("Got a GET request for <%s>\n", uri); /* Decode the URI */ decoded = evhttp_uri_parse(uri); if (!decoded) { printf("It's not a good URI. Sending BADREQUEST\n"); evhttp_send_error(req, HTTP_BADREQUEST, 0); return; } /* Let's see what path the user asked for. */ path = evhttp_uri_get_path(decoded); if (!path) path = "/"; /* We need to decode it, to see what path the user really wanted. */ decoded_path = evhttp_uridecode(path, 0, NULL); /* Don't allow any ".."s in the path, to avoid exposing stuff outside * of the docroot. This test is both overzealous and underzealous: * it forbids aceptable paths like "/this/one..here", but it doesn't * do anything to prevent symlink following." */ if (strstr(decoded_path, "..")) goto err; len = strlen(decoded_path)+strlen(docroot)+2; if (!(whole_path = malloc(len))) { perror("malloc"); goto err; } evutil_snprintf(whole_path, len, "%s/%s", docroot, decoded_path); if (stat(whole_path, &st)<0) { goto err; } /* This holds the content we're sending. */ evb = evbuffer_new(); if (S_ISDIR(st.st_mode)) { /* If it's a directory, read the comments and make a little * index page */ DIR *d; struct dirent *ent; const char *trailing_slash = ""; if (!strlen(path) || path[strlen(path)-1] != '/') trailing_slash = "/"; if (!(d = opendir(whole_path))) goto err; evbuffer_add_printf(evb, "<html>\n <head>\n" " <title>%s</title>\n" " <base href='%s%s%s'>\n" " </head>\n" " <body>\n" " <h1>%s</h1>\n" " <ul>\n", decoded_path, /* XXX html-escape this. */ uri_root, path, /* XXX html-escape this? */ trailing_slash, decoded_path /* XXX html-escape this */); while ((ent = readdir(d))) { const char *name = ent->d_name; evbuffer_add_printf(evb, " <li><a href=\"%s\">%s</a>\n", name, name);/* XXX escape this */ } evbuffer_add_printf(evb, "</ul></body></html>\n"); closedir(d); evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/html"); } else { /* Otherwise it's a file; add it to the buffer to get * sent via sendfile */ const char *type = guess_content_type(decoded_path); if ((fd = open(whole_path, O_RDONLY)) < 0) { perror("open"); goto err; } if (fstat(fd, &st)<0) { /* Make sure the length still matches, now that we * opened the file :/ */ perror("fstat"); goto err; } evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", type); evbuffer_add_file(evb, fd, 0, st.st_size); } evhttp_send_reply(req, 200, "OK", evb); goto done; err: evhttp_send_error(req, 404, "Document was not found"); if (fd>=0) close(fd); done: if (decoded) evhttp_uri_free(decoded); if (decoded_path) free(decoded_path); if (whole_path) free(whole_path); if (evb) evbuffer_free(evb); } %} absviewtype event_base (l:addr) viewtypedef event_base0 = [l:addr | l >= null ] event_base l viewtypedef event_base1 = [l:addr | l > null ] event_base l absviewtype evhttp (l:addr) viewtypedef evhttp0 = [l:addr | l >= null ] evhttp l viewtypedef evhttp1 = [l:addr | l > null ] evhttp l absviewtype evhttp_request (l:addr) viewtypedef evhttp_request0 = [l:addr | l >= null ] evhttp_request l viewtypedef evhttp_request1 = [l:addr | l > null ] evhttp_request l extern fun event_base_isnot_null {l:addr} (p: !event_base l):<> bool (l > null) = "atspre_ptr_isnot_null" overload ~ with event_base_isnot_null extern fun evhttp_isnot_null {l:addr} (p: !evhttp l):<> bool (l > null) = "atspre_ptr_isnot_null" overload ~ with evhttp_isnot_null extern fun event_base_new(): event_base0 = "mac#event_base_new" extern fun event_base_free (p: event_base1):void = "mac#event_base_free" extern fun evhttp_new (base: !event_base1): evhttp0 = "mac#evhttp_new" extern fun evhttp_free (http: evhttp1):void = "mac#evhttp_free" extern fun evhttp_bind_socket (http: !evhttp1, address: string, port: uint16):int = "mac#evhttp_bind_socket" extern fun event_base_dispatch (base: !event_base1):int = "mac#event_base_dispatch" extern fun dump_request_cb (request: !evhttp_request1, arg: !ptr): void = "mac#dump_request_cb" extern fun send_document_cb (request: !evhttp_request1, arg: !string): void = "mac#send_document_cb" typedef evhttp_callback (t1:viewtype) = (!evhttp_request1, !t1) -<fun1> void extern fun evhttp_set_cb {a:viewtype} (http: !evhttp1, path: string, callback: evhttp_callback (a), arg: !a): int = "mac#evhttp_set_cb" extern fun evhttp_set_gencb {a:viewtype} (http: !evhttp1, callback: evhttp_callback (a), arg: !a): void = "mac#evhttp_set_gencb" extern fun event_base_loopexit (base: !event_base1, tv: ptr):int = "mac#event_base_loopexit" macdef ignore (x) = let val _ = ,(x) in () end fn http_server(docroot: string):void = let val _ = signal(SIGPIPE, SIG_IGN) val base = event_base_new() val () = assert_errmsg(~base, "event_base_new failed") val http = evhttp_new(base) val () = assert_errmsg(~http, "evhttp_new failed") val _ = evhttp_set_cb {ptr} (http, "/dump", dump_request_cb, null) val _ = evhttp_set_cb {event_base1} (http, "/quit", lam (req, arg) => ignore(event_base_loopexit(arg, null)), base) val () = evhttp_set_gencb {string} (http, send_document_cb, docroot) val r = evhttp_bind_socket(http, "0.0.0.0", uint16_of_int(8080)); val () = assert_errmsg(r = 0, "evhttp_bind_socket failed") val _ = event_base_dispatch(base) val () = evhttp_free(http) val () = event_base_free(base) in () end fn syntax():void = print_string("Syntax: http-server <docroot>\n") implement main(argc, argv) = if argc < 2 then syntax() else http_server(argv[1])