Initial implementation
authorFelix Fietkau <nbd@openwrt.org>
Sun, 30 Dec 2012 18:36:18 +0000 (19:36 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Sun, 30 Dec 2012 18:36:18 +0000 (19:36 +0100)
CMakeLists.txt [new file with mode: 0644]
client.c [new file with mode: 0644]
file.c [new file with mode: 0644]
listen.c [new file with mode: 0644]
main.c [new file with mode: 0644]
uhttpd-mimetypes.h [new file with mode: 0644]
uhttpd.h [new file with mode: 0644]
utils.c [new file with mode: 0644]
utils.h [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3489414
--- /dev/null
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(uhttpd C)
+ADD_DEFINITIONS(-O1 -Wall -Werror --std=gnu99 -g3)
+
+IF(APPLE)
+  INCLUDE_DIRECTORIES(/opt/local/include)
+  LINK_DIRECTORIES(/opt/local/lib)
+ENDIF()
+
+ADD_EXECUTABLE(uhttpd main.c listen.c client.c utils.c file.c)
+TARGET_LINK_LIBRARIES(uhttpd ubox ubus)
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..9e387c7
--- /dev/null
+++ b/client.c
@@ -0,0 +1,332 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include <libubox/blobmsg.h>
+#include <ctype.h>
+
+#include "uhttpd.h"
+
+static LIST_HEAD(clients);
+
+int n_clients = 0;
+struct config conf = {};
+
+static const char *http_versions[] = {
+       [UH_HTTP_VER_0_9] = "HTTP/0.9",
+       [UH_HTTP_VER_1_0] = "HTTP/1.0",
+       [UH_HTTP_VER_1_1] = "HTTP/1.1",
+};
+
+void uh_http_header(struct client *cl, int code, const char *summary)
+{
+       const char *enc = "Transfer-Encoding: chunked\r\n";
+       const char *conn;
+
+       if (!uh_use_chunked(cl))
+               enc = "";
+
+       if (cl->request.version != UH_HTTP_VER_1_1)
+               conn = "Connection: close";
+       else
+               conn = "Connection: keep-alive";
+
+       ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s",
+               http_versions[cl->request.version],
+               code, summary, conn, enc);
+}
+
+static void uh_client_error_header(struct client *cl, int code, const char *summary)
+{
+       uh_http_header(cl, code, summary);
+       ustream_printf(cl->us, "Content-Type: text/plain\r\n\r\n");
+}
+
+static void uh_connection_close(struct client *cl)
+{
+       cl->state = CLIENT_STATE_DONE;
+       cl->us->eof = true;
+       ustream_state_change(cl->us);
+}
+
+static void uh_dispatch_done(struct client *cl)
+{
+       if (cl->dispatch_free)
+               cl->dispatch_free(cl);
+       cl->dispatch_free = NULL;
+}
+
+void uh_request_done(struct client *cl)
+{
+       uh_chunk_eof(cl);
+       uh_dispatch_done(cl);
+       cl->us->notify_write = NULL;
+       memset(&cl->data, 0, sizeof(cl->data));
+
+       if (cl->request.version != UH_HTTP_VER_1_1 || !conf.http_keepalive) {
+               uh_connection_close(cl);
+               return;
+       }
+
+       cl->state = CLIENT_STATE_INIT;
+       uloop_timeout_set(&cl->timeout, conf.http_keepalive * 1000);
+}
+
+void __printf(4, 5)
+uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...)
+{
+       va_list arg;
+
+       uh_client_error_header(cl, code, summary);
+
+       va_start(arg, fmt);
+       uh_chunk_vprintf(cl, fmt, arg);
+       va_end(arg);
+
+       uh_request_done(cl);
+}
+
+static void uh_header_error(struct client *cl, int code, const char *summary)
+{
+       uh_client_error(cl, code, summary, "%s", summary);
+       uh_connection_close(cl);
+}
+
+static void client_timeout(struct uloop_timeout *timeout)
+{
+       struct client *cl = container_of(timeout, struct client, timeout);
+
+       cl->state = CLIENT_STATE_CLOSE;
+       uh_connection_close(cl);
+}
+
+static int client_parse_request(struct client *cl, char *data)
+{
+       struct http_request *req = &cl->request;
+       char *type, *path, *version;
+       int i;
+
+       type = strtok(data, " ");
+       path = strtok(NULL, " ");
+       version = strtok(NULL, " ");
+       if (!type || !path || !version)
+               return CLIENT_STATE_DONE;
+
+       req->url = path;
+       if (!strcmp(type, "GET"))
+               req->method = UH_HTTP_MSG_GET;
+       else if (!strcmp(type, "POST"))
+               req->method = UH_HTTP_MSG_POST;
+       else if (!strcmp(type, "HEAD"))
+               req->method = UH_HTTP_MSG_HEAD;
+       else
+               return CLIENT_STATE_DONE;
+
+       cl->request.version = -1;
+       i = array_size(http_versions);
+       while (i--) {
+               if (!strcmp(version, http_versions[i])) {
+                       cl->request.version = i;
+                       break;
+               }
+       }
+       if (cl->request.version < 0)
+               return CLIENT_STATE_DONE;
+
+       return CLIENT_STATE_HEADER;
+}
+
+static bool client_init_cb(struct client *cl, char *buf, int len)
+{
+       char *newline;
+
+       newline = strstr(buf, "\r\n");
+       if (!newline)
+               return false;
+
+       *newline = 0;
+       blob_buf_init(&cl->hdr, 0);
+       blobmsg_add_string(&cl->hdr, "REQUEST", buf);
+       ustream_consume(cl->us, newline + 2 - buf);
+       cl->state = client_parse_request(cl, (char *) blobmsg_data(blob_data(cl->hdr.head)));
+       if (cl->state == CLIENT_STATE_DONE)
+               uh_header_error(cl, 400, "Bad Request");
+
+       return true;
+}
+
+static void client_header_complete(struct client *cl)
+{
+       uh_handle_file_request(cl);
+}
+
+static int client_parse_header(struct client *cl, char *data)
+{
+       char *name;
+       char *val;
+
+       if (!*data) {
+               uloop_timeout_cancel(&cl->timeout);
+               client_header_complete(cl);
+               return CLIENT_STATE_DATA;
+       }
+
+       val = strchr(data, ':');
+       if (!val)
+               return CLIENT_STATE_DONE;
+
+       *val = 0;
+       val++;
+
+       while (isspace(*val))
+               val++;
+
+       for (name = data; *name; name++)
+               if (isupper(*name))
+                       *name = tolower(*name);
+
+       blobmsg_add_string(&cl->hdr, data, val);
+
+       return CLIENT_STATE_HEADER;
+}
+
+static bool client_data_cb(struct client *cl, char *buf, int len)
+{
+       return false;
+}
+
+static bool client_header_cb(struct client *cl, char *buf, int len)
+{
+       char *newline;
+       int line_len;
+
+       newline = strstr(buf, "\r\n");
+       if (!newline)
+               return false;
+
+       *newline = 0;
+       cl->state = client_parse_header(cl, buf);
+       line_len = newline + 2 - buf;
+       ustream_consume(cl->us, line_len);
+       if (cl->state == CLIENT_STATE_DATA)
+               client_data_cb(cl, newline + 2, len - line_len);
+
+       return true;
+}
+
+typedef bool (*read_cb_t)(struct client *cl, char *buf, int len);
+static read_cb_t read_cbs[] = {
+       [CLIENT_STATE_INIT] = client_init_cb,
+       [CLIENT_STATE_HEADER] = client_header_cb,
+       [CLIENT_STATE_DATA] = client_data_cb,
+};
+
+static void client_read_cb(struct client *cl)
+{
+       struct ustream *us = cl->us;
+       char *str;
+       int len;
+
+       do {
+               str = ustream_get_read_buf(us, &len);
+               if (!str)
+                       break;
+
+               if (cl->state >= array_size(read_cbs) || !read_cbs[cl->state])
+                       break;
+
+               if (!read_cbs[cl->state](cl, str, len)) {
+                       if (len == us->r.buffer_len)
+                               uh_header_error(cl, 413, "Request Entity Too Large");
+                       break;
+               }
+       } while(1);
+}
+
+static void client_close(struct client *cl)
+{
+       uh_dispatch_done(cl);
+       uloop_timeout_cancel(&cl->timeout);
+       ustream_free(&cl->sfd.stream);
+       close(cl->sfd.fd.fd);
+       list_del(&cl->list);
+       free(cl);
+
+       uh_unblock_listeners();
+}
+
+static void client_ustream_read_cb(struct ustream *s, int bytes)
+{
+       struct client *cl = container_of(s, struct client, sfd);
+
+       client_read_cb(cl);
+}
+
+static void client_ustream_write_cb(struct ustream *s, int bytes)
+{
+       struct client *cl = container_of(s, struct client, sfd);
+
+       if (cl->dispatch_write_cb)
+               cl->dispatch_write_cb(cl);
+}
+
+static void client_notify_state(struct ustream *s)
+{
+       struct client *cl = container_of(s, struct client, sfd);
+
+       if (cl->state == CLIENT_STATE_CLOSE ||
+               (s->eof && !s->w.data_bytes) || s->write_error)
+               return client_close(cl);
+}
+
+void uh_accept_client(int fd)
+{
+       static struct client *next_client;
+       struct client *cl;
+       unsigned int sl;
+       int sfd;
+       static int client_id = 0;
+
+       if (!next_client)
+               next_client = calloc(1, sizeof(*next_client));
+
+       cl = next_client;
+
+       sl = sizeof(cl->peeraddr);
+       sfd = accept(fd, (struct sockaddr *) &cl->peeraddr, &sl);
+       if (sfd < 0)
+               return;
+
+       sl = sizeof(cl->servaddr);
+       getsockname(fd, (struct sockaddr *) &cl->servaddr, &sl);
+       cl->us = &cl->sfd.stream;
+       cl->us->string_data = true;
+       cl->us->notify_read = client_ustream_read_cb;
+       cl->us->notify_write = client_ustream_write_cb;
+       cl->us->notify_state = client_notify_state;
+       ustream_fd_init(&cl->sfd, sfd);
+
+       cl->timeout.cb = client_timeout;
+       uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
+
+       list_add_tail(&cl->list, &clients);
+
+       next_client = NULL;
+       n_clients++;
+       cl->id = client_id++;
+}
diff --git a/file.c b/file.c
new file mode 100644 (file)
index 0000000..f1f5d6d
--- /dev/null
+++ b/file.c
@@ -0,0 +1,630 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/dir.h>
+
+#include <libubox/blobmsg.h>
+
+#include "uhttpd.h"
+#include "uhttpd-mimetypes.h"
+
+static LIST_HEAD(index_files);
+
+struct index_file {
+       struct list_head list;
+       const char *name;
+};
+
+struct path_info {
+       char *root;
+       char *phys;
+       char *name;
+       char *info;
+       char *query;
+       int redirected;
+       struct stat stat;
+};
+
+enum file_hdr {
+       HDR_IF_MODIFIED_SINCE,
+       HDR_IF_UNMODIFIED_SINCE,
+       HDR_IF_MATCH,
+       HDR_IF_NONE_MATCH,
+       HDR_IF_RANGE,
+       __HDR_MAX
+};
+
+void uh_index_add(const char *filename)
+{
+       struct index_file *idx;
+
+       idx = calloc(1, sizeof(*idx));
+       idx->name = filename;
+       list_add_tail(&idx->list, &index_files);
+}
+
+static char * canonpath(const char *path, char *path_resolved)
+{
+       char path_copy[PATH_MAX];
+       char *path_cpy = path_copy;
+       char *path_res = path_resolved;
+       struct stat s;
+
+       /* relative -> absolute */
+       if (*path != '/') {
+               getcwd(path_copy, PATH_MAX);
+               strncat(path_copy, "/", PATH_MAX - strlen(path_copy));
+               strncat(path_copy, path, PATH_MAX - strlen(path_copy));
+       } else {
+               strncpy(path_copy, path, PATH_MAX);
+       }
+
+       /* normalize */
+       while ((*path_cpy != '\0') && (path_cpy < (path_copy + PATH_MAX - 2))) {
+               if (*path_cpy != '/')
+                       goto next;
+
+               /* skip repeating / */
+               if (path_cpy[1] == '/') {
+                       path_cpy++;
+                       continue;
+               }
+
+               /* /./ or /../ */
+               if (path_cpy[1] == '.') {
+                       /* skip /./ */
+                       if ((path_cpy[2] == '/') || (path_cpy[2] == '\0')) {
+                               path_cpy += 2;
+                               continue;
+                       }
+
+                       /* collapse /x/../ */
+                       if ((path_cpy[2] == '.') &&
+                           ((path_cpy[3] == '/') || (path_cpy[3] == '\0'))) {
+                               while ((path_res > path_resolved) && (*--path_res != '/'));
+
+                               path_cpy += 3;
+                               continue;
+                       }
+               }
+
+next:
+               *path_res++ = *path_cpy++;
+       }
+
+       /* remove trailing slash if not root / */
+       if ((path_res > (path_resolved+1)) && (path_res[-1] == '/'))
+               path_res--;
+       else if (path_res == path_resolved)
+               *path_res++ = '/';
+
+       *path_res = '\0';
+
+       /* test access */
+       if (!stat(path_resolved, &s) && (s.st_mode & S_IROTH))
+               return path_resolved;
+
+       return NULL;
+}
+
+/* Returns NULL on error.
+** NB: improperly encoded URL should give client 400 [Bad Syntax]; returning
+** NULL here causes 404 [Not Found], but that's not too unreasonable. */
+struct path_info * uh_path_lookup(struct client *cl, const char *url)
+{
+       static char path_phys[PATH_MAX];
+       static char path_info[PATH_MAX];
+       static struct path_info p;
+
+       char buffer[UH_LIMIT_MSGHEAD];
+       char *docroot = conf.docroot;
+       char *pathptr = NULL;
+
+       int slash = 0;
+       int no_sym = conf.no_symlinks;
+       int i = 0;
+       struct stat s;
+       struct index_file *idx;
+
+       /* back out early if url is undefined */
+       if (url == NULL)
+               return NULL;
+
+       memset(path_phys, 0, sizeof(path_phys));
+       memset(path_info, 0, sizeof(path_info));
+       memset(buffer, 0, sizeof(buffer));
+       memset(&p, 0, sizeof(p));
+
+       /* copy docroot */
+       memcpy(buffer, docroot,
+                  min(strlen(docroot), sizeof(buffer) - 1));
+
+       /* separate query string from url */
+       if ((pathptr = strchr(url, '?')) != NULL) {
+               p.query = pathptr[1] ? pathptr + 1 : NULL;
+
+               /* urldecode component w/o query */
+               if (pathptr > url) {
+                       if (uh_urldecode(&buffer[strlen(docroot)],
+                                                        sizeof(buffer) - strlen(docroot) - 1,
+                                                        url, pathptr - url ) < 0)
+                               return NULL; /* bad URL */
+               }
+       }
+
+       /* no query string, decode all of url */
+       else if (uh_urldecode(&buffer[strlen(docroot)],
+                             sizeof(buffer) - strlen(docroot) - 1,
+                             url, strlen(url) ) < 0)
+               return NULL; /* bad URL */
+
+       /* create canon path */
+       for (i = strlen(buffer), slash = (buffer[max(0, i-1)] == '/'); i >= 0; i--) {
+               if ((buffer[i] == 0) || (buffer[i] == '/')) {
+                       memset(path_info, 0, sizeof(path_info));
+                       memcpy(path_info, buffer, min(i + 1, sizeof(path_info) - 1));
+
+                       if (no_sym ? realpath(path_info, path_phys)
+                                  : canonpath(path_info, path_phys)) {
+                               memset(path_info, 0, sizeof(path_info));
+                               memcpy(path_info, &buffer[i],
+                                          min(strlen(buffer) - i, sizeof(path_info) - 1));
+
+                               break;
+                       }
+               }
+       }
+
+       /* check whether found path is within docroot */
+       if (strncmp(path_phys, docroot, strlen(docroot)) ||
+               ((path_phys[strlen(docroot)] != 0) &&
+                (path_phys[strlen(docroot)] != '/')))
+               return NULL;
+
+       /* test current path */
+       if (!stat(path_phys, &p.stat)) {
+               /* is a regular file */
+               if (p.stat.st_mode & S_IFREG) {
+                       p.root = docroot;
+                       p.phys = path_phys;
+                       p.name = &path_phys[strlen(docroot)];
+                       p.info = path_info[0] ? path_info : NULL;
+               }
+
+               /* is a directory */
+               else if ((p.stat.st_mode & S_IFDIR) && !strlen(path_info)) {
+                       /* ensure trailing slash */
+                       if (path_phys[strlen(path_phys)-1] != '/')
+                               path_phys[strlen(path_phys)] = '/';
+
+                       /* try to locate index file */
+                       memset(buffer, 0, sizeof(buffer));
+                       memcpy(buffer, path_phys, sizeof(buffer));
+                       pathptr = &buffer[strlen(buffer)];
+
+                       /* if requested url resolves to a directory and a trailing slash
+                          is missing in the request url, redirect the client to the same
+                          url with trailing slash appended */
+                       if (!slash) {
+                               uh_http_header(cl, 302, "Found");
+                               ustream_printf(cl->us, "Location: %s%s%s\r\n\r\n",
+                                               &path_phys[strlen(docroot)],
+                                               p.query ? "?" : "",
+                                               p.query ? p.query : "");
+                               uh_request_done(cl);
+                               p.redirected = 1;
+                       } else {
+                               list_for_each_entry(idx, &index_files, list) {
+                                       strncat(buffer, idx->name, sizeof(buffer));
+
+                                       if (!stat(buffer, &s) && (s.st_mode & S_IFREG)) {
+                                               memcpy(path_phys, buffer, sizeof(path_phys));
+                                               memcpy(&p.stat, &s, sizeof(p.stat));
+                                               break;
+                                       }
+
+                                       *pathptr = 0;
+                               }
+                       }
+
+                       p.root = docroot;
+                       p.phys = path_phys;
+                       p.name = &path_phys[strlen(docroot)];
+               }
+       }
+
+       return p.phys ? &p : NULL;
+}
+
+#ifdef __APPLE__
+time_t timegm (struct tm *tm);
+#endif
+
+static const char * uh_file_mime_lookup(const char *path)
+{
+       struct mimetype *m = &uh_mime_types[0];
+       const char *e;
+
+       while (m->extn) {
+               e = &path[strlen(path)-1];
+
+               while (e >= path) {
+                       if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
+                               return m->mime;
+
+                       e--;
+               }
+
+               m++;
+       }
+
+       return "application/octet-stream";
+}
+
+static const char * uh_file_mktag(struct stat *s)
+{
+       static char tag[128];
+
+       snprintf(tag, sizeof(tag), "\"%x-%x-%x\"",
+                        (unsigned int) s->st_ino,
+                        (unsigned int) s->st_size,
+                        (unsigned int) s->st_mtime);
+
+       return tag;
+}
+
+static time_t uh_file_date2unix(const char *date)
+{
+       struct tm t;
+
+       memset(&t, 0, sizeof(t));
+
+       if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL)
+               return timegm(&t);
+
+       return 0;
+}
+
+static char * uh_file_unix2date(time_t ts)
+{
+       static char str[128];
+       struct tm *t = gmtime(&ts);
+
+       strftime(str, sizeof(str), "%a, %d %b %Y %H:%M:%S GMT", t);
+
+       return str;
+}
+
+static char *uh_file_header(struct client *cl, int idx)
+{
+       if (!cl->data.file.hdr[idx])
+               return NULL;
+
+       return (char *) blobmsg_data(cl->data.file.hdr[idx]);
+}
+
+static void uh_file_response_ok_hdrs(struct client *cl, struct stat *s)
+{
+       if (s) {
+               ustream_printf(cl->us, "ETag: %s\r\n", uh_file_mktag(s));
+               ustream_printf(cl->us, "Last-Modified: %s\r\n",
+                              uh_file_unix2date(s->st_mtime));
+       }
+       ustream_printf(cl->us, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
+}
+
+static void uh_file_response_200(struct client *cl, struct stat *s)
+{
+       uh_http_header(cl, 200, "OK");
+       return uh_file_response_ok_hdrs(cl, s);
+}
+
+static void uh_file_response_304(struct client *cl, struct stat *s)
+{
+       uh_http_header(cl, 304, "Not Modified");
+
+       return uh_file_response_ok_hdrs(cl, s);
+}
+
+static void uh_file_response_412(struct client *cl)
+{
+       uh_http_header(cl, 412, "Precondition Failed");
+}
+
+static bool uh_file_if_match(struct client *cl, struct stat *s)
+{
+       const char *tag = uh_file_mktag(s);
+       char *hdr = uh_file_header(cl, HDR_IF_MATCH);
+       char *p;
+       int i;
+
+       if (!hdr)
+               return true;
+
+       p = &hdr[0];
+       for (i = 0; i < strlen(hdr); i++)
+       {
+               if ((hdr[i] == ' ') || (hdr[i] == ',')) {
+                       hdr[i++] = 0;
+                       p = &hdr[i];
+               } else if (!strcmp(p, "*") || !strcmp(p, tag)) {
+                       return true;
+               }
+       }
+
+       uh_file_response_412(cl);
+       return false;
+}
+
+static int uh_file_if_modified_since(struct client *cl, struct stat *s)
+{
+       char *hdr = uh_file_header(cl, HDR_IF_MODIFIED_SINCE);
+
+       if (!hdr)
+               return true;
+
+       if (uh_file_date2unix(hdr) >= s->st_mtime) {
+               uh_file_response_304(cl, s);
+               return false;
+       }
+
+       return true;
+}
+
+static int uh_file_if_none_match(struct client *cl, struct stat *s)
+{
+       const char *tag = uh_file_mktag(s);
+       char *hdr = uh_file_header(cl, HDR_IF_NONE_MATCH);
+       char *p;
+       int i;
+
+       if (!hdr)
+               return true;
+
+       p = &hdr[0];
+       for (i = 0; i < strlen(hdr); i++) {
+               if ((hdr[i] == ' ') || (hdr[i] == ',')) {
+                       hdr[i++] = 0;
+                       p = &hdr[i];
+               } else if (!strcmp(p, "*") || !strcmp(p, tag)) {
+                       if ((cl->request.method == UH_HTTP_MSG_GET) ||
+                               (cl->request.method == UH_HTTP_MSG_HEAD))
+                               uh_file_response_304(cl, s);
+                       else
+                               uh_file_response_412(cl);
+
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static int uh_file_if_range(struct client *cl, struct stat *s)
+{
+       char *hdr = uh_file_header(cl, HDR_IF_RANGE);
+
+       if (hdr) {
+               uh_file_response_412(cl);
+               return false;
+       }
+
+       return true;
+}
+
+static int uh_file_if_unmodified_since(struct client *cl, struct stat *s)
+{
+       char *hdr = uh_file_header(cl, HDR_IF_UNMODIFIED_SINCE);
+
+       if (hdr && uh_file_date2unix(hdr) <= s->st_mtime) {
+               uh_file_response_412(cl);
+               return false;
+       }
+
+       return true;
+}
+
+
+static int uh_file_scandir_filter_dir(const struct dirent *e)
+{
+       return strcmp(e->d_name, ".") ? 1 : 0;
+}
+
+static void uh_file_dirlist(struct client *cl, struct path_info *pi)
+{
+       int i;
+       int count = 0;
+       char filename[PATH_MAX];
+       char *pathptr;
+       struct dirent **files = NULL;
+       struct stat s;
+
+       uh_file_response_200(cl, NULL);
+       ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
+
+       uh_chunk_printf(cl,
+               "<html><head><title>Index of %s</title></head>"
+               "<body><h1>Index of %s</h1><hr /><ol>",
+               pi->name, pi->name);
+
+       if ((count = scandir(pi->phys, &files, uh_file_scandir_filter_dir,
+                                                alphasort)) > 0)
+       {
+               memset(filename, 0, sizeof(filename));
+               memcpy(filename, pi->phys, sizeof(filename));
+               pathptr = &filename[strlen(filename)];
+
+               /* list subdirs */
+               for (i = 0; i < count; i++) {
+                       strncat(filename, files[i]->d_name,
+                                       sizeof(filename) - strlen(files[i]->d_name));
+
+                       if (!stat(filename, &s) &&
+                               (s.st_mode & S_IFDIR) && (s.st_mode & S_IXOTH))
+                               uh_chunk_printf(cl,
+                                       "<li><strong><a href='%s%s'>%s</a>/"
+                                       "</strong><br /><small>modified: %s"
+                                       "<br />directory - %.02f kbyte<br />"
+                                       "<br /></small></li>",
+                                       pi->name, files[i]->d_name,
+                                       files[i]->d_name,
+                                       uh_file_unix2date(s.st_mtime),
+                                       s.st_size / 1024.0);
+
+                       *pathptr = 0;
+               }
+
+               /* list files */
+               for (i = 0; i < count; i++) {
+                       strncat(filename, files[i]->d_name,
+                                       sizeof(filename) - strlen(files[i]->d_name));
+
+                       if (!stat(filename, &s) &&
+                               !(s.st_mode & S_IFDIR) && (s.st_mode & S_IROTH))
+                               uh_chunk_printf(cl,
+                                       "<li><strong><a href='%s%s'>%s</a>"
+                                       "</strong><br /><small>modified: %s"
+                                       "<br />%s - %.02f kbyte<br />"
+                                       "<br /></small></li>",
+                                       pi->name, files[i]->d_name,
+                                       files[i]->d_name,
+                                       uh_file_unix2date(s.st_mtime),
+                                       uh_file_mime_lookup(filename),
+                                       s.st_size / 1024.0);
+
+                       *pathptr = 0;
+               }
+       }
+
+       uh_chunk_printf(cl, "</ol><hr /></body></html>");
+       uh_request_done(cl);
+
+       if (files)
+       {
+               for (i = 0; i < count; i++)
+                       free(files[i]);
+
+               free(files);
+       }
+}
+
+static void file_write_cb(struct client *cl)
+{
+       char buf[512];
+       int fd = cl->data.file.fd;
+       int r;
+
+       while (cl->us->w.data_bytes < 256) {
+               r = read(fd, buf, sizeof(buf));
+               if (r < 0) {
+                       if (errno == EINTR)
+                               continue;
+               }
+
+               if (!r) {
+                       uh_request_done(cl);
+                       return;
+               }
+
+               uh_chunk_write(cl, buf, r);
+       }
+}
+
+static void uh_file_free(struct client *cl)
+{
+       close(cl->data.file.fd);
+}
+
+static void uh_file_data(struct client *cl, struct path_info *pi, int fd)
+{
+       /* test preconditions */
+       if (!uh_file_if_modified_since(cl, &pi->stat) ||
+               !uh_file_if_match(cl, &pi->stat) ||
+               !uh_file_if_range(cl, &pi->stat) ||
+               !uh_file_if_unmodified_since(cl, &pi->stat) ||
+               !uh_file_if_none_match(cl, &pi->stat)) {
+               uh_request_done(cl);
+               close(fd);
+               return;
+       }
+
+       /* write status */
+       uh_file_response_200(cl, &pi->stat);
+
+       ustream_printf(cl->us, "Content-Type: %s\r\n",
+                          uh_file_mime_lookup(pi->name));
+
+       ustream_printf(cl->us, "Content-Length: %i\r\n\r\n",
+                          pi->stat.st_size);
+
+
+       /* send body */
+       if (cl->request.method == UH_HTTP_MSG_HEAD) {
+               uh_request_done(cl);
+               close(fd);
+               return;
+       }
+
+       cl->data.file.fd = fd;
+       cl->dispatch_write_cb = file_write_cb;
+       cl->dispatch_free = uh_file_free;
+       file_write_cb(cl);
+}
+
+static void uh_file_request(struct client *cl, struct path_info *pi)
+{
+       static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
+               [HDR_IF_MODIFIED_SINCE] = { "if-modified-since", BLOBMSG_TYPE_STRING },
+               [HDR_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", BLOBMSG_TYPE_STRING },
+               [HDR_IF_MATCH] = { "if-match", BLOBMSG_TYPE_STRING },
+               [HDR_IF_NONE_MATCH] = { "if-none-match", BLOBMSG_TYPE_STRING },
+               [HDR_IF_RANGE] = { "if-range", BLOBMSG_TYPE_STRING },
+       };
+       struct blob_attr *tb[__HDR_MAX];
+       int fd;
+
+       blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
+
+       cl->data.file.hdr = tb;
+       if ((pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0))
+               uh_file_data(cl, pi, fd);
+       else if ((pi->stat.st_mode & S_IFDIR) && !conf.no_dirlists)
+               uh_file_dirlist(cl, pi);
+       else
+               uh_client_error(cl, 403, "Forbidden",
+                               "Access to this resource is forbidden");
+       cl->data.file.hdr = NULL;
+}
+
+void uh_handle_file_request(struct client *cl)
+{
+       struct path_info *pi;
+
+       pi = uh_path_lookup(cl, cl->request.url);
+       if (!pi) {
+               uh_request_done(cl);
+               return;
+       }
+
+       if (pi->redirected)
+               return;
+
+       uh_file_request(cl, pi);
+}
diff --git a/listen.c b/listen.c
new file mode 100644 (file)
index 0000000..74a85f9
--- /dev/null
+++ b/listen.c
@@ -0,0 +1,177 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+
+#include "uhttpd.h"
+
+struct listener {
+       struct list_head list;
+       struct uloop_fd fd;
+       int socket;
+       int n_clients;
+       struct sockaddr_in6 addr;
+       bool tls;
+       bool blocked;
+};
+
+static LIST_HEAD(listeners);
+static int n_blocked;
+
+static void uh_block_listener(struct listener *l)
+{
+       uloop_fd_delete(&l->fd);
+       n_blocked++;
+       l->blocked = true;
+}
+
+void uh_unblock_listeners(void)
+{
+       struct listener *l;
+
+       if (!n_blocked && conf.max_requests &&
+           n_clients >= conf.max_requests)
+               return;
+
+       list_for_each_entry(l, &listeners, list) {
+               if (!l->blocked)
+                       continue;
+
+               n_blocked--;
+               l->blocked = false;
+               uloop_fd_add(&l->fd, ULOOP_READ);
+       }
+}
+
+static void listener_cb(struct uloop_fd *fd, unsigned int events)
+{
+       struct listener *l = container_of(fd, struct listener, fd);
+
+       uh_accept_client(fd->fd);
+
+       if (conf.max_requests && n_clients >= conf.max_requests)
+               uh_block_listener(l);
+}
+
+void uh_setup_listeners(void)
+{
+       struct listener *l;
+
+       list_for_each_entry(l, &listeners, list) {
+               l->fd.cb = listener_cb;
+               uloop_fd_add(&l->fd, ULOOP_READ);
+       }
+}
+
+int uh_socket_bind(const char *host, const char *port, bool tls)
+{
+       int sock = -1;
+       int yes = 1;
+       int status;
+       int bound = 0;
+       struct listener *l = NULL;
+       struct addrinfo *addrs = NULL, *p = NULL;
+       static struct addrinfo hints = {
+               .ai_family = AF_UNSPEC,
+               .ai_socktype = SOCK_STREAM,
+               .ai_flags = AI_PASSIVE,
+       };
+
+       if ((status = getaddrinfo(host, port, &hints, &addrs)) != 0) {
+               fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
+               return -1;
+       }
+
+       /* try to bind a new socket to each found address */
+       for (p = addrs; p; p = p->ai_next) {
+               /* get the socket */
+               sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+               if (sock < 0) {
+                       perror("socket()");
+                       goto error;
+               }
+
+               /* "address already in use" */
+               if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) {
+                       perror("setsockopt()");
+                       goto error;
+               }
+
+               /* TCP keep-alive */
+               if (conf.tcp_keepalive > 0) {
+                       int ret = 0;
+#ifdef linux
+                       int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt;
+
+                       tcp_ka_idl = 1;
+                       tcp_ka_cnt = 3;
+                       tcp_ka_int = conf->tcp_keepalive;
+                       ret =   setsockopt(sock, SOL_TCP, TCP_KEEPIDLE,  &tcp_ka_idl, sizeof(tcp_ka_idl)) ||
+                               setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int)) ||
+                               setsockopt(sock, SOL_TCP, TCP_KEEPCNT,   &tcp_ka_cnt, sizeof(tcp_ka_cnt));
+#endif
+
+                       if (ret || setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)))
+                               fprintf(stderr, "Notice: Unable to enable TCP keep-alive: %s\n",
+                                       strerror(errno));
+               }
+
+               /* required to get parallel v4 + v6 working */
+               if (p->ai_family == AF_INET6 &&
+                   setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
+                       perror("setsockopt()");
+                       goto error;
+               }
+
+               /* bind */
+               if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) {
+                       perror("bind()");
+                       goto error;
+               }
+
+               /* listen */
+               if (listen(sock, UH_LIMIT_CLIENTS) < 0) {
+                       perror("listen()");
+                       goto error;
+               }
+
+               fd_cloexec(sock);
+
+               l = calloc(1, sizeof(*l));
+               if (!l)
+                       goto error;
+
+               l->fd.fd = sock;
+               l->tls = tls;
+               list_add_tail(&l->list, &listeners);
+
+               continue;
+
+error:
+               if (sock > 0)
+                       close(sock);
+       }
+
+       freeaddrinfo(addrs);
+
+       return bound;
+}
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..37cba7f
--- /dev/null
+++ b/main.c
@@ -0,0 +1,105 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <getopt.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+
+#include <libubox/usock.h>
+
+#include "uhttpd.h"
+
+
+static int run_server(void)
+{
+       uloop_init();
+       uh_setup_listeners();
+       uloop_run();
+
+       return 0;
+}
+
+static void add_listener_arg(char *arg, bool tls)
+{
+       char *host = NULL;
+       char *port = arg;
+       char *s;
+
+       s = strrchr(arg, ':');
+       if (s) {
+               host = arg;
+               port = s + 1;
+               *s = 0;
+       }
+       uh_socket_bind(host, port, tls);
+}
+
+static int usage(const char *name)
+{
+       fprintf(stderr, "Usage: %s -p <port>\n", name);
+       return 1;
+}
+
+static void init_defaults(void)
+{
+       conf.network_timeout = 30;
+       conf.http_keepalive = 0; /* fixme */
+
+       uh_index_add("index.html");
+       uh_index_add("index.htm");
+       uh_index_add("default.html");
+       uh_index_add("default.htm");
+}
+
+int main(int argc, char **argv)
+{
+       int ch;
+
+       init_defaults();
+       signal(SIGPIPE, SIG_IGN);
+
+       while ((ch = getopt(argc, argv, "sp:h:")) != -1) {
+               bool tls = false;
+               switch(ch) {
+               case 's':
+                       tls = true;
+               case 'p':
+                       add_listener_arg(optarg, tls);
+                       break;
+
+               case 'h':
+                       /* docroot */
+                       if (!realpath(optarg, conf.docroot)) {
+                               fprintf(stderr, "Error: Invalid directory %s: %s\n",
+                                               optarg, strerror(errno));
+                               exit(1);
+                       }
+                       break;
+               default:
+                       return usage(argv[0]);
+               }
+       }
+
+       return run_server();
+}
diff --git a/uhttpd-mimetypes.h b/uhttpd-mimetypes.h
new file mode 100644 (file)
index 0000000..7fd885c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - MIME type definitions
+ *
+ *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _UHTTPD_MIMETYPES_
+
+struct mimetype {
+       const char *extn;
+       const char *mime;
+};
+
+static struct mimetype uh_mime_types[] = {
+
+       { "txt",     "text/plain" },
+       { "log",     "text/plain" },
+       { "js",      "text/javascript" },
+       { "css",     "text/css" },
+       { "htm",     "text/html" },
+       { "html",    "text/html" },
+       { "diff",    "text/x-patch" },
+       { "patch",   "text/x-patch" },
+       { "c",       "text/x-csrc" },
+       { "h",       "text/x-chdr" },
+       { "o",       "text/x-object" },
+       { "ko",      "text/x-object" },
+
+       { "bmp",     "image/bmp" },
+       { "gif",     "image/gif" },
+       { "png",     "image/png" },
+       { "jpg",     "image/jpeg" },
+       { "jpeg",    "image/jpeg" },
+       { "svg",     "image/svg+xml" },
+
+       { "zip",     "application/zip" },
+       { "pdf",     "application/pdf" },
+       { "xml",     "application/xml" },
+       { "xsl",     "application/xml" },
+       { "doc",     "application/msword" },
+       { "ppt",     "application/vnd.ms-powerpoint" },
+       { "xls",     "application/vnd.ms-excel" },
+       { "odt",     "application/vnd.oasis.opendocument.text" },
+       { "odp",     "application/vnd.oasis.opendocument.presentation" },
+       { "pl",      "application/x-perl" },
+       { "sh",      "application/x-shellscript" },
+       { "php",     "application/x-php" },
+       { "deb",     "application/x-deb" },
+       { "iso",     "application/x-cd-image" },
+       { "tar.gz",  "application/x-compressed-tar" },
+       { "tgz",     "application/x-compressed-tar" },
+       { "gz",      "application/x-gzip" },
+       { "tar.bz2", "application/x-bzip-compressed-tar" },
+       { "tbz",     "application/x-bzip-compressed-tar" },
+       { "bz2",     "application/x-bzip" },
+       { "tar",     "application/x-tar" },
+       { "rar",     "application/x-rar-compressed" },
+
+       { "mp3",     "audio/mpeg" },
+       { "ogg",     "audio/x-vorbis+ogg" },
+       { "wav",     "audio/x-wav" },
+
+       { "mpg",     "video/mpeg" },
+       { "mpeg",    "video/mpeg" },
+       { "avi",     "video/x-msvideo" },
+
+       { "README",  "text/plain" },
+       { "log",     "text/plain" },
+       { "cfg",     "text/plain" },
+       { "conf",    "text/plain" },
+
+       { "pac",                "application/x-ns-proxy-autoconfig" },
+       { "wpad.dat",   "application/x-ns-proxy-autoconfig" },
+
+       { NULL, NULL }
+};
+
+#endif
+
diff --git a/uhttpd.h b/uhttpd.h
new file mode 100644 (file)
index 0000000..0cb8eb8
--- /dev/null
+++ b/uhttpd.h
@@ -0,0 +1,149 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Main header
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef __UHTTPD_H
+#define __UHTTPD_H
+
+#include <netinet/in.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include <libubox/list.h>
+#include <libubox/uloop.h>
+#include <libubox/ustream.h>
+#include <libubox/blob.h>
+
+#include "utils.h"
+
+#define UH_LIMIT_CLIENTS       64
+#define UH_LIMIT_HEADERS       64
+#define UH_LIMIT_MSGHEAD       4096
+
+struct config {
+       char docroot[PATH_MAX];
+       char *realm;
+       char *file;
+       char *error_handler;
+       int no_symlinks;
+       int no_dirlists;
+       int network_timeout;
+       int rfc1918_filter;
+       int tcp_keepalive;
+       int max_requests;
+       int http_keepalive;
+};
+
+enum http_method {
+       UH_HTTP_MSG_GET,
+       UH_HTTP_MSG_POST,
+       UH_HTTP_MSG_HEAD,
+};
+
+enum http_version {
+       UH_HTTP_VER_0_9,
+       UH_HTTP_VER_1_0,
+       UH_HTTP_VER_1_1,
+};
+
+struct http_request {
+       enum http_method method;
+       enum http_version version;
+       int redirect_status;
+       char *url;
+       struct auth_realm *realm;
+};
+
+struct http_response {
+       int statuscode;
+       char *statusmsg;
+       char *headers[UH_LIMIT_HEADERS];
+};
+
+enum client_state {
+       CLIENT_STATE_INIT,
+       CLIENT_STATE_HEADER,
+       CLIENT_STATE_DATA,
+       CLIENT_STATE_DONE,
+       CLIENT_STATE_CLOSE,
+};
+
+struct client {
+       struct list_head list;
+       int id;
+
+       struct ustream *us;
+       struct ustream_fd sfd;
+#ifdef HAVE_TLS
+       struct ustream_ssl stream_ssl;
+#endif
+       struct uloop_fd rpipe;
+       struct uloop_fd wpipe;
+       struct uloop_process proc;
+       struct uloop_timeout timeout;
+       bool (*cb)(struct client *);
+       void *priv;
+
+       enum client_state state;
+
+       struct http_request request;
+       struct http_response response;
+       struct sockaddr_in6 servaddr;
+       struct sockaddr_in6 peeraddr;
+
+       struct blob_buf hdr;
+
+       void (*dispatch_write_cb)(struct client *cl);
+       void (*dispatch_free)(struct client *cl);
+
+       union {
+               struct {
+                       struct blob_attr **hdr;
+                       int fd;
+               } file;
+       } data;
+};
+
+extern int n_clients;
+extern struct config conf;
+
+void uh_index_add(const char *filename);
+
+void uh_accept_client(int fd);
+
+void uh_unblock_listeners(void);
+void uh_setup_listeners(void);
+int uh_socket_bind(const char *host, const char *port, bool tls);
+
+bool uh_use_chunked(struct client *cl);
+void uh_chunk_write(struct client *cl, const void *data, int len);
+void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg);
+
+void __printf(2, 3)
+uh_chunk_printf(struct client *cl, const char *format, ...);
+
+void uh_chunk_eof(struct client *cl);
+void uh_request_done(struct client *cl);
+
+void uh_http_header(struct client *cl, int code, const char *summary);
+void __printf(4, 5)
+uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...);
+
+void uh_handle_file_request(struct client *cl);
+
+#endif
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..ec0b3aa
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,210 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include <ctype.h>
+#include "uhttpd.h"
+
+bool uh_use_chunked(struct client *cl)
+{
+       if (cl->request.version != UH_HTTP_VER_1_1)
+               return false;
+
+       if (cl->request.method == UH_HTTP_MSG_HEAD)
+               return false;
+
+       return true;
+}
+
+void uh_chunk_write(struct client *cl, const void *data, int len)
+{
+       bool chunked = uh_use_chunked(cl);
+
+       uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
+       if (chunked)
+               ustream_printf(cl->us, "%X\r\n", len);
+       ustream_write(cl->us, data, len, true);
+       if (chunked)
+               ustream_printf(cl->us, "\r\n", len);
+}
+
+void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
+{
+       char buf[256];
+       va_list arg2;
+       int len;
+
+       uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
+       if (!uh_use_chunked(cl)) {
+               ustream_vprintf(cl->us, format, arg);
+               return;
+       }
+
+       va_copy(arg2, arg);
+       len = vsnprintf(buf, sizeof(buf), format, arg2);
+       va_end(arg2);
+
+       ustream_printf(cl->us, "%X\r\n", len);
+       if (len < sizeof(buf))
+               ustream_write(cl->us, buf, len, true);
+       else
+               ustream_vprintf(cl->us, format, arg);
+       ustream_printf(cl->us, "\r\n", len);
+}
+
+void uh_chunk_printf(struct client *cl, const char *format, ...)
+{
+       va_list arg;
+
+       va_start(arg, format);
+       uh_chunk_vprintf(cl, format, arg);
+       va_end(arg);
+}
+
+void uh_chunk_eof(struct client *cl)
+{
+       if (!uh_use_chunked(cl))
+               return;
+
+       ustream_printf(cl->us, "0\r\n\r\n");
+}
+
+/* blen is the size of buf; slen is the length of src.  The input-string need
+** not be, and the output string will not be, null-terminated.  Returns the
+** length of the decoded string, -1 on buffer overflow, -2 on malformed string. */
+int uh_urldecode(char *buf, int blen, const char *src, int slen)
+{
+       int i;
+       int len = 0;
+
+#define hex(x) \
+       (((x) <= '9') ? ((x) - '0') : \
+               (((x) <= 'F') ? ((x) - 'A' + 10) : \
+                       ((x) - 'a' + 10)))
+
+       for (i = 0; (i < slen) && (len < blen); i++)
+       {
+               if (src[i] == '%')
+               {
+                       if (((i+2) < slen) && isxdigit(src[i+1]) && isxdigit(src[i+2]))
+                       {
+                               buf[len++] = (char)(16 * hex(src[i+1]) + hex(src[i+2]));
+                               i += 2;
+                       }
+                       else
+                       {
+                               /* Encoding error: it's hard to think of a
+                               ** scenario in which returning an incorrect
+                               ** 'decoding' of the malformed string is
+                               ** preferable to signaling an error condition. */
+                               #if 0 /* WORSE_IS_BETTER */
+                                   buf[len++] = '%';
+                               #else
+                                   return -2;
+                               #endif
+                       }
+               }
+               else
+               {
+                       buf[len++] = src[i];
+               }
+       }
+
+       return (i == slen) ? len : -1;
+}
+
+/* blen is the size of buf; slen is the length of src.  The input-string need
+** not be, and the output string will not be, null-terminated.  Returns the
+** length of the encoded string, or -1 on error (buffer overflow) */
+int uh_urlencode(char *buf, int blen, const char *src, int slen)
+{
+       int i;
+       int len = 0;
+       const char hex[] = "0123456789abcdef";
+
+       for (i = 0; (i < slen) && (len < blen); i++)
+       {
+               if( isalnum(src[i]) || (src[i] == '-') || (src[i] == '_') ||
+                   (src[i] == '.') || (src[i] == '~') )
+               {
+                       buf[len++] = src[i];
+               }
+               else if ((len+3) <= blen)
+               {
+                       buf[len++] = '%';
+                       buf[len++] = hex[(src[i] >> 4) & 15];
+                       buf[len++] = hex[ src[i]       & 15];
+               }
+               else
+               {
+                       len = -1;
+                       break;
+               }
+       }
+
+       return (i == slen) ? len : -1;
+}
+
+int uh_b64decode(char *buf, int blen, const unsigned char *src, int slen)
+{
+       int i = 0;
+       int len = 0;
+
+       unsigned int cin  = 0;
+       unsigned int cout = 0;
+
+
+       for (i = 0; (i <= slen) && (src[i] != 0); i++)
+       {
+               cin = src[i];
+
+               if ((cin >= '0') && (cin <= '9'))
+                       cin = cin - '0' + 52;
+               else if ((cin >= 'A') && (cin <= 'Z'))
+                       cin = cin - 'A';
+               else if ((cin >= 'a') && (cin <= 'z'))
+                       cin = cin - 'a' + 26;
+               else if (cin == '+')
+                       cin = 62;
+               else if (cin == '/')
+                       cin = 63;
+               else if (cin == '=')
+                       cin = 0;
+               else
+                       continue;
+
+               cout = (cout << 6) | cin;
+
+               if ((i % 4) == 3)
+               {
+                       if ((len + 3) < blen)
+                       {
+                               buf[len++] = (char)(cout >> 16);
+                               buf[len++] = (char)(cout >> 8);
+                               buf[len++] = (char)(cout);
+                       }
+                       else
+                       {
+                               break;
+                       }
+               }
+       }
+
+       buf[len++] = 0;
+       return len;
+}
diff --git a/utils.h b/utils.h
new file mode 100644 (file)
index 0000000..4f74f5c
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,58 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Utility header
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _UHTTPD_UTILS_
+
+#include <sys/stat.h>
+
+#include <stdarg.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define min(x, y) (((x) < (y)) ? (x) : (y))
+#define max(x, y) (((x) > (y)) ? (x) : (y))
+
+#define array_size(x) \
+       (sizeof(x) / sizeof(x[0]))
+
+#define fd_cloexec(fd) \
+       fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)
+
+#ifdef __APPLE__
+static inline void clearenv(void)
+{
+       extern char **environ;
+       environ = NULL;
+}
+#endif
+
+#ifdef __GNUC__
+#define __printf(a, b) __attribute__((format(printf, a, b)))
+#else
+#define __printf(a, b)
+#endif
+
+int uh_urldecode(char *buf, int blen, const char *src, int slen);
+int uh_urlencode(char *buf, int blen, const char *src, int slen);
+int uh_b64decode(char *buf, int blen, const unsigned char *src, int slen);
+#endif