aboutsummaryrefslogtreecommitdiffstats
path: root/src/blogc-runserver/httpd.c
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/httpd.c
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/httpd.c')
-rw-r--r--src/blogc-runserver/httpd.c254
1 files changed, 254 insertions, 0 deletions
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;
+}