michael@0: /* michael@0: A trivial static http webserver using Libevent's evhttp. michael@0: michael@0: This is not the best code in the world, and it does some fairly stupid stuff michael@0: that you would never want to do in a production webserver. Caveat hackor! michael@0: michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #ifdef WIN32 michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #ifndef S_ISDIR michael@0: #define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) michael@0: #endif michael@0: #else michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #ifdef _EVENT_HAVE_NETINET_IN_H michael@0: #include michael@0: # ifdef _XOPEN_SOURCE_EXTENDED michael@0: # include michael@0: # endif michael@0: #endif michael@0: michael@0: /* Compatibility for possible missing IPv6 declarations */ michael@0: #include "../util-internal.h" michael@0: michael@0: #ifdef WIN32 michael@0: #define stat _stat michael@0: #define fstat _fstat michael@0: #define open _open michael@0: #define close _close michael@0: #define O_RDONLY _O_RDONLY michael@0: #endif michael@0: michael@0: char uri_root[512]; michael@0: michael@0: static const struct table_entry { michael@0: const char *extension; michael@0: const char *content_type; michael@0: } content_type_table[] = { michael@0: { "txt", "text/plain" }, michael@0: { "c", "text/plain" }, michael@0: { "h", "text/plain" }, michael@0: { "html", "text/html" }, michael@0: { "htm", "text/htm" }, michael@0: { "css", "text/css" }, michael@0: { "gif", "image/gif" }, michael@0: { "jpg", "image/jpeg" }, michael@0: { "jpeg", "image/jpeg" }, michael@0: { "png", "image/png" }, michael@0: { "pdf", "application/pdf" }, michael@0: { "ps", "application/postsript" }, michael@0: { NULL, NULL }, michael@0: }; michael@0: michael@0: /* Try to guess a good content-type for 'path' */ michael@0: static const char * michael@0: guess_content_type(const char *path) michael@0: { michael@0: const char *last_period, *extension; michael@0: const struct table_entry *ent; michael@0: last_period = strrchr(path, '.'); michael@0: if (!last_period || strchr(last_period, '/')) michael@0: goto not_found; /* no exension */ michael@0: extension = last_period + 1; michael@0: for (ent = &content_type_table[0]; ent->extension; ++ent) { michael@0: if (!evutil_ascii_strcasecmp(ent->extension, extension)) michael@0: return ent->content_type; michael@0: } michael@0: michael@0: not_found: michael@0: return "application/misc"; michael@0: } michael@0: michael@0: /* Callback used for the /dump URI, and for every non-GET request: michael@0: * dumps all information to stdout and gives back a trivial 200 ok */ michael@0: static void michael@0: dump_request_cb(struct evhttp_request *req, void *arg) michael@0: { michael@0: const char *cmdtype; michael@0: struct evkeyvalq *headers; michael@0: struct evkeyval *header; michael@0: struct evbuffer *buf; michael@0: michael@0: switch (evhttp_request_get_command(req)) { michael@0: case EVHTTP_REQ_GET: cmdtype = "GET"; break; michael@0: case EVHTTP_REQ_POST: cmdtype = "POST"; break; michael@0: case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break; michael@0: case EVHTTP_REQ_PUT: cmdtype = "PUT"; break; michael@0: case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break; michael@0: case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break; michael@0: case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break; michael@0: case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break; michael@0: case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break; michael@0: default: cmdtype = "unknown"; break; michael@0: } michael@0: michael@0: printf("Received a %s request for %s\nHeaders:\n", michael@0: cmdtype, evhttp_request_get_uri(req)); michael@0: michael@0: headers = evhttp_request_get_input_headers(req); michael@0: for (header = headers->tqh_first; header; michael@0: header = header->next.tqe_next) { michael@0: printf(" %s: %s\n", header->key, header->value); michael@0: } michael@0: michael@0: buf = evhttp_request_get_input_buffer(req); michael@0: puts("Input data: <<<"); michael@0: while (evbuffer_get_length(buf)) { michael@0: int n; michael@0: char cbuf[128]; michael@0: n = evbuffer_remove(buf, cbuf, sizeof(buf)-1); michael@0: if (n > 0) michael@0: (void) fwrite(cbuf, 1, n, stdout); michael@0: } michael@0: puts(">>>"); michael@0: michael@0: evhttp_send_reply(req, 200, "OK", NULL); michael@0: } michael@0: michael@0: /* This callback gets invoked when we get any http request that doesn't match michael@0: * any other callback. Like any evhttp server callback, it has a simple job: michael@0: * it must eventually call evhttp_send_error() or evhttp_send_reply(). michael@0: */ michael@0: static void michael@0: send_document_cb(struct evhttp_request *req, void *arg) michael@0: { michael@0: struct evbuffer *evb = NULL; michael@0: const char *docroot = arg; michael@0: const char *uri = evhttp_request_get_uri(req); michael@0: struct evhttp_uri *decoded = NULL; michael@0: const char *path; michael@0: char *decoded_path; michael@0: char *whole_path = NULL; michael@0: size_t len; michael@0: int fd = -1; michael@0: struct stat st; michael@0: michael@0: if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) { michael@0: dump_request_cb(req, arg); michael@0: return; michael@0: } michael@0: michael@0: printf("Got a GET request for <%s>\n", uri); michael@0: michael@0: /* Decode the URI */ michael@0: decoded = evhttp_uri_parse(uri); michael@0: if (!decoded) { michael@0: printf("It's not a good URI. Sending BADREQUEST\n"); michael@0: evhttp_send_error(req, HTTP_BADREQUEST, 0); michael@0: return; michael@0: } michael@0: michael@0: /* Let's see what path the user asked for. */ michael@0: path = evhttp_uri_get_path(decoded); michael@0: if (!path) path = "/"; michael@0: michael@0: /* We need to decode it, to see what path the user really wanted. */ michael@0: decoded_path = evhttp_uridecode(path, 0, NULL); michael@0: if (decoded_path == NULL) michael@0: goto err; michael@0: /* Don't allow any ".."s in the path, to avoid exposing stuff outside michael@0: * of the docroot. This test is both overzealous and underzealous: michael@0: * it forbids aceptable paths like "/this/one..here", but it doesn't michael@0: * do anything to prevent symlink following." */ michael@0: if (strstr(decoded_path, "..")) michael@0: goto err; michael@0: michael@0: len = strlen(decoded_path)+strlen(docroot)+2; michael@0: if (!(whole_path = malloc(len))) { michael@0: perror("malloc"); michael@0: goto err; michael@0: } michael@0: evutil_snprintf(whole_path, len, "%s/%s", docroot, decoded_path); michael@0: michael@0: if (stat(whole_path, &st)<0) { michael@0: goto err; michael@0: } michael@0: michael@0: /* This holds the content we're sending. */ michael@0: evb = evbuffer_new(); michael@0: michael@0: if (S_ISDIR(st.st_mode)) { michael@0: /* If it's a directory, read the comments and make a little michael@0: * index page */ michael@0: #ifdef WIN32 michael@0: HANDLE d; michael@0: WIN32_FIND_DATAA ent; michael@0: char *pattern; michael@0: size_t dirlen; michael@0: #else michael@0: DIR *d; michael@0: struct dirent *ent; michael@0: #endif michael@0: const char *trailing_slash = ""; michael@0: michael@0: if (!strlen(path) || path[strlen(path)-1] != '/') michael@0: trailing_slash = "/"; michael@0: michael@0: #ifdef WIN32 michael@0: dirlen = strlen(whole_path); michael@0: pattern = malloc(dirlen+3); michael@0: memcpy(pattern, whole_path, dirlen); michael@0: pattern[dirlen] = '\\'; michael@0: pattern[dirlen+1] = '*'; michael@0: pattern[dirlen+2] = '\0'; michael@0: d = FindFirstFileA(pattern, &ent); michael@0: free(pattern); michael@0: if (d == INVALID_HANDLE_VALUE) michael@0: goto err; michael@0: #else michael@0: if (!(d = opendir(whole_path))) michael@0: goto err; michael@0: #endif michael@0: michael@0: evbuffer_add_printf(evb, "\n \n" michael@0: " %s\n" michael@0: " \n" michael@0: " \n" michael@0: " \n" michael@0: "

%s

\n" michael@0: "
    \n", michael@0: decoded_path, /* XXX html-escape this. */ michael@0: uri_root, path, /* XXX html-escape this? */ michael@0: trailing_slash, michael@0: decoded_path /* XXX html-escape this */); michael@0: #ifdef WIN32 michael@0: do { michael@0: const char *name = ent.cFileName; michael@0: #else michael@0: while ((ent = readdir(d))) { michael@0: const char *name = ent->d_name; michael@0: #endif michael@0: evbuffer_add_printf(evb, michael@0: "
  • %s\n", michael@0: name, name);/* XXX escape this */ michael@0: #ifdef WIN32 michael@0: } while (FindNextFileA(d, &ent)); michael@0: #else michael@0: } michael@0: #endif michael@0: evbuffer_add_printf(evb, "
\n"); michael@0: #ifdef WIN32 michael@0: CloseHandle(d); michael@0: #else michael@0: closedir(d); michael@0: #endif michael@0: evhttp_add_header(evhttp_request_get_output_headers(req), michael@0: "Content-Type", "text/html"); michael@0: } else { michael@0: /* Otherwise it's a file; add it to the buffer to get michael@0: * sent via sendfile */ michael@0: const char *type = guess_content_type(decoded_path); michael@0: if ((fd = open(whole_path, O_RDONLY)) < 0) { michael@0: perror("open"); michael@0: goto err; michael@0: } michael@0: michael@0: if (fstat(fd, &st)<0) { michael@0: /* Make sure the length still matches, now that we michael@0: * opened the file :/ */ michael@0: perror("fstat"); michael@0: goto err; michael@0: } michael@0: evhttp_add_header(evhttp_request_get_output_headers(req), michael@0: "Content-Type", type); michael@0: evbuffer_add_file(evb, fd, 0, st.st_size); michael@0: } michael@0: michael@0: evhttp_send_reply(req, 200, "OK", evb); michael@0: goto done; michael@0: err: michael@0: evhttp_send_error(req, 404, "Document was not found"); michael@0: if (fd>=0) michael@0: close(fd); michael@0: done: michael@0: if (decoded) michael@0: evhttp_uri_free(decoded); michael@0: if (decoded_path) michael@0: free(decoded_path); michael@0: if (whole_path) michael@0: free(whole_path); michael@0: if (evb) michael@0: evbuffer_free(evb); michael@0: } michael@0: michael@0: static void michael@0: syntax(void) michael@0: { michael@0: fprintf(stdout, "Syntax: http-server \n"); michael@0: } michael@0: michael@0: int michael@0: main(int argc, char **argv) michael@0: { michael@0: struct event_base *base; michael@0: struct evhttp *http; michael@0: struct evhttp_bound_socket *handle; michael@0: michael@0: unsigned short port = 0; michael@0: #ifdef WIN32 michael@0: WSADATA WSAData; michael@0: WSAStartup(0x101, &WSAData); michael@0: #else michael@0: if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) michael@0: return (1); michael@0: #endif michael@0: if (argc < 2) { michael@0: syntax(); michael@0: return 1; michael@0: } michael@0: michael@0: base = event_base_new(); michael@0: if (!base) { michael@0: fprintf(stderr, "Couldn't create an event_base: exiting\n"); michael@0: return 1; michael@0: } michael@0: michael@0: /* Create a new evhttp object to handle requests. */ michael@0: http = evhttp_new(base); michael@0: if (!http) { michael@0: fprintf(stderr, "couldn't create evhttp. Exiting.\n"); michael@0: return 1; michael@0: } michael@0: michael@0: /* The /dump URI will dump all requests to stdout and say 200 ok. */ michael@0: evhttp_set_cb(http, "/dump", dump_request_cb, NULL); michael@0: michael@0: /* We want to accept arbitrary requests, so we need to set a "generic" michael@0: * cb. We can also add callbacks for specific paths. */ michael@0: evhttp_set_gencb(http, send_document_cb, argv[1]); michael@0: michael@0: /* Now we tell the evhttp what port to listen on */ michael@0: handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port); michael@0: if (!handle) { michael@0: fprintf(stderr, "couldn't bind to port %d. Exiting.\n", michael@0: (int)port); michael@0: return 1; michael@0: } michael@0: michael@0: { michael@0: /* Extract and display the address we're listening on. */ michael@0: struct sockaddr_storage ss; michael@0: evutil_socket_t fd; michael@0: ev_socklen_t socklen = sizeof(ss); michael@0: char addrbuf[128]; michael@0: void *inaddr; michael@0: const char *addr; michael@0: int got_port = -1; michael@0: fd = evhttp_bound_socket_get_fd(handle); michael@0: memset(&ss, 0, sizeof(ss)); michael@0: if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) { michael@0: perror("getsockname() failed"); michael@0: return 1; michael@0: } michael@0: if (ss.ss_family == AF_INET) { michael@0: got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); michael@0: inaddr = &((struct sockaddr_in*)&ss)->sin_addr; michael@0: } else if (ss.ss_family == AF_INET6) { michael@0: got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); michael@0: inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; michael@0: } else { michael@0: fprintf(stderr, "Weird address family %d\n", michael@0: ss.ss_family); michael@0: return 1; michael@0: } michael@0: addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, michael@0: sizeof(addrbuf)); michael@0: if (addr) { michael@0: printf("Listening on %s:%d\n", addr, got_port); michael@0: evutil_snprintf(uri_root, sizeof(uri_root), michael@0: "http://%s:%d",addr,got_port); michael@0: } else { michael@0: fprintf(stderr, "evutil_inet_ntop failed\n"); michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: event_base_dispatch(base); michael@0: michael@0: return 0; michael@0: }