aboutsummaryrefslogtreecommitdiffstats
path: root/src/blogc-runserver
diff options
context:
space:
mode:
authorRafael G. Martins <rafael@rafaelmartins.eng.br>2016-09-25 02:57:21 +0200
committerRafael G. Martins <rafael@rafaelmartins.eng.br>2016-09-25 02:57:21 +0200
commit6ac53d4c783340ae9139c7f4dcfe9bfddace5892 (patch)
tree2637740546dcf002fbfe39eb94d6f722a2c5c7d6 /src/blogc-runserver
parent0c916e2c8b56c320fdc81f68d445194559479041 (diff)
downloadblogc-6ac53d4c783340ae9139c7f4dcfe9bfddace5892.tar.gz
blogc-6ac53d4c783340ae9139c7f4dcfe9bfddace5892.tar.bz2
blogc-6ac53d4c783340ae9139c7f4dcfe9bfddace5892.zip
runserver: reimplemented http server without libevent
yeah, this patch implements a "complete" http server for static files. It is not the best code possible, and would be easily DDoS'able if used in production, as it spawns a thread for each request, without limiting. I'm sickish and this is the best code I can deliver now. At least it works! ;)
Diffstat (limited to 'src/blogc-runserver')
-rw-r--r--src/blogc-runserver/httpd-utils.c100
-rw-r--r--src/blogc-runserver/httpd-utils.h19
-rw-r--r--src/blogc-runserver/httpd.c254
-rw-r--r--src/blogc-runserver/httpd.h14
-rw-r--r--src/blogc-runserver/main.c316
-rw-r--r--src/blogc-runserver/mime.c168
-rw-r--r--src/blogc-runserver/mime.h15
7 files changed, 572 insertions, 314 deletions
diff --git a/src/blogc-runserver/httpd-utils.c b/src/blogc-runserver/httpd-utils.c
new file mode 100644
index 0000000..e935668
--- /dev/null
+++ b/src/blogc-runserver/httpd-utils.c
@@ -0,0 +1,100 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include "../common/utils.h"
+#include "httpd-utils.h"
+
+
+char*
+br_readline(int socket)
+{
+ bc_string_t *rv = bc_string_new();
+ char buffer[READLINE_BUFFER_SIZE];
+ ssize_t len;
+
+ while ((len = read(socket, buffer, READLINE_BUFFER_SIZE)) != -1) {
+ for (ssize_t i = 0; i < len; i++) {
+ if (buffer[i] == '\r' || buffer[i] == '\n')
+ goto end;
+ bc_string_append_c(rv, buffer[i]);
+ }
+ }
+
+end:
+ return bc_string_free(rv, false);
+}
+
+
+int
+br_hextoi(const char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+
+char*
+br_urldecode(const char *str)
+{
+ bc_string_t *rv = bc_string_new();
+
+ for (size_t i = 0; i < strlen(str); i++) {
+ switch (str[i]) {
+ case '%':
+ if (i + 2 < strlen(str)) {
+ int p1 = br_hextoi(str[i + 1]) * 16;
+ int p2 = br_hextoi(str[i + 2]);
+ if (p1 >= 0 && p2 >= 0) {
+ bc_string_append_c(rv, p1 + p2);
+ i += 2;
+ continue;
+ }
+ }
+ bc_string_append_c(rv, '%');
+ break;
+ case '+':
+ bc_string_append_c(rv, ' ');
+ break;
+ default:
+ bc_string_append_c(rv, str[i]);
+ }
+ }
+
+ return bc_string_free(rv, false);
+}
+
+
+const char*
+br_get_extension(const char *filename)
+{
+ const char *ext = NULL;
+ unsigned int i;
+ for (i = strlen(filename); i > 0; i--) {
+ if (filename[i] == '.') {
+ ext = filename + i + 1;
+ break;
+ }
+ if ((filename[i] == '/') || (filename[i] == '\\'))
+ return NULL;
+ }
+ if (i == 0)
+ return NULL;
+ return ext;
+}
diff --git a/src/blogc-runserver/httpd-utils.h b/src/blogc-runserver/httpd-utils.h
new file mode 100644
index 0000000..b9af852
--- /dev/null
+++ b/src/blogc-runserver/httpd-utils.h
@@ -0,0 +1,19 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _HTTPD_UTILS_H
+#define _HTTPD_UTILS_H
+
+#define READLINE_BUFFER_SIZE 1024
+
+char* br_readline(int socket);
+int br_hextoi(const char c);
+char* br_urldecode(const char *str);
+const char* br_get_extension(const char *filename);
+
+#endif /* _HTTPD_UTILS_H */
diff --git a/src/blogc-runserver/httpd.c b/src/blogc-runserver/httpd.c
new file mode 100644
index 0000000..c659580
--- /dev/null
+++ b/src/blogc-runserver/httpd.c
@@ -0,0 +1,254 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include "../common/error.h"
+#include "../common/file.h"
+#include "../common/utils.h"
+#include "mime.h"
+#include "httpd-utils.h"
+
+#define LISTEN_BACKLOG 100
+
+
+static void
+error(int socket, int status_code, const char *error)
+{
+ char *str = bc_strdup_printf(
+ "HTTP/1.0 %d %s\r\n"
+ "Content-Type: text/html\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "<h1>%s</h1>\n", status_code, error, error);
+ write(socket, str, strlen(str));
+ free(str);
+}
+
+
+typedef struct {
+ int socket;
+ const char *docroot;
+} request_data_t;
+
+
+static void*
+handle_request(void *arg)
+{
+ request_data_t *req = arg;
+ int client_socket = req->socket;
+ const char *docroot = req->docroot;
+ free(arg);
+
+ char *conn_line = br_readline(client_socket);
+ char **pieces = bc_str_split(conn_line, ' ', 3);
+ if (bc_strv_length(pieces) != 3) {
+ error(client_socket, 400, "Bad Request");
+ goto point1;
+ }
+
+ if (strcmp(pieces[0], "GET") != 0) {
+ error(client_socket, 405, "Method Not Allowed");
+ goto point1;
+ }
+
+ char **pieces2 = bc_str_split(pieces[1], '?', 2);
+ char *path = br_urldecode(pieces2[0]);
+ bc_strv_free(pieces2);
+
+ if (path == NULL) {
+ error(client_socket, 400, "Bad Request");
+ goto point1;
+ }
+
+ char *abs_path = bc_strdup_printf("%s/%s", docroot, path);
+ char *real_path = realpath(abs_path, NULL);
+ free(abs_path);
+
+ if (real_path == NULL) {
+ error(client_socket, 404, "Not Found");
+ goto point1;
+ }
+
+ char *real_root = realpath(docroot, NULL);
+ if (real_root == NULL) {
+ error(client_socket, 500, "Internal Server Error");
+ goto point2;
+ }
+
+ if (0 != strncmp(real_root, real_path, strlen(real_root))) {
+ error(client_socket, 404, "Not Found");
+ goto point3;
+ }
+
+ struct stat st;
+ if (0 > stat(real_path, &st)) {
+ error(client_socket, 404, "Not Found");
+ goto point3;
+ }
+
+ bool add_slash = false;
+
+ if (S_ISDIR(st.st_mode)) {
+ char *found = br_mime_guess_index(real_path);
+
+ if (found == NULL) {
+ error(client_socket, 403, "Forbidden");
+ goto point3;
+ }
+
+ size_t path_len = strlen(path);
+ if (path_len > 0 && path[path_len - 1] != '/')
+ add_slash = true;
+
+ free(real_path);
+ real_path = found;
+ }
+
+ if (0 != access(real_path, F_OK)) {
+ error(client_socket, 500, "Internal Server Error");
+ goto point3;
+ }
+
+ if (add_slash) {
+ // production webservers usually returns 301 in such cases, but 302 is
+ // better for development/testing.
+ char *tmp = bc_strdup_printf(
+ "HTTP/1.0 302 Found\r\n"
+ "Location: %s/\r\n"
+ "Connection: close\r\n"
+ "\r\n", path);
+ write(client_socket, tmp, strlen(tmp));
+ free(tmp);
+ goto point3;
+ }
+
+ size_t len;
+ bc_error_t *err = NULL;
+ char* contents = bc_file_get_contents(real_path, false, &len, &err);
+ if (err != NULL) {
+ error(client_socket, 500, "Internal Server Error");
+ bc_error_free(err);
+ goto point3;
+ }
+
+ char *out = bc_strdup_printf(
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: %d\r\n"
+ "Connection: close\r\n"
+ "\r\n", br_mime_guess_content_type(real_path), len);
+ write(client_socket, out, strlen(out));
+ free(out);
+
+ write(client_socket, contents, len);
+
+point3:
+ free(real_root);
+point2:
+ free(real_path);
+point1:
+ bc_strv_free(pieces);
+ close(client_socket);
+ return NULL;
+}
+
+
+int
+br_httpd_run(const char *host, unsigned short port, const char *docroot)
+{
+ int server_socket = socket(AF_INET, SOCK_STREAM, 0);
+ if (server_socket == -1) {
+ fprintf(stderr, "Failed to open server socket: %s\n", strerror(errno));
+ return 1;
+ }
+
+ int rv = 0;
+
+ int value = 1;
+ if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) < 0) {
+ fprintf(stderr, "Failed to set socket option: %s\n", strerror(errno));
+ rv = 1;
+ goto cleanup;
+ }
+
+ struct sockaddr_in server_addr;
+ memset(&server_addr, 0, sizeof(struct sockaddr_in));
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(port);
+ if ((server_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
+ fprintf(stderr, "Invalid server listen address: %s\n", host);
+ rv = 1;
+ goto cleanup;
+ }
+
+ if ((bind(server_socket, (struct sockaddr*) &server_addr,
+ sizeof(struct sockaddr_in))) == -1)
+ {
+ fprintf(stderr, "Failed to bind to server socket (%s:%d): %s\n",
+ host, port, strerror(errno));
+ rv = 1;
+ goto cleanup;
+ }
+
+ if (listen(server_socket, LISTEN_BACKLOG) == -1) {
+ fprintf(stderr, "Failed to listen to server socket: %s\n", strerror(errno));
+ rv = 1;
+ goto cleanup;
+ }
+
+ fprintf(stderr,
+ " * Running on http://%s:%d/\n"
+ "\n"
+ "WARNING!!! This is a development server, DO NOT RUN IT IN PRODUCTION!\n"
+ "\n", host, port);
+
+ socklen_t len = sizeof(struct sockaddr_in);
+
+ while (1) {
+ struct sockaddr_in client_addr;
+ int client_socket = accept(server_socket,
+ (struct sockaddr *) &client_addr, &len);
+ if (client_socket == -1) {
+ fprintf(stderr, "Failed to accept connection: %s\n", strerror(errno));
+ rv = 1;
+ goto cleanup;
+ }
+
+ request_data_t *arg = malloc(sizeof(request_data_t));
+ arg->socket = client_socket;
+ arg->docroot = docroot;
+
+ // this isn't really safe. the server can be easily DDoS'ed.
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, handle_request, arg) != 0) {
+ fprintf(stderr, "Failed to create thread\n");
+ rv = 1;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ close(server_socket);
+ return rv;
+}
diff --git a/src/blogc-runserver/httpd.h b/src/blogc-runserver/httpd.h
new file mode 100644
index 0000000..a59a467
--- /dev/null
+++ b/src/blogc-runserver/httpd.h
@@ -0,0 +1,14 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _HTTPD_H
+#define _HTTPD_H
+
+int br_httpd_run(const char *host, unsigned short port, const char *docroot);
+
+#endif /* _HTTPD_H */
diff --git a/src/blogc-runserver/main.c b/src/blogc-runserver/main.c
index 685b6e3..d12fc99 100644
--- a/src/blogc-runserver/main.c
+++ b/src/blogc-runserver/main.c
@@ -10,324 +10,12 @@
#include <config.h>
#endif /* HAVE_CONFIG_H */
-#include <event2/event.h>
-#include <event2/http.h>
-#include <event2/buffer.h>
#include <signal.h>
-#include <stdbool.h>
-#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
#include "../common/utils.h"
-
-
-// mime types with index should be in the begin of the list. first NULL
-// index aborts the lookup, for optimization
-static const struct content_type_map {
- const char *mimetype;
- const char *extension;
- const char *index;
-} content_types[] = {
-
- // with index
- {"text/html", "html", "index.html"},
- {"text/html", "htm", "index.htm"},
- {"text/html", "shtml", "index.shtml"},
- {"text/xml", "xml", "index.xml"},
- {"text/plain", "txt", "index.txt"},
- {"application/xhtml+xml", "xhtml", "index.xhtml"},
-
- // without index
- {"text/css", "css", NULL},
- {"image/gif", "gif", NULL},
- {"image/jpeg", "jpeg", NULL},
- {"image/jpeg", "jpg", NULL},
- {"application/javascript", "js", NULL},
- {"application/atom+xml", "atom", NULL},
- {"application/rss+xml", "rss", NULL},
- {"text/mathml", "mml", NULL},
- {"text/vnd.sun.j2me.app-descriptor", "jad", NULL},
- {"text/vnd.wap.wml", "wml", NULL},
- {"text/x-component", "htc", NULL},
- {"image/png", "png", NULL},
- {"image/tiff", "tif", NULL},
- {"image/tiff", "tiff", NULL},
- {"image/vnd.wap.wbmp", "wbmp", NULL},
- {"image/x-icon", "ico", NULL},
- {"image/x-jng", "jng", NULL},
- {"image/x-ms-bmp", "bmp", NULL},
- {"image/svg+xml", "svg", NULL},
- {"image/svg+xml", "svgz", NULL},
- {"image/webp", "webp", NULL},
- {"application/font-woff", "woff", NULL},
- {"application/java-archive", "jar", NULL},
- {"application/java-archive", "war", NULL},
- {"application/java-archive", "ear", NULL},
- {"application/json", "json", NULL},
- {"application/mac-binhex40", "hqx", NULL},
- {"application/msword", "doc", NULL},
- {"application/pdf", "pdf", NULL},
- {"application/postscript", "ps", NULL},
- {"application/postscript", "eps", NULL},
- {"application/postscript", "ai", NULL},
- {"application/rtf", "rtf", NULL},
- {"application/vnd.apple.mpegurl", "m3u8", NULL},
- {"application/vnd.ms-excel", "xls", NULL},
- {"application/vnd.ms-fontobject", "eot", NULL},
- {"application/vnd.ms-powerpoint", "ppt", NULL},
- {"application/vnd.wap.wmlc", "wmlc", NULL},
- {"application/vnd.google-earth.kml+xml", "kml", NULL},
- {"application/vnd.google-earth.kmz", "kmz", NULL},
- {"application/x-7z-compressed", "7z", NULL},
- {"application/x-cocoa", "cco", NULL},
- {"application/x-java-archive-diff", "jardiff", NULL},
- {"application/x-java-jnlp-file", "jnlp", NULL},
- {"application/x-makeself", "run", NULL},
- {"application/x-perl", "pl", NULL},
- {"application/x-perl", "pm", NULL},
- {"application/x-pilot", "prc", NULL},
- {"application/x-pilot", "pdb", NULL},
- {"application/x-rar-compressed", "rar", NULL},
- {"application/x-redhat-package-manager", "rpm", NULL},
- {"application/x-sea", "sea", NULL},
- {"application/x-shockwave-flash", "swf", NULL},
- {"application/x-stuffit", "sit", NULL},
- {"application/x-tcl", "tcl", NULL},
- {"application/x-tcl", "tk", NULL},
- {"application/x-x509-ca-cert", "der", NULL},
- {"application/x-x509-ca-cert", "pem", NULL},
- {"application/x-x509-ca-cert", "crt", NULL},
- {"application/x-xpinstall", "xpi", NULL},
- {"application/xspf+xml", "xspf", NULL},
- {"application/zip", "zip", NULL},
- {"application/octet-stream", "bin", NULL},
- {"application/octet-stream", "exe", NULL},
- {"application/octet-stream", "dll", NULL},
- {"application/octet-stream", "deb", NULL},
- {"application/octet-stream", "dmg", NULL},
- {"application/octet-stream", "iso", NULL},
- {"application/octet-stream", "img", NULL},
- {"application/octet-stream", "msi", NULL},
- {"application/octet-stream", "msp", NULL},
- {"application/octet-stream", "msm", NULL},
- {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL},
- {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL},
- {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL},
- {"audio/midi", "mid", NULL},
- {"audio/midi", "midi", NULL},
- {"audio/midi", "kar", NULL},
- {"audio/mpeg", "mp3", NULL},
- {"audio/ogg", "ogg", NULL},
- {"audio/x-m4a", "m4a", NULL},
- {"audio/x-realaudio", "ra", NULL},
- {"video/3gpp", "3gpp", NULL},
- {"video/3gpp", "3gp", NULL},
- {"video/mp2t", "ts", NULL},
- {"video/mp4", "mp4", NULL},
- {"video/mpeg", "mpeg", NULL},
- {"video/mpeg", "mpg", NULL},
- {"video/quicktime", "mov", NULL},
- {"video/webm", "webm", NULL},
- {"video/x-flv", "flv", NULL},
- {"video/x-m4v", "m4v", NULL},
- {"video/x-mng", "mng", NULL},
- {"video/x-ms-asf", "asx", NULL},
- {"video/x-ms-asf", "asf", NULL},
- {"video/x-ms-wmv", "wmv", NULL},
- {"video/x-msvideo", "avi", NULL},
- {NULL, NULL, NULL}
-};
-
-
-static const char*
-get_extension(const char *filename)
-{
- const char *ext = NULL;
- unsigned int i;
- for (i = strlen(filename); i > 0; i--) {
- if (filename[i] == '.') {
- ext = filename + i + 1;
- break;
- }
- }
- if (i == 0)
- return NULL;
- return ext;
-}
-
-
-static const char*
-guess_content_type(const char *filename)
-{
- const char *extension = get_extension(filename);
- if (extension == NULL)
- goto default_type;
- for (unsigned int i = 0; content_types[i].extension != NULL; i++) {
- if (0 == strcmp(content_types[i].extension, extension)) {
- return content_types[i].mimetype;
- }
- }
-default_type:
- return "application/octet-stream";
-}
-
-
-static void
-handler(struct evhttp_request *request, void *ptr)
-{
- const char *root = ptr;
- const char *uri = evhttp_request_get_uri(request);
-
- struct evhttp_uri *decoded_uri = evhttp_uri_parse(uri);
- if (decoded_uri == NULL) {
- evhttp_send_error(request, 400, "Bad request");
- return;
- }
-
- const char *path = evhttp_uri_get_path(decoded_uri);
- if (path == NULL)
- path = "/";
-
- char *decoded_path = evhttp_uridecode(path, 0, NULL);
- if (decoded_path == NULL) {
- evhttp_send_error(request, 400, "Bad request");
- goto point1;
- }
-
- char *abs_path = bc_strdup_printf("%s/%s", root, decoded_path);
- char *real_path = realpath(abs_path, NULL);
- free(abs_path);
-
- if (real_path == NULL) {
- evhttp_send_error(request, 404, "Not found");
- goto point2;
- }
-
- char *real_root = realpath(root, NULL);
- if (real_root == NULL) {
- evhttp_send_error(request, 500, "Internal server error");
- goto point3;
- }
-
- if (0 != strncmp(real_root, real_path, strlen(real_root))) {
- evhttp_send_error(request, 404, "Not found");
- goto point4;
- }
-
- struct stat st;
- if (0 > stat(real_path, &st)) {
- evhttp_send_error(request, 404, "Not found");
- goto point4;
- }
-
- bool add_slash = false;
-
- if (S_ISDIR(st.st_mode)) {
- char *found = NULL;
-
- for (unsigned int i = 0; content_types[i].index != NULL; i++) {
- char *f = bc_strdup_printf("%s/%s", real_path,
- content_types[i].index);
- if (0 == access(f, F_OK)) {
- found = f;
- break;
- }
- free(f);
- }
-
- if (found == NULL) {
- evhttp_send_error(request, 403, "Forbidden");
- goto point4;
- }
-
- size_t path_len = strlen(path);
- if (path_len > 0 && path[path_len - 1] != '/')
- add_slash = true;
-
- free(real_path);
- real_path = found;
- }
-
- int fd;
- if ((fd = open(real_path, O_RDONLY)) < 0) {
- evhttp_send_error(request, 500, "Internal server error");
- goto point4;
- }
-
- const char *type = guess_content_type(real_path);
-
- if (fstat(fd, &st) < 0) {
- evhttp_send_error(request, 500, "Internal server error");
- goto point4;
- }
-
- struct evkeyvalq *headers = evhttp_request_get_output_headers(request);
-
- if (add_slash) {
- char *tmp = bc_strdup_printf("%s/", path);
- evhttp_add_header(headers, "Location", tmp);
- free(tmp);
- // production webservers usually returns 301 in such cases, but 302 is
- // better for development/testing.
- evhttp_send_reply(request, 302, "Found", NULL);
- goto point4;
- }
-
- evhttp_add_header(headers, "Content-Type", type);
- char *content_length = bc_strdup_printf("%zu", st.st_size);
- evhttp_add_header(headers, "Content-Length", content_length);
- free(content_length);
-
- struct evbuffer *evb = evbuffer_new();
- evbuffer_add_file(evb, fd, 0, st.st_size);
- evhttp_send_reply(request, 200, "OK", evb);
-
-point4:
- free(real_root);
-point3:
- free(real_path);
-point2:
- free(decoded_path);
-point1:
- evhttp_uri_free(decoded_uri);
-}
-
-
-static int
-runserver(const char *address, unsigned short port, const char *root)
-{
- struct event_base *base = event_base_new();
- if (base == NULL) {
- fprintf(stderr, "error: failed to initialize event base\n");
- return 1;
- }
-
- struct evhttp *http = evhttp_new(base);
- if (http == NULL) {
- fprintf(stderr, "error: failed to initialize HTTP server\n");
- return 1;
- }
-
- evhttp_set_gencb(http, handler, (char*) root);
-
- evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_HEAD);
-
- if (0 != evhttp_bind_socket(http, address, port)) {
- fprintf(stderr, "error: failed to bind socket to %s:%d\n", address,
- port);
- return 1;
- }
-
- fprintf(stderr, " * Running on http://%s:%d/\n", address, port);
-
- event_base_dispatch(base);
-
- return 0;
-}
+#include "httpd.h"
static void
@@ -421,7 +109,7 @@ main(int argc, char **argv)
if (host == NULL)
host = bc_strdup("127.0.0.1");
- rv = runserver(host, port, docroot);
+ rv = br_httpd_run(host, port, docroot);
cleanup:
free(host);
diff --git a/src/blogc-runserver/mime.c b/src/blogc-runserver/mime.c
new file mode 100644
index 0000000..b42d70a
--- /dev/null
+++ b/src/blogc-runserver/mime.c
@@ -0,0 +1,168 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../common/utils.h"
+#include "httpd-utils.h"
+
+
+// mime types with index should be in the begin of the list. first NULL
+// index aborts the lookup, for optimization
+static const struct content_type_map {
+ const char *mimetype;
+ const char *extension;
+ const char *index;
+} content_types[] = {
+
+ // with index
+ {"text/html", "html", "index.html"},
+ {"text/html", "htm", "index.htm"},
+ {"text/html", "shtml", "index.shtml"},
+ {"text/xml", "xml", "index.xml"},
+ {"text/plain", "txt", "index.txt"},
+ {"application/xhtml+xml", "xhtml", "index.xhtml"},
+
+ // without index
+ {"text/css", "css", NULL},
+ {"image/gif", "gif", NULL},
+ {"image/jpeg", "jpeg", NULL},
+ {"image/jpeg", "jpg", NULL},
+ {"application/javascript", "js", NULL},
+ {"application/atom+xml", "atom", NULL},
+ {"application/rss+xml", "rss", NULL},
+ {"text/mathml", "mml", NULL},
+ {"text/vnd.sun.j2me.app-descriptor", "jad", NULL},
+ {"text/vnd.wap.wml", "wml", NULL},
+ {"text/x-component", "htc", NULL},
+ {"image/png", "png", NULL},
+ {"image/tiff", "tif", NULL},
+ {"image/tiff", "tiff", NULL},
+ {"image/vnd.wap.wbmp", "wbmp", NULL},
+ {"image/x-icon", "ico", NULL},
+ {"image/x-jng", "jng", NULL},
+ {"image/x-ms-bmp", "bmp", NULL},
+ {"image/svg+xml", "svg", NULL},
+ {"image/svg+xml", "svgz", NULL},
+ {"image/webp", "webp", NULL},
+ {"application/font-woff", "woff", NULL},
+ {"application/java-archive", "jar", NULL},
+ {"application/java-archive", "war", NULL},
+ {"application/java-archive", "ear", NULL},
+ {"application/json", "json", NULL},
+ {"application/mac-binhex40", "hqx", NULL},
+ {"application/msword", "doc", NULL},
+ {"application/pdf", "pdf", NULL},
+ {"application/postscript", "ps", NULL},
+ {"application/postscript", "eps", NULL},
+ {"application/postscript", "ai", NULL},
+ {"application/rtf", "rtf", NULL},
+ {"application/vnd.apple.mpegurl", "m3u8", NULL},
+ {"application/vnd.ms-excel", "xls", NULL},
+ {"application/vnd.ms-fontobject", "eot", NULL},
+ {"application/vnd.ms-powerpoint", "ppt", NULL},
+ {"application/vnd.wap.wmlc", "wmlc", NULL},
+ {"application/vnd.google-earth.kml+xml", "kml", NULL},
+ {"application/vnd.google-earth.kmz", "kmz", NULL},
+ {"application/x-7z-compressed", "7z", NULL},
+ {"application/x-cocoa", "cco", NULL},
+ {"application/x-java-archive-diff", "jardiff", NULL},
+ {"application/x-java-jnlp-file", "jnlp", NULL},
+ {"application/x-makeself", "run", NULL},
+ {"application/x-perl", "pl", NULL},
+ {"application/x-perl", "pm", NULL},
+ {"application/x-pilot", "prc", NULL},
+ {"application/x-pilot", "pdb", NULL},
+ {"application/x-rar-compressed", "rar", NULL},
+ {"application/x-redhat-package-manager", "rpm", NULL},
+ {"application/x-sea", "sea", NULL},
+ {"application/x-shockwave-flash", "swf", NULL},
+ {"application/x-stuffit", "sit", NULL},
+ {"application/x-tcl", "tcl", NULL},
+ {"application/x-tcl", "tk", NULL},
+ {"application/x-x509-ca-cert", "der", NULL},
+ {"application/x-x509-ca-cert", "pem", NULL},
+ {"application/x-x509-ca-cert", "crt", NULL},
+ {"application/x-xpinstall", "xpi", NULL},
+ {"application/xspf+xml", "xspf", NULL},
+ {"application/zip", "zip", NULL},
+ {"application/octet-stream", "bin", NULL},
+ {"application/octet-stream", "exe", NULL},
+ {"application/octet-stream", "dll", NULL},
+ {"application/octet-stream", "deb", NULL},
+ {"application/octet-stream", "dmg", NULL},
+ {"application/octet-stream", "iso", NULL},
+ {"application/octet-stream", "img", NULL},
+ {"application/octet-stream", "msi", NULL},
+ {"application/octet-stream", "msp", NULL},
+ {"application/octet-stream", "msm", NULL},
+ {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL},
+ {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL},
+ {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL},
+ {"audio/midi", "mid", NULL},
+ {"audio/midi", "midi", NULL},
+ {"audio/midi", "kar", NULL},
+ {"audio/mpeg", "mp3", NULL},
+ {"audio/ogg", "ogg", NULL},
+ {"audio/x-m4a", "m4a", NULL},
+ {"audio/x-realaudio", "ra", NULL},
+ {"video/3gpp", "3gpp", NULL},
+ {"video/3gpp", "3gp", NULL},
+ {"video/mp2t", "ts", NULL},
+ {"video/mp4", "mp4", NULL},
+ {"video/mpeg", "mpeg", NULL},
+ {"video/mpeg", "mpg", NULL},
+ {"video/quicktime", "mov", NULL},
+ {"video/webm", "webm", NULL},
+ {"video/x-flv", "flv", NULL},
+ {"video/x-m4v", "m4v", NULL},
+ {"video/x-mng", "mng", NULL},
+ {"video/x-ms-asf", "asx", NULL},
+ {"video/x-ms-asf", "asf", NULL},
+ {"video/x-ms-wmv", "wmv", NULL},
+ {"video/x-msvideo", "avi", NULL},
+ {NULL, NULL, NULL}
+};
+
+
+const char*
+br_mime_guess_content_type(const char *filename)
+{
+ const char *extension = br_get_extension(filename);
+ if (extension == NULL)
+ goto default_type;
+ for (size_t i = 0; content_types[i].extension != NULL; i++) {
+ if (0 == strcmp(content_types[i].extension, extension)) {
+ return content_types[i].mimetype;
+ }
+ }
+
+default_type:
+ return "application/octet-stream";
+}
+
+
+char*
+br_mime_guess_index(const char *path)
+{
+ char *found = NULL;
+ for (size_t i = 0; content_types[i].index != NULL; i++) {
+ char *f = bc_strdup_printf("%s/%s", path, content_types[i].index);
+ if (0 == access(f, F_OK)) {
+ found = f;
+ break;
+ }
+ free(f);
+ }
+ return found;
+}
diff --git a/src/blogc-runserver/mime.h b/src/blogc-runserver/mime.h
new file mode 100644
index 0000000..a4b6409
--- /dev/null
+++ b/src/blogc-runserver/mime.h
@@ -0,0 +1,15 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _MIME_H
+#define _MIME_H
+
+const char* br_mime_guess_content_type(const char *filename);
+char* br_mime_guess_index(const char *path);
+
+#endif /* _MIME_H */