├── .gitignore ├── src ├── stacktrace.h ├── tests │ ├── test_log.c │ ├── test_common.c │ ├── test_http_server.c │ ├── test_ioloop.c │ ├── test_iostream.c │ ├── test_buffer.c │ ├── test_site.c │ └── test_http.c ├── mod.h ├── log.h ├── mod_static.h ├── stacktrace.c ├── sample-conf.json ├── buffer.h ├── ioloop.h ├── conf_test.c ├── mod.c ├── Makefile ├── json │ ├── LICENSE │ ├── json.h │ └── json.c ├── log.c ├── common.h ├── site.h ├── iostream.h ├── common.c ├── breeze.c ├── http_connection.c ├── ioloop.c ├── buffer.c ├── http_server.c ├── http.h ├── site.c ├── mod_static.c ├── iostream.c └── http.c ├── COPYING ├── tools └── send_dummy_data.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | src/TAGS 2 | src/*.o 3 | src/test_* 4 | src/mod_static 5 | src/conf_test 6 | src/breeze 7 | -------------------------------------------------------------------------------- /src/stacktrace.h: -------------------------------------------------------------------------------- 1 | #ifndef __STACKTRACE_H 2 | 3 | #define __STACKTRACE_H 4 | 5 | void print_stacktrace_on_error(); 6 | 7 | #endif /* end of include guard: __STACKTRACE_H */ 8 | -------------------------------------------------------------------------------- /src/tests/test_log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | debug("This should not appear"); 6 | info("This should appear in stdout"); 7 | configure_log(WARN, "/tmp/test.log", 1); 8 | info("This should not appear now"); 9 | warn("This should appear in stderr and log file"); 10 | error("This should appear in stderr and log file"); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/mod.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODULE_H_ 2 | #define __MODULE_H_ 3 | 4 | #include "http.h" 5 | #include "json.h" 6 | 7 | typedef void* (*mod_conf_create)(json_value *conf_value); 8 | typedef void (*mod_conf_destroy)(void *conf); 9 | typedef int (*mod_init)(); 10 | 11 | typedef struct _module { 12 | char *name; 13 | mod_init init; 14 | mod_conf_create create; 15 | mod_conf_destroy destroy; 16 | handler_func handler; 17 | } module_t; 18 | 19 | module_t *find_module(const char* name); 20 | int init_modules(); 21 | 22 | #endif /* __MODULE_H_ */ 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This program is free software: you can redistribute it and/or modify 2 | it under the terms of the GNU General Public License as published by 3 | the Free Software Foundation, either version 3 of the License, or 4 | (at your option) any later version. 5 | 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __LOG_H_ 2 | #define __LOG_H_ 3 | 4 | enum _log_level { 5 | DEBUG = 0, 6 | INFO, 7 | WARN, 8 | ERROR 9 | }; 10 | 11 | int configure_log(int level, const char* file, int use_console); 12 | 13 | void logging(int lvl, const char *file, const int line, const char *fmt, ...); 14 | 15 | #define debug(fmt, ...) logging(DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 16 | #define info(fmt, ...) logging(INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 17 | #define warn(fmt, ...) logging(WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 18 | #define error(fmt, ...) logging(ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 19 | 20 | #endif /* __LOG_H_ */ 21 | -------------------------------------------------------------------------------- /src/mod_static.h: -------------------------------------------------------------------------------- 1 | #ifndef __MOD_STATIC_H_ 2 | #define __MOD_STATIC_H_ 3 | 4 | #include "http.h" 5 | #include "mod.h" 6 | 7 | typedef struct _mod_static_conf { 8 | char *root; 9 | char *index; 10 | int enable_list_dir; 11 | int show_hidden_file; 12 | int enable_etag; 13 | int enable_range_req; 14 | 15 | // -1 means not set expires header; other means 16 | // the expiration time (in hours) 17 | int expire_hours; 18 | } mod_static_conf_t; 19 | 20 | extern module_t mod_static; 21 | 22 | int mod_static_init(); 23 | int static_file_handle(request_t *req, response_t *resp, handler_ctx_t *ctx); 24 | 25 | #endif /* __MOD_STATIC_H_ */ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/stacktrace.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "log.h" 3 | #include "stacktrace.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MAX_TRACE_DEPTH 30 11 | 12 | static void stacktrace_handler(int sig) { 13 | void *trace[MAX_TRACE_DEPTH]; 14 | size_t depth; 15 | 16 | depth = backtrace(trace, MAX_TRACE_DEPTH); 17 | 18 | error("Error captured. Signal: %s", strsignal(sig)); 19 | backtrace_symbols_fd(trace, depth, 2); 20 | exit(1); 21 | } 22 | 23 | void print_stacktrace_on_error() { 24 | signal(SIGSEGV, stacktrace_handler); 25 | signal(SIGFPE, stacktrace_handler); 26 | signal(SIGABRT, stacktrace_handler); 27 | } 28 | -------------------------------------------------------------------------------- /src/sample-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen" : "0.0.0.0:8000", 3 | "daemonize" : true, 4 | "pidfile" : "/var/run/breeze.pid", 5 | "logfile" : "/var/log/breeze.log", 6 | "loglevel" : "debug", 7 | 8 | "sites" : [{ 9 | "host" : "localhost", 10 | "locations" : [{ 11 | "path": "/", 12 | "module": "static", 13 | "root": "/home/jerry/Documents/wiki", 14 | "expires": "480", 15 | "list_dir": false, 16 | "gzip": true 17 | }] 18 | }, { 19 | "host" : "jerrypeng.me", 20 | "locations" : [{ 21 | "path": "~ .*\\.(png|jpg|jpeg|gif|css)", 22 | "module": "static", 23 | "root": "/home/jerry/Documents/wiki", 24 | "expires": "24", 25 | "list_dir": false, 26 | "gzip": true 27 | }] 28 | }] 29 | } 30 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __BUFFER_H 2 | 3 | #define __BUFFER_H 4 | 5 | #include 6 | 7 | struct _buffer; 8 | 9 | typedef struct _buffer buffer_t; 10 | 11 | typedef void (*consumer_func)(void *data, size_t len, void *args); 12 | 13 | buffer_t *buffer_create(size_t size); 14 | int buffer_destroy(buffer_t *buf); 15 | int buffer_is_full(buffer_t *buf); 16 | int buffer_is_empty(buffer_t *buf); 17 | int buffer_put(buffer_t *buf, void *data, size_t len); 18 | size_t buffer_get(buffer_t *buf, size_t len, void *target, size_t capacity); 19 | size_t buffer_skip(buffer_t *buf, size_t len); 20 | ssize_t buffer_fill(buffer_t *buf, int fd); 21 | ssize_t buffer_flush(buffer_t *buf, int fd); 22 | size_t buffer_consume(buffer_t *buf, size_t len, consumer_func cb, void *args); 23 | int buffer_locate(buffer_t *buf, char *delimiter); 24 | 25 | #endif /* end of include guard: __BUFFER_H */ 26 | -------------------------------------------------------------------------------- /src/ioloop.h: -------------------------------------------------------------------------------- 1 | #ifndef __IOLOOP_H 2 | 3 | #define __IOLOOP_H 4 | 5 | 6 | #define MAX_TIMEOUTS 100 7 | 8 | 9 | struct _ioloop; 10 | 11 | typedef struct _ioloop ioloop_t; 12 | 13 | typedef void (*io_handler)(ioloop_t *loop, int fd, unsigned int events, void *args); 14 | typedef void (*callback_handler)(ioloop_t *loop, void *args); 15 | 16 | ioloop_t *ioloop_create(unsigned int maxfds); 17 | int ioloop_destroy(ioloop_t *loop); 18 | int ioloop_start(ioloop_t *loop); 19 | int ioloop_stop(ioloop_t *loop); 20 | int ioloop_add_handler(ioloop_t *loop, int fd, unsigned int events, io_handler handler, void *args); 21 | int ioloop_update_handler(ioloop_t *loop, int fd, unsigned int events); 22 | io_handler ioloop_remove_handler(ioloop_t *loop, int fd); 23 | int ioloop_add_callback(ioloop_t *loop, callback_handler handler, void *args); 24 | 25 | int set_nonblocking(int sockfd); 26 | 27 | #endif /* end of include guard: __IOLOOP_H */ 28 | -------------------------------------------------------------------------------- /tools/send_dummy_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import socket 4 | import sys 5 | import time 6 | 7 | HOST = 'localhost' # The remote host 8 | PORT = 9999 # The same port as used by the server 9 | s = None 10 | for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM): 11 | af, socktype, proto, canonname, sa = res 12 | try: 13 | s = socket.socket(af, socktype, proto) 14 | except socket.error, msg: 15 | s = None 16 | continue 17 | try: 18 | s.connect(sa) 19 | except socket.error, msg: 20 | s.close() 21 | s = None 22 | continue 23 | break 24 | if s is None: 25 | print 'could not open socket' 26 | sys.exit(1) 27 | 28 | filename = sys.argv[1] 29 | if filename == '-': 30 | for l in sys.stdin: 31 | s.send(l) 32 | else: 33 | try: 34 | with open(filename) as f: 35 | for l in f: 36 | s.send(l) 37 | finally: 38 | s.close() 39 | 40 | -------------------------------------------------------------------------------- /src/conf_test.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "site.h" 3 | #include "log.h" 4 | #include "stacktrace.h" 5 | #include 6 | 7 | int main(int argc, char *argv[]) { 8 | server_t *server; 9 | site_conf_t *site_conf; 10 | site_t *site; 11 | location_t *loc; 12 | int i; 13 | 14 | if (argc < 2) { 15 | fprintf(stderr, "Usage: %s config_file", argv[0]); 16 | } 17 | print_stacktrace_on_error(); 18 | server = server_parse_conf(argv[1]); 19 | if (server == NULL) { 20 | fprintf(stderr, "Error parsing config file"); 21 | return 1; 22 | } 23 | 24 | site_conf = (site_conf_t*) server->handler_conf; 25 | for (i = 0; i < site_conf->site_size; i++) { 26 | site = site_conf->sites[i]; 27 | info("Found site: %s", site->host); 28 | for (loc = site->location_head->next; loc != NULL; loc = loc->next) { 29 | info("\t Location: "); 30 | if (loc->match_type == URI_REGEX) { 31 | info("[regex]"); 32 | } else if (loc->match_type == URI_PREFIX) { 33 | info(loc->uri.prefix); 34 | } 35 | } 36 | } 37 | return 0; 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/mod.c: -------------------------------------------------------------------------------- 1 | #include "mod.h" 2 | #include "mod_static.h" 3 | #include "http.h" 4 | #include "log.h" 5 | #include 6 | #include 7 | 8 | static module_t *modules[] = { 9 | &mod_static 10 | }; 11 | 12 | static int modules_inited = 0; 13 | 14 | module_t *find_module(const char* name) { 15 | int i; 16 | size_t size; 17 | 18 | size = sizeof(modules)/sizeof(modules[0]); 19 | 20 | for (i = 0; i < size; i++) { 21 | if (strcmp(name, modules[i]->name) == 0) { 22 | return modules[i]; 23 | } 24 | } 25 | 26 | return NULL; 27 | } 28 | 29 | int init_modules() { 30 | int i; 31 | size_t size; 32 | module_t *mod; 33 | 34 | if (modules_inited) { 35 | return 0; 36 | } 37 | 38 | size = sizeof(modules)/sizeof(modules[0]); 39 | 40 | for (i = 0; i < size; i++) { 41 | mod = modules[i]; 42 | if (mod->init != NULL) { 43 | info("Init module: %s", mod->name); 44 | if (mod->init() != 0) { 45 | error("Error initializing module: %s", mod->name); 46 | return -1; 47 | } 48 | } 49 | } 50 | 51 | modules_inited = 1; 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS ?= -g -rdynamic -Wall -I. -I./json 3 | LDFLAGS ?= -g -rdynamic -lcrypt -lm 4 | 5 | objects = common.o log.o ioloop.o buffer.o iostream.o http.o stacktrace.o http_connection.o http_server.o site.o json.o mod_static.o mod.o breeze.o 6 | testobjs = test_common.o test_log.o test_buffer.o test_ioloop.o test_iostream.o test_http.o test_http_server.o test_site.o 7 | executables = test_common test_log test_buffer test_ioloop test_iostream test_http test_http_server test_site breeze 8 | 9 | vpath %.c tests json 10 | 11 | .PHONY: all 12 | all: $(executables) 13 | 14 | %.o: %.c %.h 15 | $(CC) $(CFLAGS) -c $< -o $@ 16 | 17 | test_%: test_%.o %.o common.o json.o stacktrace.o log.o 18 | $(CC) $(LDFLAGS) $^ -o $@ 19 | 20 | test_iostream: ioloop.o buffer.o 21 | test_site: http.o ioloop.o iostream.o buffer.o http_connection.o mod.o mod_static.o 22 | test_http: stacktrace.o iostream.o ioloop.o buffer.o http_connection.o 23 | test_http_server: http_connection.o iostream.o ioloop.o buffer.o http.o site.o mod.o mod_static.o 24 | 25 | breeze: $(objects) 26 | $(CC) $(LDFLAGS) $^ -o $@ 27 | 28 | .PHONY: clean 29 | clean: 30 | -rm -f $(objects) $(executables) $(tests) $(testobjs) 31 | @echo Project cleaned 32 | 33 | -------------------------------------------------------------------------------- /src/json/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (C) 2012, 2013 James McLaughlin et al. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /src/tests/test_common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | #include 6 | 7 | void test_date_functions() { 8 | char buf[50]; 9 | time_t time; 10 | 11 | assert(current_http_date(buf, 50) == 0); 12 | info("Current time in HTTP format: %s", buf); 13 | 14 | assert(parse_http_date(buf, &time) == 0); 15 | info("Current time in seconds: %ld", time); 16 | 17 | time += 81; 18 | 19 | format_http_date(&time, buf, 50); 20 | info("Current time in HTTP format(add 81 secs): %s", buf); 21 | } 22 | 23 | void test_path_starts_with() { 24 | assert(path_starts_with("/", "/bar/11") > 0); 25 | assert(path_starts_with("/", "/") > 0); 26 | assert(path_starts_with("/bar", "/bar/11") > 0); 27 | assert(path_starts_with("/bar", "/bar/") > 0); 28 | assert(path_starts_with("/bar", "/bar") > 0); 29 | assert(path_starts_with("/bar", "/bar1") == 0); 30 | assert(path_starts_with("/bar/", "/bar/11") > 0); 31 | } 32 | 33 | void test_url_decode() { 34 | char buf[20]; 35 | url_decode(buf, "/foo%20bar"); 36 | info("URL decoded: %s", buf); 37 | assert(strcmp("/foo bar", buf) == 0); 38 | url_decode(buf, "/%E4%B8%AD%E5%9B%BD%E4%BA%BA"); 39 | info("URL decoded: %s", buf); 40 | assert(strcmp("/中国人", buf) == 0); 41 | } 42 | 43 | 44 | int main(int argc, char *argv[]) { 45 | test_date_functions(); 46 | test_path_starts_with(); 47 | test_url_decode(); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static const char* LEVEL_NAMES[] = {"DEBUG", "INFO", "WARN", "ERROR"}; 9 | 10 | static int enable_console = 1; 11 | static FILE *log_stream = NULL; 12 | static int log_level = INFO; 13 | 14 | int configure_log(int lvl, const char* file, int use_console) { 15 | FILE *stream = NULL; 16 | int res = 0; 17 | 18 | if (lvl > ERROR) 19 | lvl = ERROR; 20 | else if (lvl < DEBUG) 21 | lvl = DEBUG; 22 | log_level = lvl; 23 | enable_console = use_console; 24 | if (file != NULL) { 25 | stream = fopen(file, "a"); 26 | if (stream == NULL) { 27 | error("Error opening log file"); 28 | res = 1; 29 | } else { 30 | log_stream = stream; 31 | } 32 | } 33 | 34 | return res; 35 | } 36 | 37 | void logging(int lvl, const char *file, const int line, const char *fmt, ...) { 38 | va_list ap; 39 | char buffer[512], *ptr = buffer; 40 | int size, cap = 512; 41 | time_t ts; 42 | struct tm *tmp; 43 | 44 | if (lvl < log_level) { 45 | return; 46 | } 47 | 48 | ts = time(NULL); 49 | tmp = localtime(&ts); 50 | size = strftime(ptr, cap, "[%Y-%m-%d %H:%M:%S]", tmp); 51 | ptr += size; 52 | cap -= size; 53 | size = snprintf(ptr, cap, "[%-5s][%s:%d] ", 54 | LEVEL_NAMES[lvl], file, line); 55 | ptr += size; 56 | cap -= size; 57 | 58 | va_start(ap, fmt); 59 | size = vsnprintf(ptr, cap, fmt, ap); 60 | va_end(ap); 61 | 62 | *(ptr + size) = '\n'; 63 | *(ptr + size + 1) = '\0'; 64 | 65 | if (enable_console) { 66 | if (lvl >= WARN) { 67 | fputs(buffer, stderr); 68 | } else { 69 | fputs(buffer, stdout); 70 | } 71 | } 72 | 73 | if (log_stream != NULL) { 74 | fputs(buffer, log_stream); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMON_H 2 | #define __COMMON_H 3 | 4 | #define _XOPEN_SOURCE 5 | #define _GNU_SOURCE 6 | #define _BSD_SOURCE 7 | 8 | #include 9 | #include 10 | 11 | enum _return_status { 12 | STATUS_ERROR = -1, 13 | STATUS_COMPLETE = 0, 14 | STATUS_INCOMPLETE = 1 15 | }; 16 | 17 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 18 | 19 | #define _BREEZE_NAME "breeze/0.1.0" 20 | 21 | #define DECLARE_CONF_VARIABLES() \ 22 | int i; \ 23 | char *name; \ 24 | json_value *val; 25 | 26 | #define BEGIN_CONF_HANDLE(json) \ 27 | for (i = 0; i < (json)->u.object.length; i++) { \ 28 | name = (json)->u.object.values[i].name; \ 29 | val = (json)->u.object.values[i].value; \ 30 | if (1 == 2) {} 31 | 32 | #define ON_CONF_OPTION(expected_name, expected_type) \ 33 | else if (strcmp(name, (expected_name)) == 0 && val->type == (expected_type)) 34 | 35 | #define ON_STRING_CONF(expected_name, var) \ 36 | ON_CONF_OPTION(expected_name, json_string) { var = val->u.string.ptr; } 37 | 38 | #define ON_INTEGER_CONF(expected_name, var) \ 39 | ON_CONF_OPTION(expected_name, json_integer) { var = val->u.integer; } 40 | 41 | #define ON_BOOLEAN_CONF(expected_name, var) \ 42 | ON_CONF_OPTION(expected_name, json_boolean) { var = val->u.boolean; } 43 | 44 | #define END_CONF_HANDLE() \ 45 | else {warn("Unknown config option %s with type %d", name, val->type); } } 46 | 47 | inline void strlowercase(const char *src, char *dst, size_t n); 48 | 49 | void format_http_date(const time_t *time, char *dst, size_t len); 50 | 51 | int parse_http_date(const char* str, time_t *time); 52 | 53 | int current_http_date(char *dst, size_t len); 54 | 55 | int path_starts_with(const char *prefix, const char *path); 56 | 57 | char* url_decode(char *dst, const char *src); 58 | 59 | #endif /* end of include guard: __COMMON_H */ 60 | -------------------------------------------------------------------------------- /src/tests/test_http_server.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "log.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | char* msg = "hello"; 10 | 11 | void dump_request(request_t *req) { 12 | int i; 13 | 14 | info("--------- Parser State -----------------------"); 15 | info("Method: %s", req->method); 16 | info("Path: %s", req->path); 17 | info("Query String: %s", req->query_str); 18 | info("HTTP Version: %d", req->version); 19 | info("Header count: %zu", req->header_count); 20 | info("Connection: %d", req->connection); 21 | info("Headers: "); 22 | info("------------"); 23 | 24 | for (i = 0; i < req->header_count; i++) { 25 | info("\r%s: %s", req->headers[i].name, req->headers[i].value); 26 | } 27 | 28 | info("----------------------------------------------"); 29 | } 30 | 31 | int foobar_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 32 | char *response = 33 | "" 34 | "Hello world" 35 | "" 36 | "

Welcome to Breeze

" 37 | "

Hello world!

" 38 | "" 39 | ""; 40 | size_t len = strlen(response); 41 | 42 | dump_request(req); 43 | 44 | // Make sure the conf pointer points to the string we specified. 45 | assert(ctx->conf == (void*)msg); 46 | 47 | resp->status = STATUS_OK; 48 | resp->content_length = len; 49 | resp->connection = req->connection; 50 | response_set_header(resp, "Content-Type", "text/html"); 51 | response_send_headers(resp, NULL); 52 | response_write(resp, response, len, NULL); 53 | return HANDLER_DONE; 54 | } 55 | 56 | int main(int argc, char** args) { 57 | server_t *server; 58 | server = server_create(); 59 | 60 | if (server == NULL) { 61 | error("Error creating server"); 62 | return -1; 63 | } 64 | 65 | server->handler = foobar_handler; 66 | server->handler_conf = msg; 67 | server_start(server); 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /src/site.h: -------------------------------------------------------------------------------- 1 | #ifndef __LOCATION_H 2 | #define __LOCATION_H 3 | 4 | #include "http.h" 5 | #include 6 | #include 7 | 8 | #define MAX_HOST_LENGTH 64 9 | #define MAX_SITES 100 10 | 11 | typedef struct _location location_t; 12 | typedef struct _site site_t; 13 | 14 | enum _location_match_type { 15 | URI_PREFIX, 16 | URI_REGEX 17 | }; 18 | 19 | struct _location { 20 | union { 21 | char *prefix; 22 | regex_t *regex; 23 | } uri; 24 | int match_type; 25 | handler_func handler; 26 | void *handler_conf; 27 | struct _location *next; 28 | }; 29 | 30 | struct _site { 31 | char host[MAX_HOST_LENGTH]; 32 | 33 | /* 34 | * Use a single linked list to hold all the locations. The regex 35 | * locations have higher priority, so they are always at the front 36 | * of the list. There order is the samed as the order in the 37 | * config file. 38 | * 39 | * After regex locations goes the normal URI prefix 40 | * locations. These locations, however, are sorted in a 41 | * longest-to-shortest order. 42 | * 43 | * Using this structure, we can stop at 44 | * the first match and it is guaranteed to be the best match. 45 | */ 46 | location_t *location_head; 47 | }; 48 | 49 | typedef struct _site_conf { 50 | site_t *sites[MAX_SITES]; 51 | int site_size; 52 | struct hsearch_data site_hash; 53 | } site_conf_t; 54 | 55 | int site_handler(request_t *req, response_t *resp, handler_ctx_t *ctx); 56 | 57 | site_conf_t *site_conf_create(); 58 | site_conf_t *site_conf_parse(json_value *sites_obj); 59 | int site_conf_destroy(site_conf_t *conf); 60 | int site_conf_add_site(site_conf_t *conf, site_t *site); 61 | 62 | site_t *site_create(const char* host); 63 | site_t *site_parse(json_value *site_obj); 64 | int site_destroy(site_t *site); 65 | 66 | int site_add_location(site_t *site, int type, 67 | char *prefix_or_regex, 68 | handler_func handler, 69 | void *handler_conf); 70 | 71 | #endif /* end of include guard: __LOCATION_H */ 72 | -------------------------------------------------------------------------------- /src/iostream.h: -------------------------------------------------------------------------------- 1 | #ifndef __IOSTREAM_H 2 | 3 | #define __IOSTREAM_H 4 | 5 | #include "ioloop.h" 6 | #include "buffer.h" 7 | #include 8 | #include 9 | 10 | struct _iostream; 11 | typedef struct _iostream iostream_t; 12 | 13 | typedef void (*read_handler)(iostream_t *stream, void *data, size_t len); 14 | typedef void (*write_handler)(iostream_t *stream); 15 | typedef void (*error_handler)(iostream_t *stream, unsigned int events); 16 | typedef void (*close_handler)(iostream_t *stream); 17 | 18 | struct _iostream { 19 | int fd; 20 | int state; 21 | ioloop_t *ioloop; 22 | 23 | read_handler read_callback; 24 | read_handler stream_callback; 25 | write_handler write_callback; 26 | error_handler error_callback; 27 | close_handler close_callback; 28 | 29 | int read_type; 30 | size_t read_bytes; 31 | char *read_delimiter; 32 | 33 | unsigned int events; 34 | 35 | buffer_t *read_buf; 36 | size_t read_buf_size; 37 | size_t read_buf_cap; 38 | buffer_t *write_buf; 39 | size_t write_buf_size; 40 | size_t write_buf_cap; 41 | 42 | int write_state; 43 | int sendfile_fd; 44 | off_t sendfile_offset; 45 | size_t sendfile_len; 46 | 47 | void *user_data; 48 | }; 49 | 50 | iostream_t *iostream_create(ioloop_t *loop, int sockfd, 51 | size_t read_buf_size, size_t write_buf_size, 52 | void *user_data); 53 | 54 | int iostream_close(iostream_t *stream); 55 | int iostream_destroy(iostream_t *stream); 56 | int iostream_read_bytes(iostream_t *stream, size_t sz, read_handler callback, read_handler stream_callback); 57 | int iostream_read_until(iostream_t *stream, char *delimiter, read_handler callback); 58 | int iostream_write(iostream_t *stream, void *data, size_t len, write_handler callback); 59 | int iostream_sendfile(iostream_t *stream, int in_fd, 60 | size_t offset, size_t len, 61 | write_handler callback); 62 | int iostream_set_error_handler(iostream_t *stream, error_handler callback); 63 | int iostream_set_close_handler(iostream_t *stream, close_handler callback); 64 | 65 | #endif /* end of include guard: __IOSTREAM_H */ 66 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | inline void strlowercase(const char *src, char *dst, size_t n) { 8 | int i; 9 | const char *p; 10 | for (i = 0, p = src; 11 | i < n && *p != '\0'; 12 | i++, p++) { 13 | dst[i] = tolower(*p); 14 | } 15 | dst[i] = '\0'; 16 | } 17 | 18 | const char* HTTP_DATE_FMT = "%a, %d %b %Y %H:%M:%S %Z"; 19 | 20 | void format_http_date(const time_t* time, char *dst, size_t len) { 21 | strftime(dst, len, HTTP_DATE_FMT, gmtime(time)); 22 | } 23 | 24 | int parse_http_date(const char* str, time_t *time) { 25 | struct tm tm; 26 | if (strptime(str, HTTP_DATE_FMT, &tm) == NULL) { 27 | return -1; 28 | } 29 | // Assume it's GMT time since strptime does not really support %Z 30 | *time = timegm(&tm); 31 | return 0; 32 | } 33 | 34 | int current_http_date(char *dst, size_t len) { 35 | struct timeval tv; 36 | int res; 37 | res = gettimeofday(&tv, NULL); 38 | if (res < 0) { 39 | error("Error getting time of day"); 40 | return -1; 41 | } 42 | format_http_date(&tv.tv_sec, dst, len); 43 | return 0; 44 | } 45 | 46 | int path_starts_with(const char* prefix, const char* path) { 47 | int match_count; 48 | 49 | if (path == NULL || prefix == NULL) 50 | return 0; 51 | 52 | match_count = 0; 53 | 54 | while (*path != '\0' && *prefix != '\0' && *path == *prefix) { 55 | match_count++; 56 | path++; 57 | prefix++; 58 | } 59 | 60 | if (*prefix == '\0' 61 | && (*path == '\0' || *path == '/' || *(prefix-1) == '/')) 62 | return match_count; 63 | else 64 | return 0; 65 | } 66 | 67 | 68 | char* url_decode(char *dst, const char *src) { 69 | char a, b; 70 | while (*src) { 71 | if ((*src == '%') && 72 | ((a = src[1]) && (b = src[2])) && 73 | (isxdigit(a) && isxdigit(b))) { 74 | if (a >= 'a') 75 | a -= 'A'-'a'; 76 | if (a >= 'A') 77 | a -= ('A' - 10); 78 | else 79 | a -= '0'; 80 | if (b >= 'a') 81 | b -= 'A'-'a'; 82 | if (b >= 'A') 83 | b -= ('A' - 10); 84 | else 85 | b -= '0'; 86 | *dst++ = 16*a+b; 87 | src+=3; 88 | } else { 89 | *dst++ = *src++; 90 | } 91 | } 92 | *dst++ = '\0'; 93 | return dst; 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breeze 2 | 3 | This is a simple HTTP server inspired by 4 | [Tornado Web Server](https://github.com/facebook/tornado), for 5 | practising my Linux programming skills. No third-party libraries are 6 | used, only glibc. 7 | 8 | This project is still a work-on-progress. But several features are 9 | already implemented: 10 | 11 | - ioloop/iostream: Async IO module inspired by Tornado 12 | - buffer: Simple ring-buffer module 13 | - http: Core HTTP related features like protocol parser, 14 | handler architecture, etc. 15 | - virtual host: Simple virtual host support. 16 | - location config: Simple location configuration mechanism based on 17 | URI prefix and regex. 18 | - module architecture: An simple module architecture to easily add new 19 | modules 20 | - mod_static: A module that could serve static files and support basic 21 | MIME mapping, cache-control, welcome files, directory listing, 22 | ranged request (partially). 23 | 24 | A lot work needs to be done, but it won't stop you from trying the 25 | features already implemented. Please refer to the following sections 26 | for instructions on how to run it. 27 | 28 | ## Build 29 | 30 | Run make in the `src` dir: 31 | 32 | ```bash 33 | cd src 34 | make 35 | ``` 36 | 37 | The target executable files will appear in the same directory. There 38 | are several unit test cases and a main program `breeze`. 39 | 40 | ## Usage 41 | 42 | ### Run breeze based on config file 43 | 44 | Run the following command in `src` directory: 45 | 46 | ```bash 47 | ./breeze [-c configfile] [-t] 48 | ``` 49 | 50 | If `-c` is not specified, then `/etc/breeze.conf` is used. If `-t` is 51 | specified, the program will print the details of the config and exits. 52 | 53 | Please refer to the following sections for details about the config 54 | file. 55 | 56 | ### Run breeze on specified root directory 57 | 58 | Run the following command in `src` directory: 59 | 60 | ```bash 61 | ./breeze [-r root_dir] [-p port] 62 | ``` 63 | 64 | This mode is activated with the `-r` option. When activated, `breeze` 65 | will not read config file. Instead, breeze will setup a simple server 66 | serving content in the `root_dir`. In this mode, the directory listing 67 | feature is enabled. Therefore this is a perfect replacement of Python 68 | SimpleHTTPServer. Use `-p` to specify the port, by default `8000` is 69 | used. 70 | 71 | ### Hello world HTTP server 72 | 73 | Run the following command in `src` directory: 74 | 75 | ```bash 76 | ./test_http_server 77 | ``` 78 | 79 | A server on port 8000 will be started. It will return a short HTML for 80 | any request on port 8000. 81 | 82 | ## Config File 83 | 84 | Please refer to [the sample config file](src/sample-conf.json) for an 85 | example. Please note that some config options are not implemented yet. 86 | 87 | ## TODO 88 | 89 | - Automatically close keep-alive connection on server side 90 | - HTTP chunked encoding, gzip compression 91 | - FastCGI support 92 | - File upload module 93 | - Master-worker multi-process architecture 94 | 95 | ## Known Bugs 96 | 97 | - The server stops responding to any request on high load (>2000 98 | concurrent connections) 99 | 100 | ## Lisence 101 | 102 | GPLv3 103 | -------------------------------------------------------------------------------- /src/tests/test_ioloop.c: -------------------------------------------------------------------------------- 1 | #include "ioloop.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static void connection_handler(ioloop_t *loop, int fd, unsigned int events, void *args); 15 | static void echo_handler(ioloop_t *loop, int fd, unsigned int events, void *args); 16 | static void send_welcome_message(ioloop_t *loop, void* args); 17 | 18 | static void connection_handler(ioloop_t *loop, int listen_fd, unsigned int events, void *args) { 19 | socklen_t addr_len; 20 | int conn_fd; 21 | struct sockaddr_in remo_addr; 22 | 23 | // -------- Accepting connection ---------------------------- 24 | addr_len = sizeof(struct sockaddr_in); 25 | conn_fd = accept(listen_fd, (struct sockaddr*) &remo_addr, &addr_len); 26 | if (conn_fd == -1) { 27 | error("Error accepting new connection"); 28 | return; 29 | } 30 | 31 | ioloop_add_handler(loop, conn_fd, EPOLLIN, echo_handler, NULL); 32 | #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" 33 | ioloop_add_callback(loop, send_welcome_message, (void*)conn_fd); 34 | } 35 | 36 | static void send_welcome_message(ioloop_t *loop, void* args) { 37 | #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" 38 | int fd = (int)args; 39 | char msg[] = "Welcome to echo server powered by libioloop!\n"; 40 | assert(write(fd, msg, sizeof(msg) > 0)); 41 | } 42 | 43 | 44 | static void echo_handler(ioloop_t *loop, int fd, unsigned int events, void *args) { 45 | char buffer[1024]; 46 | int nread; 47 | if (events & EPOLLIN) { 48 | nread = read(fd, buffer, 1024); 49 | if (nread == 0) { 50 | info("Connection closed or error condition occurs: %d", fd); 51 | ioloop_remove_handler(loop, fd); 52 | close(fd); 53 | return; 54 | } else if (nread < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { 55 | error("Error reading"); 56 | return; 57 | } 58 | buffer[nread] = '\0'; 59 | debug("Read from client: %s", buffer); 60 | assert(write(fd, buffer, nread) > 0); 61 | } 62 | } 63 | 64 | int main(int argc, char *argv[]) { 65 | ioloop_t *loop; 66 | int listen_fd; 67 | struct sockaddr_in addr; 68 | 69 | loop = ioloop_create(100); 70 | if (loop == NULL) { 71 | error("Error initializing ioloop"); 72 | return -1; 73 | } 74 | 75 | // ---------- Create and bind listen socket fd -------------- 76 | listen_fd = socket(AF_INET, SOCK_STREAM, 0); 77 | if (listen_fd == -1) { 78 | error("Error creating socket"); 79 | return -1; 80 | } 81 | 82 | bzero(&addr, sizeof(struct sockaddr_in)); 83 | addr.sin_addr.s_addr = INADDR_ANY; 84 | addr.sin_port = htons(9999); 85 | 86 | if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1) { 87 | error("Error binding address"); 88 | return -1; 89 | } 90 | 91 | // ------------ Start listening ------------------------------ 92 | if (listen(listen_fd, 10) == -1) { 93 | error("Error listening"); 94 | return -1; 95 | } 96 | 97 | ioloop_add_handler(loop, listen_fd, EPOLLIN, connection_handler, NULL); 98 | ioloop_start(loop); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /src/breeze.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "mod.h" 3 | #include "site.h" 4 | #include "log.h" 5 | #include "mod_static.h" 6 | #include "stacktrace.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DEFAULT_CONF "/etc/breeze.conf" 13 | 14 | typedef struct _opts { 15 | char *conf_file; 16 | int is_simple_mode; 17 | char *root_dir; 18 | unsigned short port; 19 | int is_conf_test; 20 | } opt_t; 21 | 22 | static opt_t *parse_opts(int argc, char *argv[]); 23 | static server_t *create_simple_server(opt_t *opt); 24 | static void print_conf_details(server_t *server); 25 | 26 | int main(int argc, char *argv[]) { 27 | server_t *server; 28 | opt_t *opt; 29 | 30 | opt = parse_opts(argc, argv); 31 | if (opt == NULL) { 32 | return -1; 33 | } 34 | print_stacktrace_on_error(); 35 | // Simple Mode: do not need config file 36 | if (opt->root_dir != NULL) { 37 | server = create_simple_server(opt); 38 | } else { 39 | server = server_parse_conf(opt->conf_file); 40 | } 41 | 42 | if (server == NULL) { 43 | error("Error creating server\n"); 44 | return 1; 45 | } 46 | 47 | if (opt->is_conf_test) { 48 | print_conf_details(server); 49 | return 0; 50 | } 51 | server_start(server); 52 | server_destroy(server); 53 | free(opt); 54 | return 0; 55 | } 56 | 57 | static opt_t *parse_opts(int argc, char *argv[]) { 58 | opt_t *opt = NULL; 59 | int opt_id; 60 | 61 | opt = (opt_t*) calloc(1, sizeof(opt_t)); 62 | if (opt == NULL) { 63 | error("Error allocating memory\n"); 64 | return NULL; 65 | } 66 | opt->conf_file = DEFAULT_CONF; 67 | 68 | while ((opt_id = getopt(argc, argv, "c:p:r:t")) != -1) { 69 | switch(opt_id) { 70 | case 'c': 71 | opt->conf_file = optarg; 72 | break; 73 | 74 | case 'p': 75 | opt->port = atoi(optarg); 76 | break; 77 | 78 | case 't': 79 | opt->is_conf_test = 1; 80 | break; 81 | 82 | case 'r': 83 | opt->root_dir = optarg; 84 | break; 85 | 86 | default: 87 | fprintf(stderr, "Usage: %s [-c configfile] [-t]\n", argv[0]); 88 | fprintf(stderr, " %s [-r rootdir] [-p port]\n", argv[0]); 89 | free(opt); 90 | return NULL; 91 | } 92 | } 93 | 94 | return opt; 95 | } 96 | 97 | static void print_conf_details(server_t *server) { 98 | int i; 99 | site_t *site; 100 | location_t *loc; 101 | site_conf_t *site_conf = (site_conf_t*) server->handler_conf; 102 | 103 | for (i = 0; i < site_conf->site_size; i++) { 104 | site = site_conf->sites[i]; 105 | info("Found site: %s", site->host); 106 | for (loc = site->location_head->next; loc != NULL; loc = loc->next) { 107 | info("\t Location: "); 108 | if (loc->match_type == URI_REGEX) { 109 | info("[regex]"); 110 | } else if (loc->match_type == URI_PREFIX) { 111 | info("%s", loc->uri.prefix); 112 | } 113 | } 114 | } 115 | } 116 | 117 | mod_static_conf_t conf; 118 | 119 | static server_t *create_simple_server(opt_t *opt) { 120 | server_t *server; 121 | bzero(&conf, sizeof(mod_static_conf_t)); 122 | 123 | if (mod_static_init() < 0) { 124 | error("Error initializing mod_static\n"); 125 | return NULL; 126 | } 127 | 128 | server = server_create(); 129 | if (server == NULL) { 130 | return NULL; 131 | } 132 | 133 | conf.root = opt->root_dir; 134 | conf.expire_hours = 24; 135 | conf.enable_list_dir = 1; 136 | conf.index = "index.html"; 137 | if (opt->port > 0) { 138 | server->port = opt->port; 139 | } 140 | server->handler = static_file_handle; 141 | server->handler_conf = &conf; 142 | return server; 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/http_connection.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "common.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static void _connection_close_handler(iostream_t *stream); 18 | static void _on_http_header_data(iostream_t *stream, void *data, size_t len); 19 | static void _set_tcp_nodelay(int fd); 20 | 21 | connection_t* connection_accept(server_t *server, int listen_fd) { 22 | connection_t *conn; 23 | iostream_t *stream; 24 | socklen_t addr_len; 25 | int conn_fd; 26 | struct sockaddr_in remote_addr; 27 | 28 | // -------- Accepting connection ---------------------------- 29 | addr_len = sizeof(struct sockaddr_in); 30 | conn_fd = accept(listen_fd, (struct sockaddr*) &remote_addr, &addr_len); 31 | if (conn_fd == -1) { 32 | if (errno != EAGAIN && errno != EWOULDBLOCK) 33 | error("Error accepting new connection"); 34 | return NULL; 35 | } 36 | 37 | conn = (connection_t*) calloc(1, sizeof(connection_t)); 38 | if (conn == NULL) { 39 | goto error; 40 | } 41 | bzero(conn, sizeof(connection_t)); 42 | 43 | if (set_nonblocking(conn_fd) < 0) { 44 | error("Error configuring Non-blocking"); 45 | goto error; 46 | } 47 | 48 | _set_tcp_nodelay(conn_fd); 49 | stream = iostream_create(server->ioloop, conn_fd, 10240, 40960, conn); 50 | if (stream == NULL) { 51 | goto error; 52 | } 53 | 54 | iostream_set_close_handler(stream, _connection_close_handler); 55 | 56 | conn->server = server; 57 | conn->stream = stream; 58 | inet_ntop(AF_INET, &remote_addr.sin_addr, conn->remote_ip, 20); 59 | conn->remote_port = remote_addr.sin_port; 60 | conn->state = CONN_ACTIVE; 61 | conn->request = request_create(conn); 62 | conn->response = response_create(conn); 63 | conn->context = context_create(); 64 | 65 | return conn; 66 | 67 | error: 68 | close(conn_fd); 69 | if (conn != NULL) 70 | free(conn); 71 | return NULL; 72 | } 73 | 74 | int connection_close(connection_t *conn) { 75 | return iostream_close(conn->stream); 76 | } 77 | 78 | int connection_destroy(connection_t *conn) { 79 | request_destroy(conn->request); 80 | response_destroy(conn->response); 81 | context_destroy(conn->context); 82 | free(conn); 83 | return 0; 84 | } 85 | 86 | int connection_run(connection_t *conn) { 87 | iostream_read_until(conn->stream, "\r\n\r\n", _on_http_header_data); 88 | return 0; 89 | } 90 | 91 | static void _on_http_header_data(iostream_t *stream, void *data, size_t len) { 92 | connection_t *conn; 93 | request_t *req; 94 | response_t *resp; 95 | size_t consumed; 96 | 97 | conn = (connection_t*) stream->user_data; 98 | req = conn->request; 99 | resp = conn->response; 100 | 101 | if (req == NULL || resp == NULL) { 102 | connection_close(conn); 103 | return; 104 | } 105 | 106 | if (request_parse_headers(req, (char*)data, len, &consumed) 107 | != STATUS_COMPLETE) { 108 | connection_close(conn); 109 | return; 110 | } 111 | 112 | // URL Decode 113 | url_decode(req->path, req->path); 114 | if (req->query_str != NULL) { 115 | url_decode(req->query_str, req->query_str); 116 | } 117 | 118 | // Handle HTTP keep-alive 119 | if (req->version < HTTP_VERSION_1_1 && req->connection != CONN_KEEP_ALIVE) { 120 | // For HTTP/1.0, default behaviour is not keep-alive unless 121 | // otherwise specified in request's Connection header. 122 | resp->connection = CONN_CLOSE; 123 | } else if (req->version == HTTP_VERSION_1_1 && req->connection != CONN_CLOSE) { 124 | // For HTTP/1.1, default behaviour is enabling keep-alive 125 | // unless specified explicitly. 126 | resp->connection = CONN_KEEP_ALIVE; 127 | } 128 | 129 | // TODO Handle Unknown HTTP version 130 | resp->version = req->version; 131 | // Reset handler configuration 132 | conn->context->conf = conn->server->handler_conf; 133 | connection_run_handler(conn, conn->server->handler); 134 | } 135 | 136 | void connection_run_handler(connection_t *conn, handler_func handler) { 137 | request_t *req; 138 | response_t *resp; 139 | handler_ctx_t *ctx; 140 | int res; 141 | 142 | req = conn->request; 143 | resp = conn->response; 144 | ctx = conn->context; 145 | 146 | res = handler(req, resp, ctx); 147 | 148 | if (res == HANDLER_DONE) { 149 | resp->_done = 1; 150 | } 151 | } 152 | 153 | static void _connection_close_handler(iostream_t *stream) { 154 | connection_t *conn; 155 | conn = (connection_t*) stream->user_data; 156 | connection_destroy(conn); 157 | } 158 | 159 | static void _set_tcp_nodelay(int fd) { 160 | int enable = 1; 161 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&enable, sizeof(enable)); 162 | } 163 | -------------------------------------------------------------------------------- /src/tests/test_iostream.c: -------------------------------------------------------------------------------- 1 | #include "ioloop.h" 2 | #include "iostream.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static void connection_handler(ioloop_t *loop, int fd, unsigned int events, void *args); 18 | static void connection_close_handler(iostream_t *stream); 19 | static void read_bytes(iostream_t *stream, void* data, size_t len); 20 | static void read_headers(iostream_t *stream, void *data, size_t len); 21 | static void write_texts(iostream_t *stream); 22 | static void send_file(iostream_t *stream); 23 | static void close_stream(iostream_t *stream); 24 | static void dump_data(void *data, size_t len); 25 | 26 | /** 27 | * All Modes: 28 | * 0: Test read bytes 29 | * 1: Test read until 30 | * 2: Test write 31 | * 3: Test send file 32 | * 33 | */ 34 | static int mode = 0; 35 | static char filename[1024]; 36 | 37 | static void connection_handler(ioloop_t *loop, int listen_fd, unsigned int events, void *args) { 38 | socklen_t addr_len; 39 | int conn_fd; 40 | struct sockaddr_in remo_addr; 41 | iostream_t *stream; 42 | 43 | // -------- Accepting connection ---------------------------- 44 | debug("Accepting new connection..."); 45 | addr_len = sizeof(struct sockaddr_in); 46 | conn_fd = accept(listen_fd, (struct sockaddr*) &remo_addr, &addr_len); 47 | debug("Connection fd: %d...", conn_fd); 48 | if (conn_fd == -1) { 49 | error("Error accepting new connection"); 50 | return; 51 | } 52 | 53 | if (set_nonblocking(conn_fd)) { 54 | error("Error configuring Non-blocking"); 55 | return; 56 | } 57 | stream = iostream_create(loop, conn_fd, 1024, 1024, NULL); 58 | iostream_set_close_handler(stream, connection_close_handler); 59 | switch(mode) { 60 | case 0: 61 | error("Testing read 16 bytes"); 62 | iostream_read_bytes(stream, 16, read_bytes, NULL); 63 | break; 64 | 65 | case 1: 66 | error("Testing read_until two blank lines(\\n\\n)"); 67 | iostream_read_until(stream, "\r\n\r\n", read_headers); 68 | break; 69 | 70 | case 2: 71 | error("Testing writing dummy data"); 72 | write_texts(stream); 73 | break; 74 | 75 | case 3: 76 | error("Testing sending file"); 77 | send_file(stream); 78 | break; 79 | 80 | default: 81 | error("Unknown mode: read_until two blank lines(\\n)"); 82 | iostream_read_until(stream, "\r\n\r\n", read_headers); 83 | break; 84 | } 85 | } 86 | 87 | static void read_bytes(iostream_t *stream, void* data, size_t len) { 88 | dump_data(data, len); 89 | iostream_read_bytes(stream, 16, read_bytes, NULL); 90 | } 91 | 92 | static void read_headers(iostream_t *stream, void *data, size_t len) { 93 | dump_data(data, len); 94 | iostream_read_until(stream, "\r\n\r\n", read_headers); 95 | } 96 | 97 | static void close_stream(iostream_t *stream) { 98 | debug("closing stream: %d", stream->fd); 99 | iostream_close(stream); 100 | } 101 | 102 | static void write_texts(iostream_t *stream) { 103 | static int counter = 0; 104 | char buf[200]; 105 | snprintf(buf, 200, "%d: 1234567890abcdefghijklmnopqrstuvwxyz-=_+{}()\r\n", counter++); 106 | if (counter == 1000) { 107 | counter = 0; 108 | } 109 | iostream_write(stream, buf, strlen(buf), close_stream); 110 | } 111 | 112 | static void send_file(iostream_t *stream) { 113 | int fd; 114 | struct stat st; 115 | size_t len; 116 | 117 | fd = open(filename, O_RDONLY); 118 | if (fd < 0) { 119 | error("Error opening file"); 120 | return; 121 | } 122 | 123 | if (fstat(fd, &st) < 0) { 124 | error("Error get the st of the file"); 125 | return; 126 | } 127 | 128 | len = st.st_size; 129 | 130 | if (len == 0) { 131 | error("File is empty"); 132 | return; 133 | } 134 | 135 | iostream_sendfile(stream, fd, 0, len, close_stream); 136 | } 137 | 138 | static void dump_data(void *data, size_t len) { 139 | char *str = (char*) data; 140 | int i; 141 | 142 | info("Data read:\n------------------"); 143 | for (i = 0; i < len; i++) { 144 | putchar(str[i]); 145 | } 146 | info("--------------------"); 147 | } 148 | 149 | 150 | static void connection_close_handler(iostream_t *stream) { 151 | info("Closing connection."); 152 | } 153 | 154 | int main(int argc, char *argv[]) { 155 | ioloop_t *loop; 156 | int listen_fd; 157 | struct sockaddr_in addr; 158 | 159 | loop = ioloop_create(100); 160 | if (loop == NULL) { 161 | error("Error initializing ioloop"); 162 | return -1; 163 | } 164 | 165 | // ---------- Create and bind listen socket fd -------------- 166 | listen_fd = socket(AF_INET, SOCK_STREAM, 0); 167 | if (listen_fd == -1) { 168 | error("Error creating socket"); 169 | return -1; 170 | } 171 | 172 | bzero(&addr, sizeof(struct sockaddr_in)); 173 | addr.sin_addr.s_addr = INADDR_ANY; 174 | addr.sin_port = htons(9999); 175 | 176 | if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1) { 177 | error("Error binding address"); 178 | return -1; 179 | } 180 | 181 | // ------------ Start listening ------------------------------ 182 | if (listen(listen_fd, 10) == -1) { 183 | error("Error listening"); 184 | return -1; 185 | } 186 | 187 | if (argc > 1) { 188 | mode = atoi(argv[1]); 189 | if (mode == 3) { 190 | if (argc < 3) 191 | error("Please specify a file name"); 192 | else 193 | strcpy(filename, argv[2]); 194 | } 195 | } 196 | 197 | ioloop_add_handler(loop, listen_fd, EPOLLIN, connection_handler, NULL); 198 | ioloop_start(loop); 199 | return 0; 200 | } 201 | 202 | -------------------------------------------------------------------------------- /src/ioloop.c: -------------------------------------------------------------------------------- 1 | #include "ioloop.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define MAX_CALLBACKS 10000 18 | 19 | struct _callback { 20 | callback_handler callback; 21 | void *args; 22 | }; 23 | 24 | struct _io_callback { 25 | io_handler callback; 26 | void *args; 27 | }; 28 | 29 | struct _callback_chain { 30 | struct _callback callbacks[MAX_CALLBACKS]; 31 | int callback_num; 32 | }; 33 | 34 | struct _ioloop { 35 | int epoll_fd; 36 | int state; 37 | struct _io_callback *handlers; 38 | struct _callback_chain callback_chains[2]; 39 | int callback_chain_idx; 40 | }; 41 | 42 | enum IOLOOP_STATES { 43 | INITIALIZED = 0, 44 | RUNNING = 1, 45 | STOPPED = 2 46 | }; 47 | 48 | 49 | ioloop_t *ioloop_create(unsigned int maxfds) { 50 | ioloop_t *loop = NULL; 51 | int epoll_fd; 52 | struct _io_callback *handlers = NULL; 53 | 54 | loop = (ioloop_t*) calloc (1, sizeof(ioloop_t)); 55 | if (loop == NULL) { 56 | error("Could not allocate memory for IO loop"); 57 | return NULL; 58 | } 59 | bzero(loop, sizeof(ioloop_t)); 60 | 61 | 62 | handlers = (struct _io_callback*) calloc(maxfds, sizeof(struct _io_callback)); 63 | if (handlers == NULL) { 64 | error("Could not allocate memory for IO handlers"); 65 | return NULL; 66 | } 67 | 68 | bzero(handlers, maxfds * sizeof(struct _io_callback)); 69 | 70 | 71 | epoll_fd = epoll_create(maxfds); 72 | if (epoll_fd == -1) { 73 | error("Error initializing epoll"); 74 | return NULL; 75 | } 76 | 77 | loop->handlers = handlers; 78 | loop->epoll_fd = epoll_fd; 79 | loop->state = INITIALIZED; 80 | loop->callback_chains[0].callback_num = 0; 81 | loop->callback_chains[1].callback_num = 0; 82 | loop->callback_chain_idx = 0; 83 | return loop; 84 | } 85 | 86 | 87 | int ioloop_destroy(ioloop_t *loop) { 88 | free(loop->handlers); 89 | free(loop); 90 | return 0; 91 | } 92 | 93 | int ioloop_add_handler(ioloop_t *loop, 94 | int fd, 95 | unsigned int events, 96 | io_handler handler, 97 | void *args) { 98 | struct epoll_event ev; 99 | 100 | if (handler == NULL) { 101 | error("Handler should not be NULL!"); 102 | return -1; 103 | } 104 | 105 | ev.data.fd = fd; 106 | ev.events = events | EPOLLET; 107 | if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { 108 | error("Error adding fd to epoll"); 109 | return -1; 110 | } 111 | 112 | loop->handlers[fd].callback = handler; 113 | loop->handlers[fd].args = args; 114 | return 0; 115 | } 116 | 117 | int ioloop_update_handler(ioloop_t *loop, int fd, unsigned int events) { 118 | struct epoll_event ev; 119 | 120 | ev.data.fd = fd; 121 | ev.events = events; 122 | if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == -1) { 123 | error("Error modifying epoll events"); 124 | return -1; 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | 131 | io_handler ioloop_remove_handler(ioloop_t *loop, int fd) { 132 | int res; 133 | io_handler handler; 134 | debug("Removing handler for fd %d", fd); 135 | handler = loop->handlers[fd].callback; 136 | loop->handlers[fd].callback = NULL; 137 | loop->handlers[fd].args = NULL; 138 | res = epoll_ctl(loop->epoll_fd, EPOLL_CTL_DEL, fd, NULL); 139 | if (res < 0) { 140 | error("Error removing fd from epoll"); 141 | } 142 | return handler; 143 | } 144 | 145 | 146 | #define MAX_EVENTS 1024 147 | #define EPOLL_DEFAULT_TIMEOUT 100 148 | 149 | int ioloop_start(ioloop_t *loop) { 150 | struct epoll_event events[MAX_EVENTS]; 151 | struct _callback_chain *current_chain; 152 | int epoll_fd, nfds, i, fd, epoll_timeout; 153 | io_handler handler; 154 | void *args; 155 | 156 | if (loop->state != INITIALIZED) { 157 | error("Could not restart an IO loop"); 158 | return -1; 159 | } 160 | epoll_fd = loop->epoll_fd; 161 | loop->state = RUNNING; 162 | while (loop->state == RUNNING) { 163 | // Handle callbacks 164 | current_chain = loop->callback_chains + loop->callback_chain_idx; 165 | if (current_chain->callback_num > 0) { 166 | loop->callback_chain_idx = (loop->callback_chain_idx + 1) % 2; 167 | for (i = 0; i < current_chain->callback_num; i++) { 168 | current_chain->callbacks[i].callback(loop, current_chain->callbacks[i].args); 169 | } 170 | current_chain->callback_num = 0; 171 | } 172 | 173 | // Wait for events 174 | epoll_timeout = EPOLL_DEFAULT_TIMEOUT; 175 | if (loop->callback_chains[loop->callback_chain_idx].callback_num > 0) { 176 | // There are callbacks that needs running, so we do not wait in epoll_wait 177 | epoll_timeout = 0; 178 | } 179 | nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, epoll_timeout); 180 | if (nfds == -1) { 181 | error("epoll_wait"); 182 | continue; 183 | } 184 | 185 | // Handle events 186 | for (i = 0; i < nfds; i++) { 187 | fd = events[i].data.fd; 188 | handler = loop->handlers[fd].callback; 189 | args = loop->handlers[fd].args; 190 | if (handler == NULL) { 191 | continue; 192 | } 193 | handler(loop, fd, events[i].events, args); 194 | } 195 | } 196 | 197 | close(epoll_fd); 198 | //TODO Other cleanup work here. 199 | return 0; 200 | } 201 | 202 | 203 | int ioloop_stop(ioloop_t *loop) { 204 | loop->state = STOPPED; 205 | return 0; 206 | } 207 | 208 | 209 | int ioloop_add_callback(ioloop_t *loop, callback_handler handler, void *args) { 210 | struct _callback_chain *current_chain = loop->callback_chains + loop->callback_chain_idx; 211 | int n = current_chain->callback_num; 212 | if (n >= MAX_CALLBACKS) { 213 | return -1; 214 | } 215 | current_chain->callbacks[n].callback = handler; 216 | current_chain->callbacks[n].args = args; 217 | current_chain->callback_num ++; 218 | return 0; 219 | } 220 | 221 | int set_nonblocking(int sockfd) { 222 | int opts; 223 | opts = fcntl(sockfd, F_GETFL); 224 | if (opts < 0) 225 | return -1; 226 | opts |= O_NONBLOCK; 227 | if (fcntl(sockfd, F_SETFL, opts) < 0) 228 | return -1; 229 | return 0; 230 | } 231 | -------------------------------------------------------------------------------- /src/tests/test_buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void assert_equals(char *expected, char *actual, size_t sz) { 11 | int i; 12 | for (i = 0; i < sz; i++) { 13 | assert(expected[i] == actual[i]); 14 | } 15 | } 16 | 17 | buffer_t *create_buffer(size_t sz) { 18 | buffer_t *buf = NULL; 19 | 20 | buf = buffer_create(sz); 21 | assert(buf != NULL); 22 | return buf; 23 | } 24 | 25 | void test_basic_case() { 26 | char data[] = "1234567890"; 27 | char result[12]; 28 | buffer_t *buf = create_buffer(10); 29 | 30 | assert(buffer_put(buf, data, strlen(data)) == 0); 31 | assert(buffer_get(buf, 10, result, 12) == 10); 32 | assert_equals(data, result, 10); 33 | assert(buffer_destroy(buf) == 0); 34 | } 35 | 36 | void test_overflow() { 37 | char data[] = "1234567890"; 38 | buffer_t *buf = create_buffer(5); 39 | 40 | assert(buffer_put(buf, data, strlen(data)) < 0); 41 | assert(buffer_destroy(buf) == 0); 42 | } 43 | 44 | void test_multiple_putget() { 45 | char data1[] = "123456"; 46 | char data2[] = "abcd"; 47 | char data3[] = "mnpqrs"; 48 | char data4[] = "!@#$%^&*="; 49 | char result[12]; 50 | buffer_t *buf = create_buffer(10); 51 | 52 | assert(buffer_put(buf, data1, strlen(data1)) == 0); 53 | assert(buffer_put(buf, data2, strlen(data2)) == 0); 54 | assert(buffer_get(buf, 8, result, 12) == 8); 55 | assert_equals("123456ab", result, 8); 56 | 57 | assert(buffer_put(buf, data3, strlen(data3)) == 0); 58 | assert(buffer_get(buf, 10, result, 12) == 8); 59 | assert_equals("cdmnpqrs", result, 8); 60 | 61 | assert(buffer_put(buf, data4, strlen(data4)) == 0); 62 | assert(buffer_get(buf, 10, result, 12) == 9); 63 | assert_equals(data4, result, 9); 64 | assert(buffer_destroy(buf) == 0); 65 | } 66 | 67 | 68 | void test_flush() { 69 | char data[] = "1234567890abcdefg!@#$%^&*"; 70 | buffer_t *buf = create_buffer(30); 71 | int fd; 72 | 73 | assert(buffer_put(buf, data, strlen(data)) == 0); 74 | fd = open("/tmp/foobar.txt", O_RDWR | O_CREAT, 0755); 75 | assert(buffer_flush(buf, fd) == strlen(data)); 76 | fsync(fd); 77 | close(fd); 78 | assert(buffer_destroy(buf) == 0); 79 | } 80 | 81 | 82 | void test_flush_incontinous() { 83 | char garbage[] = "1234567890abcdef"; 84 | char data[] = "1234567890abcdefg!@#$%^&*"; 85 | buffer_t *buf = create_buffer(30); 86 | int fd; 87 | size_t garbage_len = strlen(garbage); 88 | 89 | // Put some garbage to the buffer and skip them to make the ring 90 | // buffer have incontinous space. So we can test writev with 91 | // multiple iovec. 92 | assert(buffer_put(buf, garbage, garbage_len) == 0); 93 | assert(buffer_skip(buf, garbage_len) == garbage_len); 94 | 95 | assert(buffer_put(buf, data, strlen(data)) == 0); 96 | fd = open("/tmp/foobar2.txt", O_RDWR | O_CREAT, 0755); 97 | assert(buffer_flush(buf, fd) == strlen(data)); 98 | fsync(fd); 99 | close(fd); 100 | assert(buffer_destroy(buf) == 0); 101 | } 102 | 103 | void test_fill() { 104 | char data[] = "1234567890abcdefg!@#$%^&*"; 105 | char result[100]; 106 | buffer_t *buf = create_buffer(512); 107 | int fd; 108 | size_t data_len; 109 | 110 | data_len = strlen(data); 111 | fd = open("/tmp/foobar2.txt", O_RDWR | O_CREAT, 0755); 112 | assert(write(fd, data, data_len) > 0); 113 | lseek(fd, 0, SEEK_SET); 114 | assert(buffer_fill(buf, fd) == data_len); 115 | assert(buffer_get(buf, data_len, result, 100) == data_len); 116 | assert_equals(data, result, data_len); 117 | } 118 | 119 | void test_fill_overflow() { 120 | char data[] = "1234567890abcdefg!@#$%^&*"; 121 | char result[100]; 122 | buffer_t *buf = create_buffer(10); 123 | int fd; 124 | size_t data_len; 125 | 126 | data_len = strlen(data); 127 | fd = open("/tmp/foobar3.txt", O_RDWR | O_CREAT, 0755); 128 | assert(write(fd, data, data_len) > 0); 129 | lseek(fd, 0, SEEK_SET); 130 | assert(buffer_fill(buf, fd) == 10); 131 | assert(buffer_is_full(buf)); 132 | assert(buffer_get(buf, 10, result, 100) == 10); 133 | assert_equals(data, result, 10); 134 | assert(buffer_destroy(buf) == 0); 135 | } 136 | 137 | #define SECRET_ARG "foobar" 138 | 139 | static void _print_consumer(void *data, size_t len, void *args) { 140 | char *str = (char*) data; 141 | int i; 142 | 143 | assert_equals((char*) args, SECRET_ARG, 6); 144 | for (i = 0; i < len; i++) { 145 | putchar(str[i]); 146 | } 147 | putchar('\n'); 148 | } 149 | 150 | 151 | void test_consume() { 152 | char data[] = "12345"; 153 | char data1[] = "abcdefghij"; 154 | buffer_t *buf = create_buffer(10); 155 | 156 | assert(buffer_put(buf, data, strlen(data)) == 0); 157 | assert(buffer_consume(buf, 10, _print_consumer, SECRET_ARG) == strlen(data)); 158 | 159 | assert(buffer_put(buf, data1, strlen(data1)) == 0); 160 | assert(buffer_consume(buf, 10, _print_consumer, SECRET_ARG) == strlen(data1)); 161 | 162 | assert(buffer_put(buf, data, strlen(data)) == 0); 163 | assert(buffer_consume(buf, 2, _print_consumer, SECRET_ARG) == 2); 164 | assert(buffer_consume(buf, 3, _print_consumer, SECRET_ARG) == 3); 165 | assert(buffer_destroy(buf) == 0); 166 | } 167 | 168 | void test_locate() { 169 | char data[] = "abcde"; 170 | char data1[] = "0123456789"; 171 | buffer_t *buf = create_buffer(10); 172 | 173 | assert(buffer_put(buf, data, strlen(data)) == 0); 174 | assert(buffer_locate(buf, "cd") == 2); 175 | assert(buffer_locate(buf, "c") == 2); 176 | assert(buffer_locate(buf, "d") == 3); 177 | assert(buffer_locate(buf, "a") == 0); 178 | assert(buffer_locate(buf, "e") == 4); 179 | assert(buffer_locate(buf, "kkk") < 0); 180 | 181 | assert(buffer_consume(buf, 10, _print_consumer, SECRET_ARG) == strlen(data)); 182 | 183 | assert(buffer_put(buf, data1, strlen(data1)) == 0); 184 | assert(buffer_locate(buf, "012") == 0); 185 | assert(buffer_locate(buf, "0") == 0); 186 | assert(buffer_locate(buf, "9") == 9); 187 | assert(buffer_locate(buf, "6") == 6); 188 | assert(buffer_locate(buf, "3456") == 3); 189 | assert(buffer_locate(buf, "89") == 8); 190 | assert(buffer_locate(buf, "kkk") < 0); 191 | assert(buffer_destroy(buf) == 0); 192 | } 193 | 194 | int main(int argc, char *argv[]) { 195 | test_basic_case(); 196 | test_overflow(); 197 | test_multiple_putget(); 198 | test_flush(); 199 | test_flush_incontinous(); 200 | test_fill(); 201 | test_fill_overflow(); 202 | test_consume(); 203 | test_locate(); 204 | return 0; 205 | } 206 | -------------------------------------------------------------------------------- /src/json/json.h: -------------------------------------------------------------------------------- 1 | 2 | /* vim: set et ts=3 sw=3 ft=c: 3 | * 4 | * Copyright (C) 2012 James McLaughlin et al. All rights reserved. 5 | * https://github.com/udp/json-parser 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef _JSON_H 32 | #define _JSON_H 33 | 34 | #ifndef json_char 35 | #define json_char char 36 | #endif 37 | 38 | #ifndef json_int_t 39 | #ifndef _MSC_VER 40 | #include 41 | #define json_int_t int64_t 42 | #else 43 | #define json_int_t __int64 44 | #endif 45 | #endif 46 | 47 | #include 48 | 49 | #ifdef __cplusplus 50 | 51 | #include 52 | 53 | extern "C" 54 | { 55 | 56 | #endif 57 | 58 | typedef struct 59 | { 60 | unsigned long max_memory; 61 | int settings; 62 | 63 | /* Custom allocator support (leave null to use malloc/free) 64 | */ 65 | 66 | void * (* mem_alloc) (size_t, int zero, void * user_data); 67 | void (* mem_free) (void *, void * user_data); 68 | 69 | void * user_data; /* will be passed to mem_alloc and mem_free */ 70 | 71 | } json_settings; 72 | 73 | #define json_relaxed_commas 1 74 | 75 | typedef enum 76 | { 77 | json_none, 78 | json_object, 79 | json_array, 80 | json_integer, 81 | json_double, 82 | json_string, 83 | json_boolean, 84 | json_null 85 | 86 | } json_type; 87 | 88 | extern const struct _json_value json_value_none; 89 | 90 | typedef struct _json_value 91 | { 92 | struct _json_value * parent; 93 | 94 | json_type type; 95 | 96 | union 97 | { 98 | int boolean; 99 | json_int_t integer; 100 | double dbl; 101 | 102 | struct 103 | { 104 | unsigned int length; 105 | json_char * ptr; /* null terminated */ 106 | 107 | } string; 108 | 109 | struct 110 | { 111 | unsigned int length; 112 | 113 | struct 114 | { 115 | json_char * name; 116 | struct _json_value * value; 117 | 118 | } * values; 119 | 120 | #if defined(__cplusplus) && __cplusplus >= 201103L 121 | decltype(values) begin () const 122 | { return values; 123 | } 124 | decltype(values) end () const 125 | { return values + length; 126 | } 127 | #endif 128 | 129 | } object; 130 | 131 | struct 132 | { 133 | unsigned int length; 134 | struct _json_value ** values; 135 | 136 | #if defined(__cplusplus) && __cplusplus >= 201103L 137 | decltype(values) begin () const 138 | { return values; 139 | } 140 | decltype(values) end () const 141 | { return values + length; 142 | } 143 | #endif 144 | 145 | } array; 146 | 147 | } u; 148 | 149 | union 150 | { 151 | struct _json_value * next_alloc; 152 | void * object_mem; 153 | 154 | } _reserved; 155 | 156 | 157 | /* Some C++ operator sugar */ 158 | 159 | #ifdef __cplusplus 160 | 161 | public: 162 | 163 | inline _json_value () 164 | { memset (this, 0, sizeof (_json_value)); 165 | } 166 | 167 | inline const struct _json_value &operator [] (int index) const 168 | { 169 | if (type != json_array || index < 0 170 | || ((unsigned int) index) >= u.array.length) 171 | { 172 | return json_value_none; 173 | } 174 | 175 | return *u.array.values [index]; 176 | } 177 | 178 | inline const struct _json_value &operator [] (const char * index) const 179 | { 180 | if (type != json_object) 181 | return json_value_none; 182 | 183 | for (unsigned int i = 0; i < u.object.length; ++ i) 184 | if (!strcmp (u.object.values [i].name, index)) 185 | return *u.object.values [i].value; 186 | 187 | return json_value_none; 188 | } 189 | 190 | inline operator const char * () const 191 | { 192 | switch (type) 193 | { 194 | case json_string: 195 | return u.string.ptr; 196 | 197 | default: 198 | return ""; 199 | }; 200 | } 201 | 202 | inline operator json_int_t () const 203 | { 204 | switch (type) 205 | { 206 | case json_integer: 207 | return u.integer; 208 | 209 | case json_double: 210 | return (json_int_t) u.dbl; 211 | 212 | default: 213 | return 0; 214 | }; 215 | } 216 | 217 | inline operator bool () const 218 | { 219 | if (type != json_boolean) 220 | return false; 221 | 222 | return u.boolean != 0; 223 | } 224 | 225 | inline operator double () const 226 | { 227 | switch (type) 228 | { 229 | case json_integer: 230 | return (double) u.integer; 231 | 232 | case json_double: 233 | return u.dbl; 234 | 235 | default: 236 | return 0; 237 | }; 238 | } 239 | 240 | #endif 241 | 242 | } json_value; 243 | 244 | json_value * json_parse (const json_char * json, 245 | size_t length); 246 | 247 | json_value * json_parse_ex (json_settings * settings, 248 | const json_char * json, 249 | size_t length, 250 | char * error); 251 | 252 | void json_value_free (json_value *); 253 | 254 | 255 | /* Not usually necessary, unless you used a custom mem_alloc and now want to 256 | * use a custom mem_free. 257 | */ 258 | void json_value_free_ex (json_settings * settings, 259 | json_value *); 260 | 261 | 262 | #ifdef __cplusplus 263 | } /* extern "C" */ 264 | #endif 265 | 266 | #endif 267 | 268 | 269 | -------------------------------------------------------------------------------- /src/tests/test_site.c: -------------------------------------------------------------------------------- 1 | #include "site.h" 2 | #include "stacktrace.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void test_create_site_conf() { 10 | site_conf_t *conf = site_conf_create(); 11 | assert(conf != NULL); 12 | assert(conf->site_size == 0); 13 | assert(site_conf_destroy(conf) == 0); 14 | } 15 | 16 | static void test_create_site() { 17 | site_t *site = site_create("jerrypeng.me"); 18 | assert(site != NULL); 19 | assert(strcmp("jerrypeng.me", site->host) == 0); 20 | assert(site->location_head != NULL); 21 | assert(site->location_head->next == NULL); 22 | assert(site_destroy(site) == 0); 23 | } 24 | 25 | static void test_add_site() { 26 | site_conf_t *conf = site_conf_create(); 27 | site_t *site = site_create("jerrypeng.me"); 28 | site_t *site2 = site_create("clojure.org"); 29 | site_t *site3 = site_create("jerrypeng.me"); 30 | 31 | assert(site_conf_add_site(conf, site) == 0); 32 | assert(site_conf_add_site(conf, site2) == 0); 33 | // Duplicate site can not be added. 34 | assert(site_conf_add_site(conf, site3) < 0); 35 | 36 | assert(conf->site_size == 2); 37 | assert(conf->sites[0] != NULL); 38 | assert(strcmp("jerrypeng.me", conf->sites[0]->host) == 0); 39 | assert(conf->sites[1] != NULL); 40 | assert(strcmp("clojure.org", conf->sites[1]->host) == 0); 41 | } 42 | 43 | static void test_add_location() { 44 | site_t *site = site_create("jerrypeng.me"); 45 | location_t *loc; 46 | 47 | assert(site_add_location(site, URI_PREFIX, "/", NULL, NULL) == 0); 48 | assert(site_add_location(site, URI_PREFIX, "/foo", NULL, NULL) == 0); 49 | assert(site_add_location(site, URI_PREFIX, "/foobar", NULL, NULL) == 0); 50 | assert(site_add_location(site, URI_PREFIX, "/bar", NULL, NULL) == 0); 51 | assert(site_add_location(site, URI_PREFIX, "/foo/bar", NULL, NULL) == 0); 52 | 53 | // Prefix must starts with "/" 54 | assert(site_add_location(site, URI_PREFIX, "oops", NULL, NULL) < 0); 55 | 56 | assert(site_add_location(site, URI_REGEX, "\\.jsp", NULL, "jsp") == 0); 57 | assert(site_add_location(site, URI_REGEX, "\\.php", NULL, "php") == 0); 58 | assert(site_add_location(site, URI_REGEX, "\\.cgi", NULL, "cgi") == 0); 59 | 60 | //Invalid regex 61 | assert(site_add_location(site, URI_REGEX, "*", NULL, NULL) < 0); 62 | 63 | loc = site->location_head->next; 64 | 65 | assert(loc != NULL); 66 | assert(loc->match_type == URI_REGEX); 67 | assert(strcmp("jsp", (char*)loc->handler_conf) == 0); 68 | loc = loc->next; 69 | 70 | assert(loc != NULL); 71 | assert(loc->match_type == URI_REGEX); 72 | assert(strcmp("php", (char*)loc->handler_conf) == 0); 73 | loc = loc->next; 74 | 75 | assert(loc != NULL); 76 | assert(loc->match_type == URI_REGEX); 77 | assert(strcmp("cgi", (char*)loc->handler_conf) == 0); 78 | loc = loc->next; 79 | 80 | assert(loc != NULL); 81 | assert(loc->match_type == URI_PREFIX); 82 | assert(strcmp("/foo/bar", loc->uri.prefix) == 0); 83 | loc = loc->next; 84 | 85 | assert(loc != NULL); 86 | assert(loc->match_type == URI_PREFIX); 87 | assert(strcmp("/foobar", loc->uri.prefix) == 0); 88 | loc = loc->next; 89 | 90 | assert(loc != NULL); 91 | assert(loc->match_type == URI_PREFIX); 92 | assert(strcmp("/foo", loc->uri.prefix) == 0); 93 | loc = loc->next; 94 | 95 | assert(loc != NULL); 96 | assert(loc->match_type == URI_PREFIX); 97 | assert(strcmp("/bar", loc->uri.prefix) == 0); 98 | loc = loc->next; 99 | 100 | assert(loc != NULL); 101 | assert(loc->match_type == URI_PREFIX); 102 | assert(strcmp("/", loc->uri.prefix) == 0); 103 | loc = loc->next; 104 | } 105 | 106 | #define ROOT_VAL 90 107 | #define FOO_VAL 91 108 | #define BAR_VAL 92 109 | #define FOOBAR_VAL 93 110 | #define JPG_VAL 94 111 | 112 | int root_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 113 | return ROOT_VAL; 114 | } 115 | 116 | int foo_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 117 | return FOO_VAL; 118 | } 119 | 120 | int bar_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 121 | return BAR_VAL; 122 | } 123 | 124 | int foobar_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 125 | return FOOBAR_VAL; 126 | } 127 | 128 | int jpg_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 129 | return JPG_VAL; 130 | } 131 | 132 | void test_handler_single_site() { 133 | site_conf_t *conf = site_conf_create(); 134 | site_t *site = site_create("jerrypeng.me"); 135 | request_t req; 136 | response_t resp; 137 | handler_ctx_t ctx; 138 | 139 | bzero(&req, sizeof(request_t)); 140 | bzero(&resp, sizeof(response_t)); 141 | bzero(&ctx, sizeof(handler_ctx_t)); 142 | 143 | assert(site_conf_add_site(conf, site) == 0); 144 | assert(site_add_location(site, URI_PREFIX, "/", root_handler, NULL) == 0); 145 | assert(site_add_location(site, URI_PREFIX, "/foo", foo_handler, NULL) == 0); 146 | assert(site_add_location(site, URI_PREFIX, "/bar", bar_handler, NULL) == 0); 147 | assert(site_add_location(site, URI_PREFIX, "/foo/bar", foobar_handler, NULL) == 0); 148 | assert(site_add_location(site, URI_REGEX, "\\.jpg", jpg_handler, NULL) == 0); 149 | 150 | req.host = "jerrypeng.me"; 151 | 152 | ctx.conf = conf; 153 | req.path = "/foo/bar"; 154 | assert(site_handler(&req, &resp, &ctx) == FOOBAR_VAL); 155 | 156 | ctx.conf = conf; 157 | req.path = "/foo/bar1"; 158 | assert(site_handler(&req, &resp, &ctx) == FOO_VAL); 159 | 160 | ctx.conf = conf; 161 | req.path = "/bar/1"; 162 | assert(site_handler(&req, &resp, &ctx) == BAR_VAL); 163 | 164 | ctx.conf = conf; 165 | req.path = "/foo/bar/foo.jpg"; 166 | assert(site_handler(&req, &resp, &ctx) == JPG_VAL); 167 | 168 | ctx.conf = conf; 169 | req.path = "/foo.jpg"; 170 | assert(site_handler(&req, &resp, &ctx) == JPG_VAL); 171 | 172 | ctx.conf = conf; 173 | req.path = "/"; 174 | assert(site_handler(&req, &resp, &ctx) == ROOT_VAL); 175 | 176 | } 177 | 178 | 179 | void test_handler_multi_site() { 180 | site_conf_t *conf = site_conf_create(); 181 | site_t *site = site_create("foo.com"); 182 | site_t *site1 = site_create("bar.com"); 183 | request_t req; 184 | response_t resp; 185 | handler_ctx_t ctx; 186 | 187 | bzero(&req, sizeof(request_t)); 188 | bzero(&resp, sizeof(response_t)); 189 | bzero(&ctx, sizeof(handler_ctx_t)); 190 | 191 | assert(site_add_location(site, URI_PREFIX, "/", foo_handler, NULL) == 0); 192 | assert(site_conf_add_site(conf, site) == 0); 193 | 194 | assert(site_add_location(site1, URI_PREFIX, "/", bar_handler, NULL) == 0); 195 | assert(site_conf_add_site(conf, site1) == 0); 196 | 197 | req.host = "jerrypeng.me"; // Unknown host will go to the first 198 | // one configured. 199 | ctx.conf = conf; 200 | req.path = "/foo/bar"; 201 | assert(site_handler(&req, &resp, &ctx) == FOO_VAL); 202 | 203 | req.host = "foo.com"; 204 | ctx.conf = conf; 205 | req.path = "/foo/bar"; 206 | assert(site_handler(&req, &resp, &ctx) == FOO_VAL); 207 | 208 | req.host = "bar.com"; 209 | ctx.conf = conf; 210 | req.path = "/"; 211 | assert(site_handler(&req, &resp, &ctx) == BAR_VAL); 212 | } 213 | 214 | int main(int argc, char *argv[]) { 215 | print_stacktrace_on_error(); 216 | test_create_site_conf(); 217 | test_create_site(); 218 | test_add_site(); 219 | test_add_location(); 220 | test_handler_single_site(); 221 | test_handler_multi_site(); 222 | info("All tests finished."); 223 | return 0; 224 | } 225 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef unsigned char byte_t; 11 | 12 | struct _buffer { 13 | byte_t *data; 14 | size_t size; 15 | size_t capacity; 16 | int head; 17 | int tail; 18 | }; 19 | 20 | __inline__ static void _do_put(buffer_t *buf, byte_t *data, size_t len); 21 | __inline__ static void _do_get(buffer_t *buf, byte_t *target, size_t len); 22 | __inline__ static void _do_consume(buffer_t *buf, size_t len, consumer_func func, void *args); 23 | 24 | 25 | buffer_t *buffer_create(size_t size) { 26 | buffer_t *buf; 27 | void *mem; 28 | size_t mem_sz; 29 | 30 | mem_sz = size + sizeof(buffer_t); 31 | mem = malloc(mem_sz); 32 | if (mem == NULL) { 33 | error("Error allocating buffer memory"); 34 | return NULL; 35 | } 36 | buf = (buffer_t*) mem; 37 | buf->capacity = size; 38 | buf->size = 0; 39 | buf->head = 0; 40 | buf->tail = 0; 41 | buf->data = (byte_t*) mem + sizeof(buffer_t); 42 | 43 | return buf; 44 | } 45 | 46 | 47 | int buffer_destroy(buffer_t *buf) { 48 | if (buf == NULL) 49 | return -1; 50 | free(buf); 51 | return 0; 52 | } 53 | 54 | 55 | int buffer_is_full(buffer_t *buf) { 56 | return buf->size == buf->capacity; 57 | } 58 | 59 | 60 | int buffer_is_empty(buffer_t *buf) { 61 | return buf->size == 0; 62 | } 63 | 64 | 65 | int buffer_put(buffer_t *buf, void *data, size_t len) { 66 | size_t cap; 67 | size_t write_len; 68 | byte_t *_data = (byte_t*) data; 69 | 70 | cap = buf->capacity - buf->size; 71 | if (len > cap) { 72 | return -1; 73 | } 74 | 75 | write_len = MIN(len, buf->capacity - buf->tail); 76 | _do_put(buf, _data, write_len); 77 | 78 | _data += write_len; 79 | write_len = len - write_len; 80 | if (write_len > 0) { 81 | _do_put(buf, _data, write_len); 82 | } 83 | return 0; 84 | } 85 | 86 | 87 | ssize_t buffer_fill(buffer_t *buf, int fd) { 88 | ssize_t n, iovcnt; 89 | struct iovec iov[2]; 90 | 91 | iov[0].iov_base = buf->data + buf->tail; 92 | if (buf->head > buf->tail) { 93 | iov[0].iov_len = buf->head - buf->tail; 94 | iovcnt = 1; 95 | } else { 96 | iov[0].iov_len = buf->capacity - buf->tail; 97 | iov[1].iov_base = buf->data; 98 | iov[1].iov_len = buf->head; 99 | iovcnt = 2; 100 | } 101 | 102 | n = readv(fd, iov, iovcnt); 103 | if (n < 0) { 104 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 105 | return 0; 106 | } 107 | return -1; 108 | } else if (n == 0) { 109 | return -2; 110 | } 111 | buf->size += n; 112 | buf->tail = (buf->tail + n) % buf->capacity; 113 | return n; 114 | } 115 | 116 | 117 | size_t buffer_get(buffer_t *buf, size_t len, void *target, size_t capacity) { 118 | size_t read_len, total = 0; 119 | byte_t *_target = (byte_t*) target; 120 | 121 | len = MIN(buf->size, len); 122 | read_len = MIN(len, buf->capacity - buf->head); 123 | read_len = MIN(read_len, capacity); 124 | _do_get(buf, _target, read_len); 125 | 126 | total += read_len; 127 | _target += read_len; 128 | capacity -= read_len; 129 | read_len = MIN(capacity, len - read_len); 130 | 131 | if (read_len > 0) { 132 | _do_get(buf, _target, read_len); 133 | total += read_len; 134 | } 135 | 136 | return total; 137 | } 138 | 139 | size_t buffer_skip(buffer_t *buf, size_t len) { 140 | len = MIN(buf->size, len); 141 | buf->size -= len; 142 | buf->head = (buf->head + len) % buf->capacity; 143 | return len; 144 | } 145 | 146 | size_t buffer_consume(buffer_t *buf, size_t len, consumer_func cb, void *args) { 147 | size_t read_len, total = 0; 148 | 149 | len = MIN(buf->size, len); 150 | read_len = MIN(len, buf->capacity - buf->head); 151 | _do_consume(buf, read_len, cb, args); 152 | 153 | total += read_len; 154 | read_len = len - read_len; 155 | 156 | if (read_len > 0) { 157 | _do_consume(buf, read_len, cb, args); 158 | total += read_len; 159 | } 160 | 161 | return total; 162 | } 163 | 164 | 165 | ssize_t buffer_flush(buffer_t *buf, int fd) { 166 | ssize_t n; 167 | int iovcnt; 168 | struct iovec iov[2]; 169 | 170 | iov[0].iov_base = buf->data + buf->head; 171 | if (buf->tail > buf->head) { 172 | iov[0].iov_len = buf->tail - buf->head; 173 | iovcnt = 1; 174 | } else { 175 | iov[0].iov_len = buf->capacity - buf->head; 176 | iov[1].iov_base = buf->data; 177 | iov[1].iov_len = buf->tail; 178 | iovcnt = 2; 179 | } 180 | 181 | n = writev(fd, iov, iovcnt); 182 | if (n < 0) { 183 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 184 | return 0; 185 | } 186 | return -1; 187 | } 188 | 189 | buf->size -= n; 190 | buf->head = (buf->head + n) % buf->capacity; 191 | 192 | return n; 193 | } 194 | 195 | 196 | #define MAX_DELIM_LEN 20 197 | 198 | int buffer_locate(buffer_t *buf, char *delimiter) { 199 | char tmp[MAX_DELIM_LEN]; 200 | char last_char; 201 | char *sub; 202 | int idx = -1; 203 | size_t delim_len; 204 | 205 | if (buf->size <= 0 || *delimiter == '\0') { 206 | return -1; 207 | } 208 | 209 | if (buf->head < buf->tail) { 210 | last_char = buf->data[buf->tail]; 211 | buf->data[buf->tail] = '\0'; 212 | sub = strstr((char*)buf->data + buf->head, delimiter); 213 | buf->data[buf->tail] = last_char; 214 | if (sub != NULL) { 215 | idx = (sub - (char*)buf->data) - buf->head; 216 | goto finish; 217 | } 218 | } else { 219 | last_char = buf->data[buf->capacity - 1]; 220 | buf->data[buf->capacity - 1] = '\0'; 221 | sub = strstr((char*)buf->data + buf->head, delimiter); 222 | buf->data[buf->capacity - 1] = last_char; 223 | if (sub != NULL) { 224 | idx = (sub - (char*)buf->data) - buf->head; 225 | goto finish; 226 | } 227 | 228 | delim_len = strlen(delimiter); 229 | memcpy(tmp, buf->data + buf->capacity - delim_len, delim_len); 230 | memcpy(tmp + delim_len, buf->data, delim_len); 231 | tmp[delim_len * 2 + 1] = '\0'; 232 | sub = strstr(tmp, delimiter); 233 | if (sub != NULL) { 234 | idx = buf->capacity - delim_len - buf->head + (sub - tmp); 235 | goto finish; 236 | } 237 | 238 | last_char = buf->data[buf->tail]; 239 | buf->data[buf->tail] = '\0'; 240 | sub = strstr((char*)buf->data, delimiter); 241 | buf->data[buf->tail] = last_char; 242 | if (sub != NULL) { 243 | idx = (sub - (char*)buf->data) + (buf->capacity - buf->head); 244 | goto finish; 245 | } 246 | } 247 | 248 | finish: 249 | return idx; 250 | } 251 | 252 | __inline__ static void _do_put(buffer_t *buf, byte_t *data, size_t len) { 253 | memcpy(buf->data + buf->tail, data, len); 254 | buf->size += len; 255 | buf->tail = (buf->tail + len) % buf->capacity; 256 | } 257 | 258 | __inline__ static void _do_get(buffer_t *buf, byte_t *target, size_t len) { 259 | memcpy(target, buf->data + buf->head, len); 260 | buf->size -= len; 261 | buf->head = (buf->head + len) % buf->capacity; 262 | } 263 | 264 | __inline__ static void _do_consume(buffer_t *buf, size_t len, consumer_func func, void *args) { 265 | void *data = buf->data + buf->head; 266 | buf->size -= len; 267 | buf->head = (buf->head + len) % buf->capacity; 268 | func(data, len, args); 269 | } 270 | 271 | 272 | -------------------------------------------------------------------------------- /src/http_server.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "site.h" 3 | #include "json.h" 4 | #include "log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define MAX_CONNECTIONS 1024000 23 | #define MAX_BACKLOG 128 24 | 25 | 26 | static int _server_init(server_t *server); 27 | static int _configure_server(server_t *server, json_value *conf_obj); 28 | static void _server_connection_handler(ioloop_t *loop, 29 | int listen_fd, 30 | unsigned int events, 31 | void *args); 32 | 33 | 34 | server_t* server_create() { 35 | server_t *server; 36 | ioloop_t *ioloop; 37 | 38 | server = (server_t*) calloc(1, sizeof(server_t)); 39 | if (server == NULL) { 40 | error("Error allocating memory for server"); 41 | return NULL; 42 | } 43 | 44 | ioloop = (ioloop_t*) ioloop_create(MAX_CONNECTIONS); 45 | if (ioloop == NULL) { 46 | error("Error creating ioloop"); 47 | return NULL; 48 | } 49 | 50 | server->addr = "127.0.0.1"; 51 | server->port = 8000; 52 | server->ioloop = ioloop; 53 | server->state = SERVER_INIT; 54 | server->loglevel = INFO; 55 | return server; 56 | } 57 | 58 | 59 | server_t* server_parse_conf(char *configfile) { 60 | server_t *server = NULL; 61 | json_value *json = NULL; 62 | json_settings settings = {0}; 63 | int fd; 64 | char buf[10240]; 65 | char error[128]; 66 | ssize_t sz; 67 | 68 | server = server_create(); 69 | if (server == NULL) { 70 | return NULL; 71 | } 72 | 73 | fd = open(configfile, O_RDONLY); 74 | if (fd < 0) { 75 | error("Error opening config file"); 76 | goto error; 77 | } 78 | sz = read(fd, buf, 10240); 79 | 80 | json = json_parse_ex(&settings, buf, sz, error); 81 | if (json == NULL) { 82 | error("Error parsing JSON config file: %s", error); 83 | goto error; 84 | } else if (json->type != json_object) { 85 | error("Invalid conf: the root is not a JSON object"); 86 | goto error; 87 | } 88 | 89 | // This pointer is used for destroying the JSON value 90 | server->conf = json; 91 | 92 | if (_configure_server(server, json) != 0) { 93 | error("Error detected when configuring the server"); 94 | goto error; 95 | } 96 | 97 | close(fd); 98 | return server; 99 | 100 | error: 101 | if (fd > 0) { 102 | close(fd); 103 | } 104 | if (json != NULL) { 105 | json_value_free(json); 106 | server->conf = NULL; 107 | } 108 | server_destroy(server); 109 | return NULL; 110 | } 111 | 112 | int server_destroy(server_t *server) { 113 | ioloop_destroy(server->ioloop); 114 | if (server->conf != NULL) 115 | json_value_free(server->conf); 116 | free(server); 117 | return 0; 118 | } 119 | 120 | int server_start(server_t *server) { 121 | configure_log(server->loglevel, server->logfile, !server->daemonize); 122 | if (_server_init(server) < 0) { 123 | error("Error initializing server"); 124 | return -1; 125 | } 126 | // Block SIGPIPE 127 | if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { 128 | error("Error blocking SIGPIPE"); 129 | } 130 | info("Start running server on %d", server->port); 131 | server->state = SERVER_RUNNING; 132 | return ioloop_start(server->ioloop); 133 | } 134 | 135 | int server_stop(server_t *server) { 136 | info("Stopping server"); 137 | if (ioloop_stop(server->ioloop) < 0) { 138 | error("Error stopping ioloop"); 139 | return -1; 140 | } 141 | server->state = SERVER_STOPPED; 142 | return 0; 143 | } 144 | 145 | static int _server_init(server_t *server) { 146 | int listen_fd; 147 | struct sockaddr_in addr; 148 | 149 | // ---------- Create and bind listen socket fd -------------- 150 | listen_fd = socket(AF_INET, SOCK_STREAM, 0); 151 | if (listen_fd == -1) { 152 | error("Error creating socket"); 153 | return -1; 154 | } 155 | 156 | bzero(&addr, sizeof(struct sockaddr_in)); 157 | addr.sin_addr.s_addr = INADDR_ANY; 158 | addr.sin_port = htons(server->port); 159 | 160 | if (bind(listen_fd, 161 | (struct sockaddr *)&addr, 162 | sizeof(struct sockaddr_in)) == -1) { 163 | error("Error binding address"); 164 | close(listen_fd); 165 | return -1; 166 | } 167 | 168 | // ------------ Start listening ------------------------------ 169 | if (listen(listen_fd, MAX_BACKLOG) == -1) { 170 | error("Error listening"); 171 | close(listen_fd); 172 | return -1; 173 | } 174 | if (set_nonblocking(listen_fd) < 0) { 175 | error("Error configuring non-blocking"); 176 | close(listen_fd); 177 | return -1; 178 | } 179 | server->listen_fd = listen_fd; 180 | if (ioloop_add_handler(server->ioloop, 181 | listen_fd, 182 | EPOLLIN, 183 | _server_connection_handler, 184 | server) < 0) { 185 | error("Error add connection handler"); 186 | return -1; 187 | } 188 | return 0; 189 | } 190 | 191 | static int _configure_server(server_t *server, json_value *conf_obj) { 192 | json_value *val; 193 | char *name, *port_str; 194 | site_conf_t *site_conf = NULL; 195 | int i; 196 | int lvl = INFO; 197 | 198 | for (i = 0; i < conf_obj->u.object.length; i++) { 199 | name = conf_obj->u.object.values[i].name; 200 | val = conf_obj->u.object.values[i].value; 201 | if (strcmp("listen", name) == 0 && val->type == json_string) { 202 | port_str = strchr(val->u.string.ptr, ':'); 203 | if (port_str != NULL) { 204 | *port_str = '\0'; 205 | port_str++; 206 | server->port = (unsigned short) atoi(port_str); 207 | } else { 208 | server->port = 80; 209 | } 210 | server->addr = val->u.string.ptr; 211 | } else if(strcmp("sites", name) == 0) { 212 | site_conf = site_conf_parse(val); 213 | if (site_conf == NULL) { 214 | error("Error creating site conf"); 215 | return -1; 216 | } 217 | } else if(strcmp("logfile", name) == 0 && val->type == json_string) { 218 | server->logfile = val->u.string.ptr; 219 | } else if(strcmp("daemonize", name) == 0 && val->type == json_boolean) { 220 | server->daemonize = val->u.boolean; 221 | } else if(strcmp("pidfile", name) == 0 && val->type == json_string) { 222 | server->pidfile = val->u.string.ptr; 223 | } else if(strcmp("loglevel", name) == 0 && val->type == json_string) { 224 | if (strcasecmp("debug", val->u.string.ptr) == 0) { 225 | lvl = DEBUG; 226 | } else if(strcasecmp("info", val->u.string.ptr) == 0) { 227 | lvl = INFO; 228 | } else if(strcasecmp("warn", val->u.string.ptr) == 0) { 229 | lvl = WARN; 230 | } else if(strcasecmp("error", val->u.string.ptr) == 0) { 231 | lvl = ERROR; 232 | } else { 233 | warn("Unknown log level: %s", val->u.string.ptr); 234 | } 235 | server->loglevel = lvl; 236 | } else { 237 | warn("Unknown config command %s with type %d", name, val->type); 238 | } 239 | 240 | } 241 | 242 | if (site_conf == NULL) { 243 | error("No site found"); 244 | return -1; 245 | } 246 | server->handler = site_handler; 247 | server->handler_conf = site_conf; 248 | return 0; 249 | } 250 | 251 | static void _server_connection_handler(ioloop_t *loop, 252 | int listen_fd, 253 | unsigned int events, 254 | void *args) 255 | { 256 | connection_t *conn; 257 | server_t *server = (server_t*) args; 258 | 259 | while ((conn = connection_accept(server, listen_fd)) != NULL) { 260 | connection_run(conn); 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/http.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_H 2 | #define __HTTP_H 3 | 4 | #include "common.h" 5 | #include "json.h" 6 | #include "ioloop.h" 7 | #include "iostream.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define REQUEST_BUFFER_SIZE 2048 16 | #define RESPONSE_BUFFER_SIZE 2048 17 | #define MAX_HEADER_SIZE 25 18 | #define MAX_STATE_STACK_SIZE 256 19 | 20 | /* 21 | * HTTP request/response 22 | */ 23 | typedef struct _request request_t; 24 | typedef struct _response response_t; 25 | typedef struct _http_header http_header_t; 26 | typedef struct _http_status http_status_t; 27 | typedef union _ctx_state ctx_state_t; 28 | typedef struct _handler_ctx handler_ctx_t; 29 | typedef struct _state_node ctx_node_t; 30 | 31 | /* 32 | * HTTP server/connection 33 | */ 34 | typedef struct _server server_t; 35 | typedef struct _connection connection_t; 36 | 37 | enum _handler_result { 38 | HANDLER_DONE, 39 | HANDLER_UNFISHED, 40 | }; 41 | 42 | /* 43 | * HTTP handler function 44 | */ 45 | typedef int (*handler_func)(request_t *request, response_t *response, handler_ctx_t *ctx); 46 | 47 | request_t* request_create(connection_t *conn); 48 | int request_reset(request_t *req); 49 | int request_destroy(request_t *request); 50 | const char* request_get_header(request_t *request, const char *header_name); 51 | int request_parse_headers(request_t *request, 52 | const char *data, 53 | const size_t data_len, 54 | size_t *consumed); 55 | 56 | response_t* response_create(connection_t *conn); 57 | int response_reset(response_t *resp); 58 | int response_destroy(response_t *response); 59 | const char* response_get_header(response_t *response, const char *header_name); 60 | int response_set_header(response_t *response, char *name, char *value); 61 | int response_set_header_printf(response_t *response, char* name, 62 | const char *fmt, ...); 63 | char* response_alloc(response_t *response, size_t n); 64 | int response_write(response_t *response, 65 | char *data, 66 | size_t data_len, 67 | handler_func next_handler); 68 | int response_send_file(response_t *response, 69 | int fd, 70 | size_t offset, 71 | size_t size, 72 | handler_func next_handler); 73 | int response_send_status(response_t *response, http_status_t status); 74 | int response_send_headers(response_t *response, handler_func next_handler); 75 | 76 | handler_ctx_t* context_create(); 77 | int context_destroy(handler_ctx_t *ctx); 78 | int context_reset(handler_ctx_t *ctx); 79 | int context_push(handler_ctx_t *ctx, ctx_state_t stat); 80 | ctx_state_t* context_pop(handler_ctx_t *ctx); 81 | ctx_state_t* context_peek(handler_ctx_t *ctx); 82 | 83 | connection_t* connection_accept(server_t *server, int listen_fd); 84 | int connection_close(connection_t *conn); 85 | int connection_destroy(connection_t *conn); 86 | int connection_run(connection_t *conn); 87 | int connection_finish_current_request(connection_t *conn); 88 | void connection_run_handler(connection_t *conn, handler_func handler); 89 | 90 | server_t* server_create(); 91 | server_t* server_parse_conf(char *confile); 92 | int server_destroy(server_t *server); 93 | int server_start(server_t *server); 94 | int server_stop(server_t *server); 95 | 96 | /* Common HTTP status codes */ 97 | 98 | // 1xx informational 99 | extern http_status_t STATUS_CONTINUE; 100 | 101 | // 2xx success 102 | extern http_status_t STATUS_OK; 103 | extern http_status_t STATUS_CREATED; 104 | extern http_status_t STATUS_ACCEPTED; 105 | extern http_status_t STATUS_NO_CONTENT; 106 | extern http_status_t STATUS_PARTIAL_CONTENT; 107 | 108 | // 3xx redirection 109 | extern http_status_t STATUS_MOVED; 110 | extern http_status_t STATUS_FOUND; 111 | extern http_status_t STATUS_SEE_OTHER; 112 | extern http_status_t STATUS_NOT_MODIFIED; 113 | 114 | // 4xx client errors 115 | extern http_status_t STATUS_BAD_REQUEST; 116 | extern http_status_t STATUS_UNAUTHORIZED; 117 | extern http_status_t STATUS_FORBIDDEN; 118 | extern http_status_t STATUS_NOT_FOUND; 119 | extern http_status_t STATUS_METHOD_NOT_ALLOWED; 120 | extern http_status_t STATUS_RANGE_NOT_SATISFIABLE; 121 | 122 | // 5xx server errors 123 | extern http_status_t STATUS_INTERNAL_ERROR; 124 | extern http_status_t STATUS_NOT_IMPLEMENTED; 125 | extern http_status_t STATUS_BAD_GATEWAY; 126 | extern http_status_t STATUS_SERVICE_UNAVAILABLE; 127 | extern http_status_t STATUS_GATEWAY_TIMEOUT; 128 | 129 | 130 | typedef enum { 131 | HTTP_VERSION_UNKNOW = -1, 132 | HTTP_VERSION_0_9 = 9, 133 | HTTP_VERSION_1_0 = 10, 134 | HTTP_VERSION_1_1 = 11 135 | } http_version_e; 136 | 137 | typedef enum _http_methods { 138 | HTTP_METHOD_EXTENDED = 0, 139 | HTTP_METHOD_GET = 1, 140 | HTTP_METHOD_POST, 141 | HTTP_METHOD_PUT, 142 | HTTP_METHOD_DELETE, 143 | HTTP_METHOD_HEAD, 144 | HTTP_METHOD_TRACE, 145 | HTTP_METHOD_CONNECT, 146 | HTTP_METHOD_OPTIONS 147 | } http_method_e; 148 | 149 | typedef enum _connection_opt { 150 | CONN_CLOSE = 0, 151 | CONN_KEEP_ALIVE 152 | } connection_opt_e; 153 | 154 | struct _http_header { 155 | char *name; 156 | char *value; 157 | }; 158 | 159 | union _ctx_state { 160 | void *as_ptr; 161 | long long as_long; 162 | int as_int; 163 | char *as_str; 164 | }; 165 | 166 | struct _handler_ctx { 167 | ctx_state_t _stat_stack[MAX_STATE_STACK_SIZE]; 168 | int _stat_top; 169 | void *conf; 170 | }; 171 | 172 | struct _request { 173 | char *path; 174 | char *query_str; 175 | char *method; 176 | http_version_e version; 177 | 178 | char *host; 179 | size_t content_length; 180 | connection_opt_e connection; 181 | 182 | char *body; 183 | 184 | // Unresolved headers of the request 185 | http_header_t headers[MAX_HEADER_SIZE]; 186 | size_t header_count; 187 | 188 | connection_t *_conn; 189 | 190 | struct hsearch_data _header_hash; 191 | 192 | // All the request header fields' are stored in this buffer 193 | char _buffer[REQUEST_BUFFER_SIZE]; 194 | int _buf_idx; 195 | }; 196 | 197 | struct _http_status { 198 | unsigned int code; 199 | char *msg; 200 | }; 201 | 202 | struct _response { 203 | http_status_t status; 204 | http_version_e version; 205 | long content_length; 206 | connection_opt_e connection; 207 | int keep_alive_timeout; 208 | /* 209 | The response object does not manage the meory for the headers 210 | itself. So the programmer must guarantee that the pointers in 211 | the http_header_t struct is effective before the headers are 212 | sent to the client. 213 | */ 214 | http_header_t headers[MAX_HEADER_SIZE]; 215 | size_t header_count; 216 | 217 | struct hsearch_data _header_hash; 218 | 219 | char _buffer[RESPONSE_BUFFER_SIZE]; 220 | size_t _buf_idx; 221 | int _header_sent; 222 | connection_t *_conn; 223 | size_t *_size_written; 224 | // This response is done? 225 | int _done; 226 | 227 | // Next handler to call after current write finishes 228 | handler_func _next_handler; 229 | }; 230 | 231 | typedef enum _server_state { 232 | SERVER_INIT = 0, 233 | SERVER_RUNNING, 234 | SERVER_STOPPED 235 | } server_state; 236 | 237 | struct _server { 238 | handler_func handler; 239 | void *handler_conf; 240 | json_value *conf; 241 | 242 | int daemonize; 243 | char *logfile; 244 | char *pidfile; 245 | int loglevel; 246 | 247 | server_state state; 248 | int listen_fd; 249 | ioloop_t *ioloop; 250 | 251 | char *addr; 252 | unsigned short port; 253 | }; 254 | 255 | typedef enum _connection_state { 256 | CONN_ACTIVE, 257 | CONN_CLOSING, 258 | CONN_CLOSED 259 | } conn_stat_e; 260 | 261 | struct _connection { 262 | server_t *server; 263 | iostream_t *stream; 264 | char remote_ip[20]; 265 | unsigned short remote_port; 266 | conn_stat_e state; 267 | 268 | request_t *request; 269 | response_t *response; 270 | handler_ctx_t *context; 271 | }; 272 | 273 | 274 | #endif /* end of include guard: __HTTP_H */ 275 | -------------------------------------------------------------------------------- /src/site.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "mod.h" 3 | #include "site.h" 4 | #include "json.h" 5 | #include "log.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static site_t *find_site(site_conf_t *conf, char *host); 12 | static location_t *find_location(site_t *site, const char *path); 13 | static int parse_locations(site_t *site, json_value *location_objs); 14 | 15 | site_conf_t *site_conf_create() { 16 | site_conf_t *conf; 17 | 18 | if (init_modules() != 0) { 19 | return NULL; 20 | } 21 | 22 | conf = (site_conf_t*) calloc(1, sizeof(site_conf_t)); 23 | if (conf == NULL) { 24 | error("Error allocating memory for site configuration"); 25 | return NULL; 26 | } 27 | 28 | if (hcreate_r(MAX_SITES, &conf->site_hash) == 0) { 29 | error("Error creating site hash"); 30 | free(conf); 31 | return NULL; 32 | } 33 | 34 | return conf; 35 | } 36 | 37 | site_conf_t *site_conf_parse(json_value *sites_obj) { 38 | site_conf_t *conf; 39 | site_t *site; 40 | json_value *val; 41 | int i; 42 | 43 | if (sites_obj->type != json_array) { 44 | error("Config option 'sites' must be a JSON array"); 45 | return NULL; 46 | } 47 | conf = site_conf_create(); 48 | if (conf == NULL) { 49 | return NULL; 50 | } 51 | for (i = 0; i < sites_obj->u.array.length; i++) { 52 | val = sites_obj->u.array.values[i]; 53 | if (val->type != json_object) { 54 | error("The elements of 'sites' must be JSON objects"); 55 | site_conf_destroy(conf); 56 | return NULL; 57 | } 58 | site = site_parse(val); 59 | if (site == NULL) { 60 | error("Error creating site"); 61 | site_conf_destroy(conf); 62 | return NULL; 63 | } 64 | site_conf_add_site(conf, site); 65 | } 66 | 67 | return conf; 68 | } 69 | 70 | int site_conf_destroy(site_conf_t *conf) { 71 | int i; 72 | for (i = 0; i < conf->site_size; i++) { 73 | site_destroy(conf->sites[i]); 74 | } 75 | hdestroy_r(&conf->site_hash); 76 | free(conf); 77 | return 0; 78 | } 79 | 80 | int site_conf_add_site(site_conf_t *conf, site_t *site) { 81 | char *host = site->host; 82 | ENTRY item, *ret; 83 | 84 | if (conf->site_size >= MAX_SITES) { 85 | error("Max sites reached: %d", MAX_SITES); 86 | return -1; 87 | } 88 | 89 | item.key = host; 90 | if (hsearch_r(item, FIND, &ret, &conf->site_hash) != 0) { 91 | error("Duplicate host: %s", host); 92 | return -1; 93 | } 94 | 95 | conf->sites[conf->site_size++] = site; 96 | 97 | item.data = site; 98 | hsearch_r(item, ENTER, &ret, &conf->site_hash); 99 | return 0; 100 | } 101 | 102 | 103 | site_t *site_create(const char* host) { 104 | site_t *site; 105 | location_t *loc; 106 | 107 | site = (site_t*) calloc(1, sizeof(site_t)); 108 | if (site == NULL) { 109 | error("Error allocating memory for the site"); 110 | return NULL; 111 | } 112 | 113 | if (host != NULL) 114 | strncpy(site->host, host, MAX_HOST_LENGTH); 115 | 116 | loc = (location_t*) calloc(1, sizeof(location_t)); 117 | if (loc == NULL) { 118 | error("Error allocating memory for location"); 119 | free(site); 120 | return NULL; 121 | } 122 | 123 | site->location_head = loc; 124 | return site; 125 | } 126 | 127 | site_t *site_parse(json_value *site_obj) { 128 | site_t *site; 129 | DECLARE_CONF_VARIABLES() 130 | 131 | site = site_create(NULL); 132 | if (site == NULL) { 133 | return NULL; 134 | } 135 | 136 | BEGIN_CONF_HANDLE(site_obj) 137 | ON_CONF_OPTION("host", json_string) { 138 | strncpy(site->host, val->u.string.ptr, MAX_HOST_LENGTH); 139 | } 140 | ON_CONF_OPTION("locations", json_array) { 141 | if (parse_locations(site, val) != 0) { 142 | error("Error parsing locations"); 143 | site_destroy(site); 144 | return NULL; 145 | } 146 | } 147 | END_CONF_HANDLE() 148 | 149 | return site; 150 | } 151 | 152 | int parse_locations(site_t *site, json_value *location_objs) { 153 | int i, j; 154 | void *handler_conf; 155 | const char *mod_name = NULL; 156 | char *name, *path = NULL; 157 | int type = URI_PREFIX; 158 | json_value *val, *inner_val; 159 | module_t *mod; 160 | 161 | for (i = 0; i < location_objs->u.array.length; i++) { 162 | val = location_objs->u.array.values[i]; 163 | if (val->type != json_object) { 164 | error("The elements of 'locations' must be JSON objects"); 165 | return -1; 166 | } 167 | 168 | for (j = 0; j < val->u.object.length; j++) { 169 | name = val->u.object.values[j].name; 170 | inner_val = val->u.object.values[j].value; 171 | if (strcmp("module", name) == 0 && inner_val->type == json_string) { 172 | mod_name = inner_val->u.string.ptr; 173 | } else if (strcmp("path", name) == 0 && inner_val->type == json_string) { 174 | path = inner_val->u.string.ptr; 175 | if (path[0] == '~' && path[1] == ' ') { 176 | type = URI_REGEX; 177 | do {path++;} while (*path == ' ' || *path == '\t'); 178 | } 179 | } 180 | } 181 | 182 | mod = find_module(mod_name); 183 | if (mod == NULL) { 184 | error("Unknown mod: %s", mod_name); 185 | return -1; 186 | } 187 | handler_conf = mod->create(val); 188 | if (site_add_location(site, type, path, mod->handler, handler_conf) != 0) { 189 | error("Error adding location %s to site", path); 190 | return -1; 191 | } 192 | } 193 | return 0; 194 | } 195 | 196 | int site_destroy(site_t *site) { 197 | location_t *prev, *loc = site->location_head; 198 | 199 | while (loc != NULL) { 200 | prev = loc; 201 | loc = loc->next; 202 | free(prev); 203 | } 204 | free(site); 205 | return 0; 206 | } 207 | 208 | int site_handler(request_t *req, response_t *resp, handler_ctx_t *ctx) { 209 | site_conf_t *conf; 210 | site_t *site; 211 | location_t *loc; 212 | 213 | conf = (site_conf_t*) ctx->conf; 214 | site = find_site(conf, req->host); 215 | if (site == NULL) { 216 | goto NOT_FOUND; 217 | } 218 | loc = find_location(site, req->path); 219 | if (loc == NULL) { 220 | goto NOT_FOUND; 221 | } 222 | 223 | ctx->conf = loc->handler_conf; 224 | return loc->handler(req, resp, ctx); 225 | 226 | NOT_FOUND: 227 | return response_send_status(resp, STATUS_NOT_FOUND); 228 | } 229 | 230 | int site_add_location(site_t *site, int type, 231 | char *prefix_or_regex, 232 | handler_func handler, 233 | void *handler_conf) { 234 | location_t *loc = NULL, *pos; 235 | regex_t *reg; 236 | void *mem; 237 | size_t len; 238 | 239 | pos = site->location_head; 240 | len = strlen(prefix_or_regex); 241 | 242 | if (len == 0) { 243 | error("Empty prefix/regex"); 244 | return -1; 245 | } 246 | if (type == URI_PREFIX) { 247 | if (prefix_or_regex[0] != '/') { 248 | error("URI Prefix must starts with /"); 249 | return -1; 250 | } 251 | loc = (location_t*) calloc(1, sizeof(location_t)); 252 | if (loc == NULL) { 253 | error("Error allocating memory for new location"); 254 | return -1; 255 | } 256 | loc->uri.prefix = prefix_or_regex; 257 | } else if (type == URI_REGEX) { 258 | mem = calloc(1, sizeof(location_t) + sizeof(regex_t)); 259 | if (mem != NULL) { 260 | loc = (location_t*) mem; 261 | mem = ((char*)mem + sizeof(location_t)); 262 | reg = (regex_t*) mem; 263 | } else { 264 | error("Error allocating memory for new location"); 265 | return -1; 266 | } 267 | if (regcomp(reg, prefix_or_regex, REG_EXTENDED | REG_NOSUB) != 0) { 268 | error("Invalid regex: %s", prefix_or_regex); 269 | free(loc); 270 | return -1; 271 | } 272 | loc->uri.regex = reg; 273 | } else { 274 | error("Unknown location type: %d", type); 275 | return -1; 276 | } 277 | 278 | loc->match_type = type; 279 | loc->handler = handler; 280 | loc->handler_conf = handler_conf; 281 | 282 | while (pos->next != NULL) { 283 | if (type == URI_REGEX 284 | && pos->next->match_type != URI_REGEX) { 285 | break; 286 | } else if (type == URI_PREFIX 287 | && pos->next->match_type == URI_PREFIX 288 | && len > strlen(pos->next->uri.prefix)) { 289 | break; 290 | } 291 | pos = pos->next; 292 | } 293 | 294 | loc->next = pos->next; 295 | pos->next = loc; 296 | return 0; 297 | } 298 | 299 | 300 | static site_t *find_site(site_conf_t *conf, char *host) { 301 | ENTRY item, *ret; 302 | 303 | if (conf->site_size == 0) { 304 | return NULL; 305 | } else if (conf->site_size == 1) { 306 | return conf->sites[0]; 307 | } 308 | 309 | item.key = host; 310 | if (hsearch_r(item, FIND, &ret, &conf->site_hash)) { 311 | return (site_t*) ret->data; 312 | } else { 313 | return conf->sites[0]; 314 | } 315 | } 316 | 317 | static location_t *find_location(site_t *site, const char *path) { 318 | location_t *loc = site->location_head->next; 319 | 320 | while (loc != NULL) { 321 | switch (loc->match_type) { 322 | case URI_REGEX: 323 | if (regexec(loc->uri.regex, path, 0, NULL, 0) == 0) 324 | return loc; 325 | 326 | case URI_PREFIX: 327 | if (path_starts_with(loc->uri.prefix, path) > 0) 328 | return loc; 329 | } 330 | 331 | loc = loc->next; 332 | } 333 | 334 | return NULL; 335 | } 336 | 337 | 338 | -------------------------------------------------------------------------------- /src/tests/test_http.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | #include "common.h" 3 | #include "log.h" 4 | #include "stacktrace.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | char* test_request = 11 | "GET /home/hello.do?id=1001&name=hello HTTP/1.1\r\n" 12 | "Host: www.javaeye.com\r\n" 13 | "Connection: keep-alive\r\n" 14 | "Referer: http://www.javaeye.com/\r\n" 15 | "Cache-Control: max-age=0\r\n" 16 | "If-Modified-Since: Sat, 15 Dec 2007 04:04:14 GMT\r\n" 17 | "If-None-Match: \"3922745954\"\r\n" 18 | "Accept: */*\r\n" 19 | "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15\r\n" 20 | "Accept-Encoding: gzip,deflate,sdch\r\n" 21 | "Accept-Language: zh-CN,zh;q=0.8\r\n" 22 | "Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3\r\n" 23 | "Cookie: lzstat_uv=39255935923757216993|1146165@1270080@1632275@1145010@0; remember_me=yes; login_token=MTAyMjExXzJkZjY4ZGIyNDIzZTdjMTE4YmM5OTU0OTQ2MTU0N2Fh%0A; _javaeye3_session_=BAh7BzoMdXNlcl9pZGkDQ48BOg9zZXNzaW9uX2lkIiVjMDdlNjJmZGJiNTFhZjhhYmU5Yjk4NjE1Y2ZjODBhOQ%3D%3D--b9945c8e50a5eba75e75c92b2c92e5a3a86f1000\r\n\r\n"; 24 | 25 | char* test_request_2 = 26 | "GET /home/hello.do?id=1001&name=hello HTTP/1.1\r\n" 27 | "Host: www.javaeye.com\r\n" 28 | "Connection: keep-alive\r\n" 29 | "Referer: http://www.javaeye.com/\r\n" 30 | "Cache-Control: max-age=0\r\n" 31 | "If-Modified-Since: Sat, 15 Dec 2007 04:04:14 GMT\r\n" 32 | "If-None-Match: \"3922745954\"\r\n" 33 | "Accept: */*\r\n" 34 | "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15\r\n" 35 | "Accept-Encoding: gzip,deflate,sdch\r\n" 36 | "Accept-Language: zh-CN,zh;q=0.8\r\n" 37 | "Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3\r\n" 38 | "Cookie: lzstat_uv=39255935923757216993|1146165@1270080@1632275@1145010@0; remember_me=yes; login_token=MTAyMjExXzJkZjY4ZGIyNDIzZTdjMTE4YmM5OTU0OTQ2MTU0N2Fh%0A; _javaeye3_session_=BAh7BzoMdXNlcl9pZGkDQ48BOg9zZXNzaW9uX2lkIiVjMDdlNjJmZGJiNTFhZjhhYmU5Yjk4NjE1Y2ZjODBhOQ%3D%3D--b9945c8e50a5eba75e75c92b2c92e5a3a86f1000\r\n\r\n" 39 | "GET /home/hello.do?id=1001&name=hello HTTP/1.1\r\n"; // This line is the start of another request 40 | 41 | char* invalid_req = 42 | "GET /home/hello.do?id=1001&name=hello HTTP/1.9\r\n" 43 | "Host: www.javaeye.com\r\n" 44 | "Connection: keep-alive\r\n" 45 | "Referer: http://www.javaeye.com/\r\n" 46 | "Cache-Control: max-age=0\r\n" 47 | "If-Modified-Since: Sat, 15 Dec 2007 04:04:14 GMT\r\n" 48 | "If-None-Match: \"3922745954\"\r\n" 49 | "Accept: */*\r\n" 50 | "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15\r\n" 51 | "Accept-Encoding: gzip,deflate,sdch\r\n" 52 | "Accept-Language: zh-CN,zh;q=0.8\r\n" 53 | "Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3\r\n" 54 | "Cookie: lzstat_uv=39255935923757216993|1146165@1270080@1632275@1145010@0; remember_me=yes; login_token=MTAyMjExXzJkZjY4ZGIyNDIzZTdjMTE4YmM5OTU0OTQ2MTU0N2Fh%0A; _javaeye3_session_=BAh7BzoMdXNlcl9pZGkDQ48BOg9zZXNzaW9uX2lkIiVjMDdlNjJmZGJiNTFhZjhhYmU5Yjk4NjE1Y2ZjODBhOQ%3D%3D--b9945c8e50a5eba75e75c92b2c92e5a3a86f1000\r\n\r\n"; 55 | 56 | const http_header_t expected_headers[] = { 57 | {"Host", "www.javaeye.com"}, 58 | {"Connection", "keep-alive"}, 59 | {"Referer", "http://www.javaeye.com/"}, 60 | {"Cache-Control", "max-age=0"}, 61 | {"If-Modified-Since", "Sat, 15 Dec 2007 04:04:14 GMT"}, 62 | {"If-None-Match", "\"3922745954\""}, 63 | {"Accept", "*/*"}, 64 | {"User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15"}, 65 | {"Accept-Encoding", "gzip,deflate,sdch"}, 66 | {"Accept-Language", "zh-CN,zh;q=0.8"}, 67 | {"Accept-Charset", "GBK,utf-8;q=0.7,*;q=0.3"}, 68 | {"Cookie", "lzstat_uv=39255935923757216993|1146165@1270080@1632275@1145010@0; remember_me=yes; login_token=MTAyMjExXzJkZjY4ZGIyNDIzZTdjMTE4YmM5OTU0OTQ2MTU0N2Fh%0A; _javaeye3_session_=BAh7BzoMdXNlcl9pZGkDQ48BOg9zZXNzaW9uX2lkIiVjMDdlNjJmZGJiNTFhZjhhYmU5Yjk4NjE1Y2ZjODBhOQ%3D%3D--b9945c8e50a5eba75e75c92b2c92e5a3a86f1000"} 69 | }; 70 | 71 | void dump_request(request_t *req); 72 | void assert_headers(request_t *req); 73 | 74 | static void assert_equals(const char* expected, const char *actual) { 75 | assert(strcmp(expected, actual) == 0); 76 | } 77 | 78 | void test_parse_once() { 79 | request_t *req; 80 | size_t req_size, consumed_size; 81 | int rc; 82 | 83 | req = request_create(NULL); 84 | assert(req != NULL); 85 | info("\n\nTesting parsing all in one time"); 86 | req_size = strlen(test_request); 87 | rc = request_parse_headers(req, test_request, req_size, &consumed_size); 88 | info("Request size: %zu, consumed size: %zu, return status: %d", 89 | req_size, consumed_size, rc); 90 | dump_request(req); 91 | assert(rc == STATUS_COMPLETE); 92 | assert(consumed_size == req_size); 93 | assert_headers(req); 94 | assert(request_destroy(req) == 0); 95 | } 96 | 97 | 98 | void test_parse_once_with_extra_data() { 99 | request_t *req; 100 | size_t req_size, consumed_size; 101 | int rc; 102 | 103 | req = request_create(NULL); 104 | assert(req != NULL); 105 | info("\n\nTesting parsing all in one time with extra data left"); 106 | req_size = strlen(test_request_2); 107 | rc = request_parse_headers(req, test_request_2, req_size, &consumed_size); 108 | info("Request size: %zu, consumed size: %zu, return status: %d", req_size, consumed_size, rc); 109 | dump_request(req); 110 | assert(rc == STATUS_COMPLETE); 111 | assert(consumed_size == req_size - strlen("GET /home/hello.do?id=1001&name=hello HTTP/1.1\r\n")); 112 | assert_headers(req); 113 | assert(request_destroy(req) == 0); 114 | } 115 | 116 | void test_parse_multiple_times() { 117 | request_t *req; 118 | size_t req_size, consumed_size; 119 | int rc; 120 | size_t part1_size; 121 | char *data; 122 | 123 | req = request_create(NULL); 124 | assert(req != NULL); 125 | info("\n\nTesting parsing all in multiple time"); 126 | data = test_request; 127 | req_size = strlen(data); 128 | part1_size = req_size / 2; 129 | 130 | // Incomplete request 131 | rc = request_parse_headers(req, data, part1_size, &consumed_size); 132 | info("First Time: Request size: %zu, consumed size: %zu, return status: %d", 133 | req_size, consumed_size, rc); 134 | dump_request(req); 135 | assert(rc == STATUS_INCOMPLETE); 136 | assert(consumed_size == part1_size); 137 | assert(request_destroy(req) == 0); 138 | } 139 | 140 | 141 | void test_parse_invalid_version() { 142 | request_t *req; 143 | int req_size, rc; 144 | size_t consumed_size; 145 | 146 | req = request_create(NULL); 147 | assert(req != NULL); 148 | req_size = strlen(invalid_req); 149 | 150 | info("\n\nTesting invalid HTTP version"); 151 | rc = request_parse_headers(req, invalid_req, req_size, &consumed_size); 152 | dump_request(req); 153 | assert(rc == STATUS_ERROR); 154 | assert(request_destroy(req) == 0); 155 | } 156 | 157 | 158 | char* test_request_3 = 159 | "GET /home/hello.do?id=1001&name=hello HTTP/1.1\r\n" 160 | "Host: www.javaeye.com\r\n" 161 | "Connection: keep-alive\r\n" 162 | "Content-Length: 42\r\n" 163 | "Referer: http://www.javaeye.com/\r\n\r\n"; 164 | 165 | void test_common_header_handling() { 166 | request_t *req; 167 | int req_size, rc; 168 | size_t consumed_size; 169 | 170 | req = request_create(NULL); 171 | assert(req != NULL); 172 | req_size = strlen(test_request_3); 173 | 174 | info("\n\nTesting common header handling"); 175 | rc = request_parse_headers(req, test_request_3, req_size, &consumed_size); 176 | dump_request(req); 177 | assert(rc == STATUS_COMPLETE); 178 | assert(strcmp(req->host, "www.javaeye.com") == 0); 179 | assert(req->content_length == 42); 180 | assert(req->connection == CONN_KEEP_ALIVE); 181 | assert(request_destroy(req) == 0); 182 | } 183 | 184 | 185 | void dump_request(request_t *req) { 186 | int i; 187 | 188 | info("--------- Parser State -----------------------"); 189 | info("Method: %s", req->method); 190 | info("Path: %s", req->path); 191 | info("Query String: %s", req->query_str); 192 | info("HTTP Version: %d", req->version); 193 | info("Header count: %zu", req->header_count); 194 | info("Headers: "); 195 | info("------------"); 196 | 197 | for (i = 0; i < req->header_count; i++) { 198 | info("\r%s: %s", req->headers[i].name, req->headers[i].value); 199 | } 200 | 201 | info("----------------------------------------------"); 202 | } 203 | 204 | void assert_headers(request_t *req) { 205 | int expected_header_size, i; 206 | const char* value; 207 | 208 | expected_header_size = sizeof(expected_headers) / sizeof(http_header_t); 209 | 210 | info("Expecting %d headers", expected_header_size); 211 | assert(expected_header_size == req->header_count); 212 | info("Checked"); 213 | 214 | for (i = 0; i < req->header_count; i++) { 215 | assert(strcmp(expected_headers[i].name, req->headers[i].name) == 0); 216 | assert(strcmp(expected_headers[i].value, req->headers[i].value) == 0); 217 | value = request_get_header(req, expected_headers[i].name); 218 | assert(value != NULL); 219 | assert(strcmp(expected_headers[i].value, value) == 0); 220 | } 221 | } 222 | 223 | void dump_response(response_t *resp) { 224 | int i; 225 | 226 | info("--------- Response State -----------------------"); 227 | info("Header count: %zu", resp->header_count); 228 | info("Headers: "); 229 | info("------------"); 230 | 231 | for (i = 0; i < resp->header_count; i++) { 232 | info("\r%s: %s", resp->headers[i].name, resp->headers[i].value); 233 | } 234 | 235 | info("----------------------------------------------"); 236 | } 237 | 238 | 239 | void test_response_set_header_basic() { 240 | response_t *response; 241 | response = response_create(NULL); 242 | assert(response != NULL); 243 | 244 | // Basic tests 245 | assert(response_set_header(response, "Content-Length", "201") == 0); 246 | assert(response_set_header(response, "Content-Type", "application/html") == 0); 247 | 248 | info("After setting content-length and content-type"); 249 | dump_response(response); 250 | 251 | assert(response->header_count == 2); 252 | assert_equals(response->headers[0].name, "Content-Length"); 253 | assert_equals(response->headers[0].value, "201"); 254 | 255 | assert_equals(response->headers[1].name, "Content-Type"); 256 | assert_equals(response->headers[1].value, "application/html"); 257 | 258 | assert_equals(response_get_header(response, "Content-Type"), "application/html"); 259 | assert_equals(response_get_header(response, "Content-type"), "application/html"); 260 | 261 | // Replace header value 262 | assert(response_set_header(response, "Content-Length", "1024") == 0); 263 | info("After setting content-length again"); 264 | dump_response(response); 265 | 266 | assert_equals(response->headers[0].value, "1024"); 267 | assert_equals(response_get_header(response, "content-length"), "1024"); 268 | 269 | // Set unknow, non-standard headers 270 | assert(response_set_header(response, "X-Foobar", "foobar") == 0); 271 | info("After setting non-standard header"); 272 | dump_response(response); 273 | 274 | assert_equals(response->headers[2].name, "X-Foobar"); 275 | assert_equals(response->headers[2].value, "foobar"); 276 | assert_equals(response_get_header(response, "x-Foobar"), "foobar"); 277 | 278 | assert_equals(response->_buffer, "x-foobar"); 279 | assert(response->_buf_idx == strlen("x-foobar") + 1); 280 | 281 | assert(response_destroy(response) == 0); 282 | } 283 | 284 | int main(int argc, const char *argv[]) 285 | { 286 | print_stacktrace_on_error(); 287 | //bzero(&request, sizeof(request_t)); 288 | test_parse_once(); 289 | test_parse_once_with_extra_data(); 290 | test_parse_multiple_times(); 291 | test_parse_invalid_version(); 292 | test_common_header_handling(); 293 | test_response_set_header_basic(); 294 | return 0; 295 | } 296 | -------------------------------------------------------------------------------- /src/mod_static.c: -------------------------------------------------------------------------------- 1 | #include "mod.h" 2 | #include "mod_static.h" 3 | #include "common.h" 4 | #include "log.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define MAX_EXPIRE_HOURS = 87600 17 | 18 | #define FILE_TYPE_COUNT 10 19 | 20 | typedef struct _mime_type { 21 | char *content_type; 22 | char *exts[FILE_TYPE_COUNT]; 23 | } mime_type_t; 24 | 25 | mime_type_t standard_types[] = { 26 | {"text/html", {"html", "htm", "shtml", NULL}}, 27 | {"text/css", {"css", NULL}}, 28 | {"text/xml", {"xml", NULL}}, 29 | {"text/plain", {"txt", NULL}}, 30 | {"image/png", {"png", NULL}}, 31 | {"image/gif", {"gif", NULL}}, 32 | {"image/tiff", {"tif", "tiff", NULL}}, 33 | {"image/jpeg", {"jpg", "jpeg", NULL}}, 34 | {"image/x-ms-bmp", {"bmp", NULL}}, 35 | {"image/svg+xml", {"svg", "svgz", NULL}}, 36 | {"application/x-javascript", {"js", NULL}} 37 | }; 38 | 39 | static struct hsearch_data std_mime_type_hash; 40 | 41 | /* 42 | * dir_filter uses this flag to decide whether to filter out 43 | * hidden files (dot files). Since C does not have closure, we 44 | * have to use a global variable to hack it. 45 | * It is set to the value of a 46 | * mod_static_conf_t.show_hidden_file before listing the file. 47 | */ 48 | static int show_hidden_file = 0; 49 | 50 | static void *mod_static_conf_create(json_value *conf_value); 51 | static void mod_static_conf_destroy(void *conf); 52 | 53 | static int static_file_write_content(request_t *req, 54 | response_t *resp, 55 | handler_ctx_t *ctx); 56 | static int static_file_cleanup(request_t *req, 57 | response_t *resp, 58 | handler_ctx_t *ctx); 59 | 60 | static int static_file_handle_error(response_t *resp, int fd); 61 | static void handle_content_type(response_t *resp, const char *filepath); 62 | static int handle_cache(request_t *req, response_t *resp, 63 | const struct stat *st, const mod_static_conf_t *conf); 64 | static int handle_range(request_t *req, response_t *resp, 65 | size_t *offset, size_t *size); 66 | static char* generate_etag(const struct stat *st); 67 | static int try_open_file(const char *path, int *fd, struct stat *st); 68 | static int static_file_listdir(response_t *resp, const char *path, 69 | const char *realpath); 70 | static int dir_filter(const struct dirent *ent); 71 | 72 | /* Module descriptor */ 73 | module_t mod_static = { 74 | "static", 75 | mod_static_init, 76 | mod_static_conf_create, 77 | mod_static_conf_destroy, 78 | static_file_handle 79 | }; 80 | 81 | int mod_static_init() { 82 | int i; 83 | int j; 84 | size_t size; 85 | ENTRY item, *ret; 86 | char **ext = NULL; 87 | 88 | size = sizeof(standard_types) / sizeof(standard_types[0]); 89 | 90 | bzero(&std_mime_type_hash, sizeof(struct hsearch_data)); 91 | if (hcreate_r(size * 2, &std_mime_type_hash) == 0) { 92 | error("Error creating standard MIME type hash"); 93 | return -1; 94 | } 95 | for (i = 0; i < size; i++) { 96 | for (ext = standard_types[i].exts, j = 0; 97 | *ext != NULL && j < FILE_TYPE_COUNT; 98 | ext++, j++) { 99 | item.key = *ext; 100 | item.data = standard_types[i].content_type; 101 | debug("Registering standard MIME type %s:%s", 102 | *ext, standard_types[i].content_type); 103 | if (hsearch_r(item, ENTER, &ret, &std_mime_type_hash) == 0) { 104 | error("Error entering standard MIME type"); 105 | } 106 | } 107 | } 108 | return 0; 109 | } 110 | 111 | static void *mod_static_conf_create(json_value *conf_value) { 112 | mod_static_conf_t *conf = NULL; 113 | DECLARE_CONF_VARIABLES() 114 | 115 | conf = (mod_static_conf_t*) calloc(1, sizeof(mod_static_conf_t)); 116 | if (conf == NULL) { 117 | return NULL; 118 | } 119 | 120 | BEGIN_CONF_HANDLE(conf_value) 121 | ON_STRING_CONF("root", conf->root) 122 | ON_STRING_CONF("index", conf->index) 123 | ON_BOOLEAN_CONF("list_dir", conf->enable_list_dir) 124 | ON_INTEGER_CONF("expires", conf->expire_hours) 125 | ON_BOOLEAN_CONF("etag", conf->enable_etag) 126 | ON_BOOLEAN_CONF("range_request", conf->enable_range_req) 127 | END_CONF_HANDLE() 128 | return conf; 129 | } 130 | 131 | static void mod_static_conf_destroy(void *conf) { 132 | free(conf); 133 | } 134 | 135 | static int try_open_file(const char *path, int *fdptr, struct stat *st) { 136 | int fd, res; 137 | 138 | debug("Try opening file: %s", path); 139 | res = stat(path, st); 140 | if (res < 0) { 141 | return -1; 142 | } 143 | 144 | if (S_ISDIR(st->st_mode)) { 145 | return 1; 146 | } else if (S_ISREG(st->st_mode)) { 147 | fd = open(path, O_RDONLY); 148 | if (fd < 0) { 149 | return -1; 150 | } 151 | *fdptr = fd; 152 | return 0; 153 | } else { 154 | return -1; 155 | } 156 | } 157 | 158 | const char *listdir_header = 159 | "" 160 | "Index of %s" 161 | "" 162 | "

Index of %s

" 163 | "
" 164 | "
";
165 | 
166 | const char *listdir_file = "%s\r\n";
167 | const char *listdir_dir = "%s/\r\n";
168 | 
169 | const char *listdir_footer =
170 |     "
" 171 | "
" 172 | "

Powered by " 173 | "%s" 174 | "

" 175 | "" 176 | ""; 177 | 178 | static int static_file_listdir(response_t *resp, const char *path, 179 | const char *realpath) { 180 | struct dirent **ent_list, *ent; 181 | int ent_len; 182 | char buf[2048]; 183 | int pos = 0, i; 184 | 185 | debug("Opening dir: %s", realpath); 186 | if ((ent_len = scandir(realpath, &ent_list, dir_filter, versionsort)) < 0) { 187 | return static_file_handle_error(resp, -1); 188 | } 189 | resp->status = STATUS_OK; 190 | resp->connection = CONN_CLOSE; 191 | response_set_header(resp, "Content-Type", "text/html; charset=UTF-8"); 192 | response_send_headers(resp, NULL); 193 | pos += snprintf(buf, 2048, listdir_header, path, path); 194 | for (i = 0; i < ent_len; i++) { 195 | ent = ent_list[i]; 196 | pos += snprintf(buf + pos, 2048 - pos, 197 | ent->d_type == DT_DIR ? listdir_dir : listdir_file, 198 | ent->d_name, ent->d_name); 199 | if (2048 - pos < 255) { 200 | response_write(resp, buf, pos, NULL); 201 | pos = 0; 202 | } 203 | free(ent); 204 | } 205 | free(ent_list); 206 | pos += snprintf(buf + pos, 2048 - pos, listdir_footer, _BREEZE_NAME); 207 | response_write(resp, buf, pos, NULL); 208 | 209 | return HANDLER_DONE; 210 | } 211 | 212 | static int dir_filter(const struct dirent *ent) { 213 | const char *name = ent->d_name; 214 | if (name[0] == '.') { 215 | if (name[1] == '\0') 216 | return 0; // Skip "." 217 | else if (name[1] == '.' && name[2] == '\0') 218 | return 1; // Show ".." for parent dir 219 | else if (!show_hidden_file) 220 | return 0; 221 | } 222 | return 1; 223 | } 224 | 225 | int static_file_handle(request_t *req, response_t *resp, 226 | handler_ctx_t *ctx) { 227 | mod_static_conf_t *conf; 228 | char path[2048]; 229 | int fd = -1, res, use_301; 230 | struct stat st; 231 | size_t len, pathlen, filesize, fileoffset; 232 | ctx_state_t val; 233 | 234 | conf = (mod_static_conf_t*) ctx->conf; 235 | len = strlen(conf->root); 236 | strncpy(path, conf->root, 2048); 237 | if (path[len - 1] == '/') { 238 | path[len - 1] = '\0'; 239 | } 240 | if (req->path[0] != '/') { 241 | return response_send_status(resp, STATUS_BAD_REQUEST); 242 | } 243 | strncat(path, req->path, 2048 - len); 244 | debug("Request path: %s, real file path: %s", req->path, path); 245 | res = try_open_file(path, &fd, &st); 246 | if (res < 0) { 247 | return static_file_handle_error(resp, fd); 248 | } else if (res > 0) { // Is a directory, try index files. 249 | pathlen = strlen(path); 250 | use_301 = 0; 251 | if (path[pathlen - 1] != '/') { 252 | path[pathlen] = '/'; 253 | path[pathlen + 1] = '\0'; 254 | pathlen++; 255 | use_301 = 1; 256 | } 257 | //for (i = 0; i < 10 && res != 0 && conf->index[i] != NULL; i++) { 258 | // path[pathlen] = '\0'; 259 | // strncat(path, conf->index[i], 2048 - pathlen); 260 | // res = try_open_file(path, &fd, &st); 261 | //} 262 | path[pathlen] = '\0'; 263 | strncat(path, conf->index, 2048 - pathlen); 264 | res = try_open_file(path, &fd, &st); 265 | path[pathlen] = '\0'; 266 | if (res != 0) { 267 | if (conf->enable_list_dir) { 268 | if (use_301) { 269 | // TODO Support HTTPS 270 | snprintf(path, 2048, "http://%s%s/", req->host, req->path); 271 | response_set_header(resp, "Location", path); 272 | resp->status = STATUS_MOVED; 273 | resp->content_length = 0; 274 | response_send_headers(resp, NULL); 275 | return HANDLER_DONE; 276 | } 277 | show_hidden_file = conf->show_hidden_file; 278 | return static_file_listdir(resp, req->path, path); 279 | } else { 280 | return static_file_handle_error(resp, fd); 281 | } 282 | } 283 | } 284 | 285 | fileoffset = 0; 286 | filesize = st.st_size; 287 | res = handle_range(req, resp, &fileoffset, &filesize); 288 | if (res < 0) { 289 | resp->status = STATUS_OK; 290 | } else if (res == 0) { 291 | resp->status = STATUS_PARTIAL_CONTENT; 292 | } else { 293 | return response_send_status(resp, STATUS_RANGE_NOT_SATISFIABLE); 294 | } 295 | 296 | resp->content_length = filesize; 297 | handle_content_type(resp, path); 298 | if (handle_cache(req, resp, &st, conf)) { 299 | return response_send_status(resp, STATUS_NOT_MODIFIED); 300 | } 301 | 302 | val.as_int = fd; 303 | context_push(ctx, val); 304 | val.as_long = fileoffset; 305 | context_push(ctx, val); 306 | val.as_long = filesize; 307 | context_push(ctx, val); 308 | debug("sending headers"); 309 | response_send_headers(resp, static_file_write_content); 310 | return HANDLER_UNFISHED; 311 | } 312 | 313 | /* 314 | * Return values: 315 | * 0: valid range request 316 | * 1: range not satisfiable (should return 416) 317 | * -1: syntactically invalid range (ignore, return full content) 318 | */ 319 | static int handle_range(request_t *req, response_t *resp, 320 | size_t *offset, size_t *size) { 321 | const char *range_spec; 322 | char buf[100], *pos; 323 | size_t len, total_size = *size, off, sz, end; 324 | int idx; 325 | 326 | range_spec = request_get_header(req, "range"); 327 | if (range_spec == NULL) { 328 | return -1; 329 | } 330 | len = strlen(range_spec); 331 | if (len < 8) { 332 | return -1; 333 | } 334 | if (strstr(range_spec, "bytes=") != range_spec) { 335 | error("Only byte ranges are supported(error range:%s)", range_spec); 336 | return -1; 337 | } 338 | strncpy(buf, range_spec + 6, 100); 339 | len = strlen(buf); 340 | if (index(buf, ',') != NULL) { 341 | error("Multiple ranges are not supported."); 342 | return -1; 343 | } 344 | pos = index(buf, '-'); 345 | if (pos == NULL) { 346 | error("Invalid range spec: %s.", range_spec); 347 | return -1; 348 | } 349 | idx = pos - buf; 350 | if (idx == 0) { 351 | sz = atol(buf + 1); 352 | end = total_size; 353 | off = total_size - sz; 354 | } else if (idx == len - 1) { 355 | buf[idx] = '\0'; 356 | off = atol(buf); 357 | end = total_size; 358 | sz = total_size - off; 359 | } else { 360 | buf[idx] = '\0'; 361 | off = atol(buf); 362 | end = atol(buf + idx + 1); 363 | sz = end - off + 1; 364 | } 365 | 366 | if (end < off) 367 | return -1; 368 | if (off >= total_size || sz > total_size) 369 | return 1; 370 | 371 | response_set_header_printf(resp, "Content-Range", "bytes %ld-%ld/%ld", 372 | off, off + sz - 1, total_size); 373 | *offset = off; 374 | *size = sz; 375 | return 0; 376 | } 377 | 378 | static void handle_content_type(response_t *resp, const char *filepath) { 379 | char *content_type = NULL, ext[20]; 380 | int dot_pos = -1; 381 | int i; 382 | size_t len = strlen(filepath); 383 | ENTRY item, *ret; 384 | 385 | for (i = len - 1; i > 0; i--) { 386 | if (filepath[i] == '.') { 387 | dot_pos = i; 388 | break; 389 | } 390 | } 391 | 392 | if (dot_pos < 0) { 393 | // No '.' found in the file name (no extension part) 394 | return; 395 | } 396 | 397 | strncpy(ext, filepath + 1 + dot_pos, 20); 398 | strlowercase(ext, ext, 20); 399 | debug("File extension: %s", ext); 400 | 401 | item.key = ext; 402 | if (hsearch_r(item, FIND, &ret, &std_mime_type_hash) == 0) { 403 | return; 404 | } 405 | content_type = (char*) ret->data; 406 | if (content_type != NULL) { 407 | debug("Content type: %s", content_type); 408 | response_set_header(resp, "Content-Type", content_type); 409 | } 410 | } 411 | 412 | static int handle_cache(request_t *req, response_t *resp, 413 | const struct stat *st, const mod_static_conf_t *conf) { 414 | const char *if_mod_since, *if_none_match; 415 | char *etag; 416 | time_t mtime, req_mtime; 417 | int not_modified = 0; 418 | char *buf; 419 | char *cache_control; 420 | 421 | mtime = st->st_mtime; 422 | if_mod_since = request_get_header(req, "if-modified-since"); 423 | if (if_mod_since != NULL && 424 | parse_http_date(if_mod_since, &req_mtime) == 0 && 425 | req_mtime == mtime) { 426 | debug("Resource not modified"); 427 | not_modified = 1; 428 | } 429 | buf = response_alloc(resp, 32); 430 | format_http_date(&mtime, buf, 32); 431 | response_set_header(resp, "Last-Modified", buf); 432 | 433 | if (conf->enable_etag) { 434 | etag = generate_etag(st); 435 | if (not_modified) { 436 | if_none_match = request_get_header(req, "if-none-match"); 437 | if (if_none_match == NULL || 438 | strcmp(etag, if_none_match) != 0) { 439 | not_modified = 0; 440 | } 441 | } 442 | response_set_header(resp, "ETag", etag); 443 | } 444 | 445 | if (conf->expire_hours >= 0) { 446 | buf = response_alloc(resp, 32); 447 | mtime += conf->expire_hours * 3600; 448 | format_http_date(&mtime, buf, 32); 449 | response_set_header(resp, "Expires", buf); 450 | cache_control = response_alloc(resp, 20); 451 | snprintf(cache_control, 20, "max-age=%d", conf->expire_hours * 3600); 452 | } else { 453 | cache_control = "no-cache"; 454 | } 455 | response_set_header(resp, "Cache-Control", cache_control); 456 | return not_modified; 457 | } 458 | 459 | static char* generate_etag(const struct stat *st) { 460 | char tag_buf[128]; 461 | snprintf(tag_buf, 128, "etag-%ld-%zu", st->st_mtime, st->st_size); 462 | return crypt(tag_buf, "$1$breeze") + 10; // Skip the $id$salt part 463 | } 464 | 465 | static int static_file_write_content(request_t *req, response_t *resp, handler_ctx_t *ctx) { 466 | int fd; 467 | size_t size, offset; 468 | 469 | size = context_pop(ctx)->as_long; 470 | offset = context_pop(ctx)->as_long; 471 | fd = context_peek(ctx)->as_int; 472 | debug("writing file"); 473 | if (response_send_file(resp, fd, offset, size, static_file_cleanup) < 0) { 474 | error("Error sending file"); 475 | return response_send_status(resp, STATUS_NOT_FOUND); 476 | } 477 | return HANDLER_UNFISHED; 478 | } 479 | 480 | static int static_file_cleanup(request_t *req, response_t *resp, handler_ctx_t *ctx) { 481 | int fd; 482 | 483 | debug("cleaning up"); 484 | fd = context_pop(ctx)->as_int; 485 | close(fd); 486 | return HANDLER_DONE; 487 | } 488 | 489 | static int static_file_handle_error(response_t *resp, int fd) { 490 | int err = errno; 491 | if (fd > 0) 492 | (void) close(fd); // Don't care the failure 493 | switch(err) { 494 | case EACCES: 495 | case EISDIR: 496 | return response_send_status(resp, STATUS_FORBIDDEN); 497 | case ENOENT: 498 | default: 499 | return response_send_status(resp, STATUS_NOT_FOUND); 500 | } 501 | } 502 | 503 | 504 | -------------------------------------------------------------------------------- /src/iostream.c: -------------------------------------------------------------------------------- 1 | #include "iostream.h" 2 | #include "ioloop.h" 3 | #include "buffer.h" 4 | #include "log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | enum READ_OP_TYPES { 18 | READ_BYTES = 1, 19 | READ_UNTIL = 2 20 | }; 21 | 22 | enum STREAM_STATE { 23 | NORMAL = 1, 24 | CLOSED = 2 25 | }; 26 | 27 | enum WRITE_STATE { 28 | NOT_WRITING, 29 | WRITE_BUFFER, 30 | SEND_FILE 31 | }; 32 | 33 | #define is_reading(stream) ((stream)->read_callback != NULL) 34 | #define is_writing(stream) ((stream)->write_callback != NULL) 35 | #define is_closed(stream) ((stream)->state == CLOSED) 36 | 37 | #define check_reading(stream) \ 38 | if (is_reading(stream)) { \ 39 | return -1; \ 40 | } 41 | 42 | #define check_writing(stream) \ 43 | if (is_writing(stream)) { \ 44 | return -1; \ 45 | } 46 | 47 | static void _handle_io_events(ioloop_t *loop, int fd, unsigned int events, void *args); 48 | static void _handle_error(iostream_t *stream, unsigned int events); 49 | static int _handle_read(iostream_t *stream); 50 | static int _handle_write(iostream_t *stream); 51 | static int _handle_sendfile(iostream_t *stream); 52 | static int _add_event(iostream_t *stream, unsigned int events); 53 | 54 | static ssize_t _read_from_socket(iostream_t *stream); 55 | static int _read_from_buffer(iostream_t *stream); 56 | static ssize_t _write_to_buffer(iostream_t *stream, void *data, size_t len); 57 | static int _write_to_socket(iostream_t *stream); 58 | static ssize_t _write_to_socket_direct(iostream_t *stream, void *data, size_t len); 59 | 60 | static void _finish_stream_callback(ioloop_t *loop, void *args); 61 | static void _finish_read_callback(ioloop_t *loop, void *args); 62 | static void _finish_write_callback(ioloop_t *loop, void *args); 63 | static void _close_callback(ioloop_t *loop, void *args); 64 | static void _destroy_callback(ioloop_t *loop, void *args); 65 | 66 | static void _stream_consumer_func(void *data, size_t len, void *args); 67 | 68 | iostream_t *iostream_create(ioloop_t *loop, 69 | int sockfd, 70 | size_t read_buf_capacity, 71 | size_t write_buf_capacity, 72 | void *user_data) { 73 | iostream_t *stream; 74 | buffer_t *in_buf = NULL, *out_buf = NULL; 75 | 76 | stream = (iostream_t*) calloc(1, sizeof(iostream_t)); 77 | if (stream == NULL) { 78 | error("Error allocating memory for IO stream"); 79 | goto error; 80 | } 81 | bzero(stream, sizeof(iostream_t)); 82 | 83 | in_buf = buffer_create(read_buf_capacity); 84 | if (in_buf == NULL ) { 85 | error("Error creating read buffer"); 86 | goto error; 87 | } 88 | out_buf = buffer_create(write_buf_capacity); 89 | if (out_buf == NULL) { 90 | error("Error creating write buffer"); 91 | goto error; 92 | } 93 | 94 | stream->events = EPOLLERR; 95 | stream->write_buf = out_buf; 96 | stream->write_buf_cap = write_buf_capacity; 97 | stream->read_buf = in_buf; 98 | stream->read_buf_cap = read_buf_capacity; 99 | stream->fd = sockfd; 100 | stream->state = NORMAL; 101 | stream->ioloop = loop; 102 | stream->read_callback = NULL; 103 | stream->write_callback = NULL; 104 | stream->close_callback = NULL; 105 | stream->error_callback = NULL; 106 | stream->sendfile_fd = -1; 107 | stream->user_data = user_data; 108 | 109 | if (ioloop_add_handler(stream->ioloop, 110 | stream->fd, 111 | stream->events, 112 | _handle_io_events, 113 | stream) < 0) { 114 | error("Error add EPOLLERR event"); 115 | goto error; 116 | } 117 | 118 | return stream; 119 | 120 | error: 121 | if (in_buf != NULL) 122 | buffer_destroy(in_buf); 123 | if (out_buf != NULL) 124 | buffer_destroy(out_buf); 125 | free(stream); 126 | return NULL; 127 | } 128 | 129 | int iostream_close(iostream_t *stream) { 130 | if (is_closed(stream)) { 131 | return -1; 132 | } 133 | stream->state = CLOSED; 134 | // Defer the close action to next loop, because there may be 135 | // pending read/write operations. 136 | ioloop_add_callback(stream->ioloop, _close_callback, stream); 137 | return 0; 138 | } 139 | 140 | static void _close_callback(ioloop_t *loop, void *args) { 141 | iostream_t *stream = (iostream_t*) args; 142 | ioloop_remove_handler(stream->ioloop, stream->fd); 143 | stream->close_callback(stream); 144 | close(stream->fd); 145 | // Defer the destroy action to next loop, in case there are 146 | // pending callbacks of this stream. 147 | ioloop_add_callback(stream->ioloop, _destroy_callback, stream); 148 | } 149 | 150 | static void _destroy_callback(ioloop_t *loop, void *args) { 151 | iostream_t *stream = (iostream_t*) args; 152 | debug("IO stream(fd:%d) destroyed.", stream->fd); 153 | iostream_destroy(stream); 154 | } 155 | 156 | int iostream_destroy(iostream_t *stream) { 157 | buffer_destroy(stream->read_buf); 158 | buffer_destroy(stream->write_buf); 159 | free(stream); 160 | return 0; 161 | } 162 | 163 | int iostream_read_bytes(iostream_t *stream, 164 | size_t sz, 165 | read_handler callback, 166 | read_handler stream_callback) { 167 | check_reading(stream); 168 | if (sz == 0) { 169 | return -1; 170 | } 171 | stream->read_callback = callback; 172 | stream->stream_callback = stream_callback; 173 | stream->read_bytes = sz; 174 | stream->read_type = READ_BYTES; 175 | for (;;) { 176 | if (_read_from_buffer(stream)) { 177 | return 0; 178 | } 179 | if (is_closed(stream)) { 180 | return -1; 181 | } 182 | if (_read_from_socket(stream) == 0) { 183 | break; 184 | } 185 | } 186 | _add_event(stream, EPOLLIN); 187 | return 0; 188 | } 189 | 190 | int iostream_read_until(iostream_t *stream, char *delimiter, read_handler callback) { 191 | check_reading(stream); 192 | assert(*delimiter != '\0'); 193 | stream->read_callback = callback; 194 | stream->stream_callback = NULL; 195 | stream->read_delimiter = delimiter; 196 | stream->read_type = READ_UNTIL; 197 | for (;;) { 198 | if (_read_from_buffer(stream)) { 199 | return 0; 200 | } 201 | if (is_closed(stream)) { 202 | return -1; 203 | } 204 | if (_read_from_socket(stream) == 0) { 205 | break; 206 | } 207 | } 208 | _add_event(stream, EPOLLIN); 209 | return 0; 210 | } 211 | 212 | int iostream_write(iostream_t *stream, void *data, size_t len, write_handler callback) { 213 | ssize_t n; 214 | // Allow appending data to existing writing action 215 | if (is_writing(stream) && callback != stream->write_callback) { 216 | return -1; 217 | } 218 | 219 | if (len == 0) { 220 | return -1; 221 | } 222 | 223 | stream->write_callback = callback; 224 | stream->write_state = WRITE_BUFFER; 225 | n = _write_to_socket_direct(stream, data, len); 226 | if (n < 0) { 227 | return -1; 228 | } else if (n == len) { 229 | // Lucky! All the data are written directly, skipping the buffer completely. 230 | return 0; 231 | } 232 | 233 | if (_write_to_buffer(stream, (char*)data + n, len - n) < 0) { 234 | return -1; 235 | } 236 | 237 | // Try to write to the socket 238 | if (_write_to_socket(stream) > 0) { 239 | return 0; 240 | } 241 | _add_event(stream, EPOLLOUT); 242 | return 0; 243 | } 244 | 245 | int iostream_sendfile(iostream_t *stream, int in_fd, 246 | size_t offset, size_t len, 247 | write_handler callback) { 248 | struct stat st; 249 | 250 | check_writing(stream); 251 | if (len == 0) { 252 | return -1; 253 | } 254 | if (fstat(in_fd, &st) < 0) { 255 | error("The file to send is not valid"); 256 | return -2; 257 | } 258 | 259 | switch (st.st_mode & S_IFMT) { 260 | case S_IFREG: 261 | case S_IFLNK: 262 | break; 263 | 264 | default: 265 | error("Unsupported file type: %d", st.st_mode); 266 | return -3; 267 | } 268 | stream->sendfile_fd = in_fd; 269 | stream->sendfile_len = len; 270 | stream->sendfile_offset = offset; 271 | stream->write_state = SEND_FILE; 272 | stream->write_callback = callback; 273 | 274 | if(_handle_sendfile(stream)) { 275 | return 0; 276 | } 277 | _add_event(stream, EPOLLOUT); 278 | return 0; 279 | } 280 | 281 | int iostream_set_error_handler(iostream_t *stream, error_handler callback) { 282 | stream->error_callback = callback; 283 | return 0; 284 | } 285 | 286 | int iostream_set_close_handler(iostream_t *stream, close_handler callback) { 287 | stream->close_callback = callback; 288 | return 0; 289 | } 290 | 291 | static void _handle_error(iostream_t *stream, unsigned int events) { 292 | error_handler err_cb = stream->error_callback; 293 | if (err_cb != NULL) 294 | stream->error_callback(stream, events); 295 | iostream_close(stream); 296 | } 297 | 298 | static void _handle_io_events(ioloop_t *loop, 299 | int fd, 300 | unsigned int events, 301 | void *args) { 302 | iostream_t *stream = (iostream_t*) args; 303 | 304 | if (events & EPOLLIN) { 305 | if (_handle_read(stream)) { 306 | stream->events &= ~EPOLLIN; 307 | } 308 | } 309 | if (events & EPOLLOUT) { 310 | if (_handle_write(stream)) { 311 | stream->events &= ~EPOLLOUT; 312 | } 313 | } 314 | if (events & EPOLLERR) { 315 | _handle_error(stream, events); 316 | return; 317 | } 318 | if (events & EPOLLHUP) { 319 | iostream_close(stream); 320 | return; 321 | } 322 | 323 | if (is_closed(stream)) { 324 | error("Stream closed"); 325 | return; 326 | } 327 | 328 | ioloop_update_handler(stream->ioloop, stream->fd, stream->events); 329 | } 330 | 331 | static int _handle_read(iostream_t *stream) { 332 | if (!is_reading(stream)) { 333 | return 0; 334 | } 335 | _read_from_socket(stream); 336 | return _read_from_buffer(stream); 337 | } 338 | 339 | static int _handle_write(iostream_t *stream) { 340 | switch (stream->write_state) { 341 | case WRITE_BUFFER: 342 | return _write_to_socket(stream); 343 | 344 | case SEND_FILE: 345 | return _handle_sendfile(stream); 346 | 347 | default: 348 | return -1; 349 | } 350 | } 351 | 352 | static int _handle_sendfile(iostream_t *stream) { 353 | ssize_t sz; 354 | 355 | sz = sendfile(stream->fd, stream->sendfile_fd, 356 | &stream->sendfile_offset, stream->sendfile_len); 357 | 358 | if (sz < 0) { 359 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 360 | return 0; 361 | } else { 362 | iostream_close(stream); 363 | return -1; 364 | } 365 | } else if (sz == 0 && stream->sendfile_len > 0) { 366 | // The lengh maybe longer than the actual available size. In 367 | // this case finish the write immediately. 368 | stream->sendfile_offset = 0; 369 | ioloop_add_callback(stream->ioloop, _finish_write_callback, stream); 370 | return 1; 371 | } 372 | 373 | stream->sendfile_len -= sz; 374 | 375 | if (stream->sendfile_len == 0) { 376 | stream->sendfile_offset = 0; 377 | ioloop_add_callback(stream->ioloop, _finish_write_callback, stream); 378 | return 1; 379 | } 380 | 381 | return 0; 382 | } 383 | 384 | static int _add_event(iostream_t *stream, unsigned int event) { 385 | if ((stream->events & event) == 0) { 386 | stream->events |= event; 387 | return ioloop_update_handler(stream->ioloop, stream->fd, stream->events); 388 | } 389 | return -1; 390 | } 391 | 392 | #define READ_SIZE 1024 393 | 394 | static ssize_t _read_from_socket(iostream_t *stream) { 395 | ssize_t n; 396 | n = buffer_fill(stream->read_buf, stream->fd); 397 | if (n < 0) { 398 | iostream_close(stream); 399 | return -1; 400 | } 401 | stream->read_buf_size += n; 402 | return n; 403 | } 404 | 405 | 406 | #define LOCAL_BUFSIZE 4096 407 | 408 | static int _read_from_buffer(iostream_t *stream) { 409 | int res = 0, idx; 410 | 411 | switch(stream->read_type) { 412 | case READ_BYTES: 413 | if (stream->stream_callback != NULL) { 414 | // Streaming mode, offer data 415 | if (stream->read_bytes <= 0) { 416 | res = 1; 417 | } else { 418 | ioloop_add_callback(stream->ioloop, _finish_stream_callback, stream); 419 | } 420 | } else if (stream->read_buf_size >= stream->read_bytes 421 | || buffer_is_full(stream->read_buf) 422 | || (stream->state == CLOSED && stream->read_buf_size > 0)) { 423 | ioloop_add_callback(stream->ioloop, _finish_read_callback, stream); 424 | res = 1; 425 | } 426 | break; 427 | 428 | case READ_UNTIL: 429 | idx = buffer_locate(stream->read_buf, stream->read_delimiter); 430 | if (idx > 0 431 | || buffer_is_full(stream->read_buf) 432 | || (stream->state == CLOSED && stream->read_buf_size > 0)) { 433 | stream->read_bytes = idx > 0 434 | ? (idx + strlen(stream->read_delimiter)) 435 | : stream->read_buf_size; 436 | ioloop_add_callback(stream->ioloop, _finish_read_callback, stream); 437 | res = 1; 438 | } 439 | break; 440 | } 441 | 442 | return res; 443 | } 444 | 445 | 446 | static void _finish_stream_callback(ioloop_t *loop, void *args) { 447 | iostream_t *stream = (iostream_t*) args; 448 | read_handler callback = stream->read_callback; 449 | 450 | buffer_consume(stream->read_buf, stream->read_bytes, _stream_consumer_func, stream); 451 | if (stream->read_bytes <= 0) { 452 | stream->read_callback = NULL; 453 | stream->read_bytes = 0; 454 | // When streaming ends, call the read_callback with NULL to indicate the finish. 455 | callback(stream, NULL, 0); 456 | } 457 | } 458 | 459 | static void _finish_read_callback(ioloop_t *loop, void *args) { 460 | char local_buf[LOCAL_BUFSIZE]; 461 | iostream_t *stream = (iostream_t*) args; 462 | read_handler callback = stream->read_callback; 463 | size_t n; 464 | 465 | // Normal mode, call read callback 466 | n = buffer_get(stream->read_buf, stream->read_bytes, local_buf, LOCAL_BUFSIZE); 467 | // assert(n == stream->read_bytes); 468 | callback = stream->read_callback; 469 | stream->read_callback = NULL; 470 | stream->read_bytes = 0; 471 | stream->read_buf_size -= n; 472 | callback(stream, local_buf, n); 473 | } 474 | 475 | static void _finish_write_callback(ioloop_t *loop, void *args) { 476 | iostream_t *stream = (iostream_t*) args; 477 | write_handler callback = stream->write_callback; 478 | 479 | stream->write_callback = NULL; 480 | stream->write_state = NOT_WRITING; 481 | if (callback != NULL) { 482 | callback(stream); 483 | } 484 | } 485 | 486 | static void _stream_consumer_func(void *data, size_t len, void *args) { 487 | iostream_t *stream = (iostream_t*) args; 488 | stream->read_bytes -= len; 489 | stream->read_buf_size -= len; 490 | stream->stream_callback(stream, data, len); 491 | } 492 | 493 | static ssize_t _write_to_buffer(iostream_t *stream, void *data, size_t len) { 494 | if (len > stream->write_buf_cap - stream->write_buf_size) { 495 | return -1; 496 | } 497 | if (buffer_put(stream->write_buf, data, len) < 0) { 498 | return -1; 499 | } 500 | stream->write_buf_size += len; 501 | return 0; 502 | } 503 | 504 | #define WRITE_CHUNK_SIZE 1024 505 | 506 | static int _write_to_socket(iostream_t *stream) { 507 | ssize_t n; 508 | 509 | n = buffer_flush(stream->write_buf, stream->fd); 510 | if (n < 0) { 511 | iostream_close(stream); 512 | return -1; 513 | } 514 | stream->write_buf_size -= n; 515 | 516 | if (stream->write_buf_size == 0) { 517 | ioloop_add_callback(stream->ioloop, _finish_write_callback, stream); 518 | return 1; 519 | } else { 520 | return 0; 521 | } 522 | } 523 | 524 | static ssize_t _write_to_socket_direct(iostream_t *stream, void *data, size_t len) { 525 | ssize_t n; 526 | 527 | if (stream->write_buf_size > 0) { 528 | // If there is data in the buffer, we could not write to socket directly. 529 | return 0; 530 | } 531 | 532 | n = write(stream->fd, data, len); 533 | if (n < 0) { 534 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 535 | return 0; 536 | } else { 537 | iostream_close(stream); 538 | return -1; 539 | } 540 | } 541 | 542 | if (n == len) { 543 | // If we could write all the data once, call the callback function now. 544 | ioloop_add_callback(stream->ioloop, _finish_write_callback, stream); 545 | } 546 | 547 | return n; 548 | } 549 | 550 | -------------------------------------------------------------------------------- /src/http.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "http.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef enum _parser_state { 10 | PARSER_STATE_BAD_REQUEST = -1, 11 | PARSER_STATE_COMPLETE = 0, 12 | PARSER_STATE_METHOD, 13 | PARSER_STATE_PATH, 14 | PARSER_STATE_QUERY_STR, 15 | PARSER_STATE_VERSION, 16 | PARSER_STATE_HEADER_NAME, 17 | PARSER_STATE_HEADER_COLON, 18 | PARSER_STATE_HEADER_VALUE, 19 | PARSER_STATE_HEADER_CR, 20 | PARSER_STATE_HEADER_LF, 21 | PARSER_STATE_HEADER_COMPLETE_CR, 22 | } parser_state_e; 23 | 24 | 25 | typedef void (*http_header_callback)(request_t *req, http_header_t *header); 26 | 27 | typedef struct _header_command { 28 | char *lower_name; 29 | http_header_callback callback; 30 | } header_command_t; 31 | 32 | static struct hsearch_data std_headers_hash; 33 | static int std_headers_hash_initialized = 0; 34 | 35 | static http_version_e _resolve_http_version(const char* version_str); 36 | static int init_std_headers_hash(); 37 | static void handle_common_header(request_t *req, int header_index); 38 | static void set_common_headers(response_t *resp); 39 | static void on_write_finished(iostream_t *stream); 40 | 41 | inline static const char* str_http_ver(http_version_e ver) { 42 | switch (ver) { 43 | case HTTP_VERSION_1_1: 44 | return "1.1"; 45 | 46 | case HTTP_VERSION_1_0: 47 | return "1.0"; 48 | 49 | case HTTP_VERSION_0_9: 50 | return "0.9"; 51 | 52 | default: 53 | return "1.1"; 54 | } 55 | } 56 | 57 | request_t* request_create(connection_t *conn) { 58 | request_t *req; 59 | req = (request_t*) calloc(1, sizeof(request_t)); 60 | if (req == NULL) { 61 | return NULL; 62 | } 63 | bzero(req, sizeof(request_t)); 64 | if (hcreate_r(MAX_HEADER_SIZE, &req->_header_hash) == 0) { 65 | error("Error creating header hash table"); 66 | free(req); 67 | return NULL; 68 | } 69 | req->_conn = conn; 70 | return req; 71 | } 72 | 73 | int request_reset(request_t *req) { 74 | connection_t *conn = req->_conn; 75 | hdestroy_r(&req->_header_hash); 76 | bzero(req, sizeof(request_t)); 77 | if (hcreate_r(MAX_HEADER_SIZE, &req->_header_hash) == 0) { 78 | error("Error creating header hash table"); 79 | return -1; 80 | } 81 | req->_conn = conn; 82 | return 0; 83 | } 84 | 85 | int request_destroy(request_t *req) { 86 | hdestroy_r(&req->_header_hash); 87 | free(req); 88 | return 0; 89 | } 90 | 91 | const char* request_get_header(request_t *request, const char *header_name) { 92 | ENTRY item, *ret; 93 | char header_name_lower[64]; 94 | 95 | strlowercase(header_name, header_name_lower, 64); 96 | item.key = header_name_lower; 97 | 98 | if (hsearch_r(item, FIND, &ret, &request->_header_hash) == 0) { 99 | return NULL; 100 | } 101 | 102 | return ((http_header_t*) ret->data)->value; 103 | } 104 | 105 | #define START_NEW_TOKEN(tok, req) \ 106 | (tok = (req)->_buffer + (req)->_buf_idx) 107 | 108 | #define FILL_NEXT_CHAR(req, ch) \ 109 | ((req)->_buffer[req->_buf_idx++] = (ch)) 110 | 111 | #define FINISH_CUR_TOKEN(req) \ 112 | ((req)->_buffer[(req)->_buf_idx++] = '\0' ) 113 | 114 | #define EXPECT_CHAR(state, ch, expected_ch, next_state) \ 115 | if ((ch) == (expected_ch)) { \ 116 | state = (next_state); \ 117 | } else { \ 118 | state = PARSER_STATE_BAD_REQUEST; \ 119 | } 120 | 121 | 122 | int request_parse_headers(request_t *req, 123 | const char *data, 124 | const size_t data_len, 125 | size_t *consumed) { 126 | http_version_e ver; 127 | int i, rc; 128 | char ch; 129 | char *cur_token = req->_buffer; 130 | parser_state_e state = PARSER_STATE_METHOD; 131 | 132 | req->_buffer[0] = '\0'; 133 | req->_buf_idx = 0; 134 | req->header_count = 0; 135 | 136 | if (std_headers_hash_initialized == 0) { 137 | init_std_headers_hash(); 138 | } 139 | 140 | for (i = 0; i < data_len;){ 141 | 142 | if (state == PARSER_STATE_COMPLETE || 143 | state == PARSER_STATE_BAD_REQUEST) { 144 | break; 145 | } 146 | ch = data[i++]; 147 | 148 | switch(state) { 149 | 150 | case PARSER_STATE_METHOD: 151 | if (ch == ' ') { 152 | FINISH_CUR_TOKEN(req); 153 | req->method = cur_token; 154 | state = PARSER_STATE_PATH; 155 | START_NEW_TOKEN(cur_token, req); 156 | } else if (ch < 'A' || ch > 'Z') { 157 | state = PARSER_STATE_BAD_REQUEST; 158 | } else { 159 | FILL_NEXT_CHAR(req, ch); 160 | } 161 | break; 162 | 163 | case PARSER_STATE_PATH: 164 | if (ch == '?') { 165 | FINISH_CUR_TOKEN(req); 166 | req->path = cur_token; 167 | state = PARSER_STATE_QUERY_STR; 168 | START_NEW_TOKEN(cur_token, req); 169 | } else if (ch == ' ') { 170 | FINISH_CUR_TOKEN(req); 171 | req->path = cur_token; 172 | state = PARSER_STATE_VERSION; 173 | START_NEW_TOKEN(cur_token, req); 174 | } else { 175 | FILL_NEXT_CHAR(req, ch); 176 | } 177 | break; 178 | 179 | case PARSER_STATE_QUERY_STR: 180 | if (ch == ' ') { 181 | FINISH_CUR_TOKEN(req); 182 | req->query_str = cur_token; 183 | state = PARSER_STATE_VERSION; 184 | START_NEW_TOKEN(cur_token, req); 185 | } else { 186 | FILL_NEXT_CHAR(req, ch); 187 | } 188 | break; 189 | 190 | case PARSER_STATE_VERSION: 191 | switch (ch) { 192 | // For HTTP part in the request line, e.g. GET / HTTP/1.1 193 | case 'H': 194 | case 'T': 195 | case 'P': 196 | 197 | // Currently only 0.9, 1.0 and 1.1 are supported. 198 | case '0': 199 | case '1': 200 | case '9': 201 | case '.': 202 | FILL_NEXT_CHAR(req, ch); 203 | break; 204 | 205 | case '/': 206 | FINISH_CUR_TOKEN(req); 207 | if (strcmp("HTTP", cur_token) != 0) { 208 | state = PARSER_STATE_BAD_REQUEST; 209 | break; 210 | } 211 | START_NEW_TOKEN(cur_token, req); 212 | break; 213 | 214 | case '\r': 215 | FINISH_CUR_TOKEN(req); 216 | ver = _resolve_http_version(cur_token); 217 | if (ver == HTTP_VERSION_UNKNOW) { 218 | state = PARSER_STATE_BAD_REQUEST; 219 | break; 220 | } 221 | req->version = ver; 222 | state = PARSER_STATE_HEADER_CR; 223 | START_NEW_TOKEN(cur_token, req); 224 | break; 225 | } 226 | break; 227 | 228 | case PARSER_STATE_HEADER_NAME: 229 | if (ch == ':') { 230 | FINISH_CUR_TOKEN(req); 231 | req->headers[req->header_count].name = cur_token; 232 | state = PARSER_STATE_HEADER_COLON; 233 | START_NEW_TOKEN(cur_token, req); 234 | } else { 235 | FILL_NEXT_CHAR(req, ch); 236 | } 237 | break; 238 | 239 | case PARSER_STATE_HEADER_COLON: 240 | EXPECT_CHAR(state, ch, ' ', PARSER_STATE_HEADER_VALUE); 241 | break; 242 | 243 | case PARSER_STATE_HEADER_VALUE: 244 | if (ch == '\r') { 245 | FINISH_CUR_TOKEN(req); 246 | req->headers[req->header_count].value = cur_token; 247 | handle_common_header(req, req->header_count); 248 | req->header_count++; 249 | state = PARSER_STATE_HEADER_CR; 250 | START_NEW_TOKEN(cur_token, req); 251 | } else { 252 | FILL_NEXT_CHAR(req, ch); 253 | } 254 | break; 255 | 256 | case PARSER_STATE_HEADER_CR: 257 | EXPECT_CHAR(state, ch, '\n', PARSER_STATE_HEADER_LF); 258 | break; 259 | 260 | case PARSER_STATE_HEADER_LF: 261 | // Another CR after a header LF, meanning the header end is met. 262 | if (ch == '\r') { 263 | state = PARSER_STATE_HEADER_COMPLETE_CR; 264 | } else { 265 | state = PARSER_STATE_HEADER_NAME; 266 | FILL_NEXT_CHAR(req, ch); 267 | } 268 | break; 269 | 270 | case PARSER_STATE_HEADER_COMPLETE_CR: 271 | EXPECT_CHAR(state, ch, '\n', PARSER_STATE_COMPLETE); 272 | break; 273 | 274 | default: 275 | error("Unexpected state: %d", state); 276 | break; 277 | 278 | } 279 | } 280 | 281 | *consumed = i; 282 | 283 | switch (state) { 284 | case PARSER_STATE_COMPLETE: 285 | rc = STATUS_COMPLETE; 286 | break; 287 | 288 | case PARSER_STATE_BAD_REQUEST: 289 | rc = STATUS_ERROR; 290 | break; 291 | 292 | default: 293 | rc = STATUS_INCOMPLETE; 294 | break; 295 | } 296 | 297 | return rc; 298 | } 299 | 300 | static http_version_e _resolve_http_version(const char* version_str) { 301 | if (strcmp(version_str, "1.1") == 0) { 302 | return HTTP_VERSION_1_1; 303 | } else if (strcmp(version_str, "1.0") == 0) { 304 | return HTTP_VERSION_1_0; 305 | } else if (strcmp(version_str, "0.9") == 0) { 306 | return HTTP_VERSION_0_9; 307 | } 308 | 309 | return HTTP_VERSION_UNKNOW; 310 | } 311 | 312 | static void _handle_content_len(request_t *req, http_header_t *header) { 313 | size_t content_length; 314 | 315 | content_length = (size_t) atol(header->value); 316 | req->content_length = content_length; 317 | } 318 | 319 | static void _handle_host(request_t *req, http_header_t *header) { 320 | req->host = header->value; 321 | } 322 | 323 | static void _handle_connection(request_t *req, http_header_t *header) { 324 | if (strcasecmp("keep-alive", header->value) == 0) { 325 | req->connection = CONN_KEEP_ALIVE; 326 | } else { 327 | req->connection = CONN_CLOSE; 328 | } 329 | } 330 | 331 | static header_command_t std_headers[] = { 332 | { "accept", NULL }, 333 | { "accept-charset", NULL }, 334 | { "accept-datetime", NULL }, 335 | { "accept-encoding", NULL }, 336 | { "accept-language", NULL }, 337 | { "accept-ranges", NULL }, 338 | { "access-control-allow-origin", NULL }, 339 | { "age", NULL }, 340 | { "allow", NULL }, 341 | { "authorization", NULL }, 342 | { "cache-control", NULL }, 343 | { "connection", _handle_connection }, 344 | { "content-disposition", NULL }, 345 | { "content-encoding", NULL }, 346 | { "content-language", NULL }, 347 | { "content-length", _handle_content_len }, 348 | { "content-location", NULL }, 349 | { "content-md5", NULL }, 350 | { "content-range", NULL }, 351 | { "content-security-policy", NULL }, 352 | { "content-type", NULL }, 353 | { "cookie", NULL }, 354 | { "dnt", NULL }, 355 | { "date", NULL }, 356 | { "etag", NULL }, 357 | { "expect", NULL }, 358 | { "expires", NULL }, 359 | { "from", NULL }, 360 | { "front-end-https", NULL }, 361 | { "host", _handle_host }, 362 | { "if-match", NULL }, 363 | { "if-modified-since", NULL }, 364 | { "if-none-match", NULL }, 365 | { "if-range", NULL }, 366 | { "if-unmodified-since", NULL }, 367 | { "last-modified", NULL }, 368 | { "link", NULL }, 369 | { "location", NULL }, 370 | { "max-forwards", NULL }, 371 | { "origin", NULL }, 372 | { "p3p", NULL }, 373 | { "pragma", NULL }, 374 | { "proxy-authenticate", NULL }, 375 | { "proxy-authorization", NULL }, 376 | { "proxy-connection", NULL }, 377 | { "range", NULL }, 378 | { "referer", NULL }, 379 | { "refresh", NULL }, 380 | { "retry-after", NULL }, 381 | { "server", NULL }, 382 | { "set-cookie", NULL }, 383 | { "status", NULL }, 384 | { "strict-transport-security", NULL }, 385 | { "te", NULL }, 386 | { "trailer", NULL }, 387 | { "transfer-encoding", NULL }, 388 | { "upgrade", NULL }, 389 | { "user-agent", NULL }, 390 | { "vary", NULL }, 391 | { "via", NULL }, 392 | { "www-authenticate", NULL }, 393 | { "warning", NULL }, 394 | { "x-att-deviceid", NULL }, 395 | { "x-content-security-policy", NULL }, 396 | { "x-content-type-options", NULL }, 397 | { "x-forwarded-for", NULL }, 398 | { "x-forwarded-proto", NULL }, 399 | { "x-frame-options", NULL }, 400 | { "x-powered-by", NULL }, 401 | { "x-requested-with", NULL }, 402 | { "x-wap-profile", NULL }, 403 | { "x-webkit-csp", NULL }, 404 | { "x-xss-protection", NULL }, 405 | { "x-ua-compatible", NULL }, 406 | }; 407 | 408 | static int init_std_headers_hash() { 409 | int i; 410 | size_t size; 411 | ENTRY item, *ret; 412 | 413 | size = sizeof(std_headers)/sizeof(std_headers[0]); 414 | 415 | bzero(&std_headers_hash, sizeof(struct hsearch_data)); 416 | if (hcreate_r(sizeof(std_headers) * 2, &std_headers_hash) == 0) { 417 | error("Error creating standard headers hash"); 418 | return -1; 419 | } 420 | for (i = 0; i < size; i++) { 421 | item.key = std_headers[i].lower_name; 422 | item.data = std_headers[i].callback; 423 | if (hsearch_r(item, ENTER, &ret, &std_headers_hash) == 0) { 424 | error("Error entering standard header %s to hash", item.key); 425 | } 426 | } 427 | std_headers_hash_initialized = 1; 428 | return 0; 429 | } 430 | 431 | static void handle_common_header(request_t *req, int header_index) { 432 | ENTRY ent, *ret; 433 | http_header_t *header; 434 | char header_lowercase[64]; 435 | http_header_callback callback = NULL; 436 | 437 | header = req->headers + header_index; 438 | strlowercase(header->name, header_lowercase, 64); 439 | ent.key = header_lowercase; 440 | if (hsearch_r(ent, FIND, &ret, &std_headers_hash) == 0) { 441 | return; 442 | } 443 | 444 | callback = (http_header_callback) ret->data; 445 | if (callback != NULL) { 446 | callback(req, header); 447 | } 448 | 449 | ent.key = ret->key; 450 | ent.data = header; 451 | 452 | hsearch_r(ent, ENTER, &ret, &req->_header_hash); 453 | } 454 | 455 | 456 | // HTTP common status codes 457 | 458 | // 1xx informational 459 | http_status_t STATUS_CONTINUE = {100, "Continue"}; 460 | 461 | // 2xx success 462 | http_status_t STATUS_OK = {200, "OK"}; 463 | http_status_t STATUS_CREATED = {201, "Created"}; 464 | http_status_t STATUS_ACCEPTED = {202, "Accepted"}; 465 | http_status_t STATUS_NO_CONTENT = {204, "No Content"}; 466 | http_status_t STATUS_PARTIAL_CONTENT = {206, "Partial Content"}; 467 | 468 | // 3xx redirection 469 | http_status_t STATUS_MOVED = {301, "Moved Permanently"}; 470 | http_status_t STATUS_FOUND = {302, "Found"}; 471 | http_status_t STATUS_SEE_OTHER = {303, "See Other"}; 472 | http_status_t STATUS_NOT_MODIFIED = {304, "Not Modified"}; 473 | 474 | // 4xx client errors 475 | http_status_t STATUS_BAD_REQUEST = {400, "Bad Request"}; 476 | http_status_t STATUS_UNAUTHORIZED = {401, "Unauthorized"}; 477 | http_status_t STATUS_FORBIDDEN = {403, "Forbidden"}; 478 | http_status_t STATUS_NOT_FOUND = {404, "Not Found"}; 479 | http_status_t STATUS_METHOD_NOT_ALLOWED = {405, "Method Not Allowed"}; 480 | http_status_t STATUS_RANGE_NOT_SATISFIABLE = {416, "Range Not Satisfiable"}; 481 | 482 | // 5xx server errors 483 | http_status_t STATUS_INTERNAL_ERROR = {500, "Internal Server Error"}; 484 | http_status_t STATUS_NOT_IMPLEMENTED = {501, "Not Implemented"}; 485 | http_status_t STATUS_BAD_GATEWAY = {502, "Bad Gateway"}; 486 | http_status_t STATUS_SERVICE_UNAVAILABLE = {503, "Service Unavailable"}; 487 | http_status_t STATUS_GATEWAY_TIMEOUT = {504, "Gateway Timeout"}; 488 | 489 | 490 | response_t* response_create(connection_t *conn) { 491 | response_t *resp; 492 | 493 | resp = (response_t*) calloc(1, sizeof(response_t)); 494 | if (resp == NULL) { 495 | return NULL; 496 | } 497 | 498 | bzero(resp, sizeof(response_t)); 499 | if (hcreate_r(MAX_HEADER_SIZE, &resp->_header_hash) == 0) { 500 | error("Error creating header hash table"); 501 | free(resp); 502 | return NULL; 503 | } 504 | 505 | resp->_conn = conn; 506 | // Content Length of -1 means we do not handle content length 507 | resp->content_length = -1; 508 | resp->connection = CONN_KEEP_ALIVE; 509 | return resp; 510 | } 511 | 512 | int response_reset(response_t *resp) { 513 | connection_t *conn = resp->_conn; 514 | hdestroy_r(&resp->_header_hash); 515 | bzero(resp, sizeof(response_t)); 516 | if (hcreate_r(MAX_HEADER_SIZE, &resp->_header_hash) == 0) { 517 | error("Error creating header hash table"); 518 | return -1; 519 | } 520 | resp->_conn = conn; 521 | resp->content_length = -1; 522 | resp->connection = CONN_KEEP_ALIVE; 523 | return 0; 524 | } 525 | 526 | int response_destroy(response_t *resp) { 527 | hdestroy_r(&resp->_header_hash); 528 | free(resp); 529 | return 0; 530 | } 531 | 532 | const char* response_get_header(response_t *resp, const char *header_name) { 533 | ENTRY item, *ret; 534 | char header_name_lower[64]; 535 | 536 | strlowercase(header_name, header_name_lower, 64); 537 | item.key = header_name_lower; 538 | 539 | if (hsearch_r(item, FIND, &ret, &resp->_header_hash) == 0) { 540 | return NULL; 541 | } 542 | 543 | return ((http_header_t*) ret->data)->value; 544 | } 545 | 546 | char* response_alloc(response_t *resp, size_t n) { 547 | char *res; 548 | if (resp->_buf_idx + n > RESPONSE_BUFFER_SIZE) { 549 | return NULL; 550 | } 551 | res = resp->_buffer + resp->_buf_idx; 552 | resp->_buf_idx += n; 553 | return res; 554 | } 555 | 556 | int response_set_header(response_t *resp, char *header_name, char *header_value) { 557 | ENTRY ent, *ret; 558 | http_header_t *header; 559 | char header_lowercase[64]; 560 | char *keybuf; 561 | size_t n; 562 | 563 | if (resp->_header_sent) { 564 | return -1; 565 | } 566 | strlowercase(header_name, header_lowercase, 64); 567 | ent.key = header_lowercase; 568 | if (hsearch_r(ent, FIND, &ret, &resp->_header_hash) != 0) { 569 | header = (http_header_t*) ret->data; 570 | header->value = header_value; 571 | return 0; 572 | } 573 | 574 | header = resp->headers + resp->header_count++; 575 | header->name = header_name; 576 | header->value = header_value; 577 | 578 | if (hsearch_r(ent, FIND, &ret, &std_headers_hash) != 0) { 579 | ent.key = ret->key; 580 | } else { 581 | n = strlen(header_lowercase) + 1; 582 | keybuf = response_alloc(resp, n); 583 | if (keybuf == NULL) { 584 | return -1; 585 | } 586 | ent.key = strncpy(keybuf, header_lowercase, n); 587 | } 588 | ent.data = header; 589 | if (hsearch_r(ent, ENTER, &ret, &resp->_header_hash) == 0) { 590 | return -1; 591 | } 592 | return 0; 593 | } 594 | 595 | int response_set_header_printf(response_t *resp, char* name, 596 | const char *fmt, ...) { 597 | char *buf = resp->_buffer + resp->_buf_idx; 598 | size_t max_len = RESPONSE_BUFFER_SIZE - resp->_buf_idx; 599 | int len = 0, res; 600 | va_list ap; 601 | va_start(ap, fmt); 602 | len = vsnprintf(buf, max_len, fmt, ap); 603 | va_end(ap); 604 | res = response_set_header(resp, name, buf); 605 | if (res == 0) { 606 | // Only truly allocate the buffer when the operation succeeds. 607 | resp->_buf_idx += (len + 1); 608 | } 609 | return res; 610 | } 611 | 612 | static void set_common_headers(response_t *resp) { 613 | char *keybuf, *tmbuf; 614 | size_t len = resp->content_length; 615 | int n; 616 | 617 | if (len >= 0) { 618 | keybuf = resp->_buffer + resp->_buf_idx; 619 | n = snprintf(keybuf, 620 | RESPONSE_BUFFER_SIZE - resp->_buf_idx, 621 | "%ld", 622 | resp->content_length); 623 | 624 | resp->_buf_idx += (n + 1); 625 | response_set_header(resp, "Content-Length", keybuf); 626 | } else { 627 | // No Content-Length header set, set connection header 628 | // to close 629 | // TODO Support chunked transfer encoding 630 | resp->connection = CONN_CLOSE; 631 | } 632 | 633 | switch(resp->connection) { 634 | case CONN_KEEP_ALIVE: 635 | //TODO Handle keep-alive time 636 | response_set_header(resp, "Connection", "keep-alive"); 637 | break; 638 | 639 | default: 640 | response_set_header(resp, "Connection", "close"); 641 | break; 642 | } 643 | 644 | response_set_header(resp, "Server", _BREEZE_NAME); 645 | tmbuf = response_alloc(resp, 32); 646 | if (current_http_date(tmbuf, 32) == 0) { 647 | response_set_header(resp, "Date", tmbuf); 648 | } 649 | } 650 | 651 | int response_send_headers(response_t *resp, handler_func next_handler) { 652 | char buffer[RESPONSE_BUFFER_SIZE]; 653 | int i, n, buf_len = 0; 654 | http_header_t *header; 655 | 656 | if (resp->_header_sent) { 657 | return -1; 658 | } 659 | 660 | set_common_headers(resp); 661 | // Write status line 662 | n = snprintf(buffer, 663 | RESPONSE_BUFFER_SIZE, 664 | "HTTP/%s %d %s\r\n", 665 | str_http_ver(resp->version), 666 | resp->status.code, 667 | resp->status.msg); 668 | buf_len += n; 669 | 670 | // Write headers 671 | for (i = 0; i < resp->header_count; i++) { 672 | header = resp->headers + i; 673 | n = snprintf(buffer + buf_len, 674 | RESPONSE_BUFFER_SIZE - buf_len, 675 | "%s: %s\r\n", 676 | header->name, 677 | header->value); 678 | buf_len += n; 679 | } 680 | 681 | // Empty line between headers and body 682 | buffer[buf_len++] = '\r'; 683 | buffer[buf_len++] = '\n'; 684 | 685 | resp->_next_handler = next_handler; 686 | if (iostream_write(resp->_conn->stream, buffer, buf_len, on_write_finished) < 0) { 687 | return -1; 688 | } 689 | resp->_header_sent = 1; 690 | return 0; 691 | } 692 | 693 | int response_write(response_t *resp, 694 | char *data, size_t data_len, 695 | handler_func next_handler) { 696 | if (!resp->_header_sent) { 697 | return -1; 698 | } 699 | resp->_next_handler = next_handler; 700 | if (iostream_write(resp->_conn->stream, 701 | data, data_len, on_write_finished) < 0) { 702 | connection_close(resp->_conn); 703 | return -1; 704 | } 705 | return 0; 706 | } 707 | 708 | int response_send_file(response_t *resp, 709 | int fd, 710 | size_t offset, 711 | size_t size, 712 | handler_func next_handler) { 713 | if (!resp->_header_sent) { 714 | return -1; 715 | } 716 | resp->_next_handler = next_handler; 717 | if (iostream_sendfile(resp->_conn->stream, fd, 718 | offset, size, on_write_finished) < 0) { 719 | connection_close(resp->_conn); 720 | return -1; 721 | } 722 | return 0; 723 | } 724 | 725 | const char *status_msg_template = "" 726 | "%d %s" 727 | "" 728 | "

%d %s

" 729 | "
Please contact website administrator to report the problem.
" 730 | "
" 731 | "
" 732 | "Powered by %s" 733 | "
" 734 | "" 735 | ""; 736 | 737 | int response_send_status(response_t *resp, http_status_t status) { 738 | size_t len = 0; 739 | char buf[1024]; 740 | 741 | resp->status = status; 742 | response_set_header(resp, "Content-Type", "text/html"); 743 | snprintf(buf, 1024, status_msg_template, 744 | status.code, status.msg, 745 | status.code, status.msg, 746 | _BREEZE_NAME); 747 | len = strlen(buf); 748 | 749 | resp->content_length = len; 750 | response_send_headers(resp, NULL); 751 | response_write(resp, buf, len, NULL); 752 | return HANDLER_DONE; 753 | } 754 | 755 | static void on_write_finished(iostream_t *stream) { 756 | connection_t *conn; 757 | handler_func handler; 758 | response_t *resp; 759 | 760 | conn = (connection_t*) stream->user_data; 761 | resp = conn->response; 762 | handler = resp->_next_handler; 763 | 764 | if (handler != NULL) { 765 | connection_run_handler(conn, handler); 766 | } 767 | if (resp->_done) { 768 | switch (resp->connection) { 769 | case CONN_CLOSE: 770 | connection_close(conn); 771 | break; 772 | 773 | case CONN_KEEP_ALIVE: 774 | default: 775 | if (request_reset(conn->request) < 0) { 776 | connection_close(conn); 777 | break; 778 | } 779 | if (response_reset(conn->response) < 0) { 780 | connection_close(conn); 781 | break; 782 | } 783 | if (context_reset(conn->context) < 0) { 784 | connection_close(conn); 785 | break; 786 | } 787 | conn->context->conf = conn->server->handler_conf; 788 | connection_run(conn); 789 | break; 790 | } 791 | } 792 | } 793 | 794 | handler_ctx_t* context_create() { 795 | handler_ctx_t *ctx = (handler_ctx_t*) calloc(1, sizeof(handler_ctx_t)); 796 | return ctx; 797 | } 798 | 799 | int context_reset(handler_ctx_t *ctx) { 800 | ctx->_stat_top = 0; 801 | ctx->conf = NULL; 802 | return 0; 803 | } 804 | 805 | int context_destroy(handler_ctx_t *ctx) { 806 | free(ctx); 807 | return 0; 808 | } 809 | 810 | int context_push(handler_ctx_t *ctx, ctx_state_t stat) { 811 | if (ctx->_stat_top >= MAX_STATE_STACK_SIZE) { 812 | return -1; 813 | } 814 | ctx->_stat_stack[ctx->_stat_top] = stat; 815 | ctx->_stat_top++; 816 | return 0; 817 | } 818 | 819 | ctx_state_t* context_pop(handler_ctx_t *ctx) { 820 | if (ctx->_stat_top <= 0) { 821 | return NULL; 822 | } 823 | ctx->_stat_top--; 824 | return &ctx->_stat_stack[ctx->_stat_top]; 825 | } 826 | 827 | ctx_state_t* context_peek(handler_ctx_t *ctx) { 828 | if (ctx->_stat_top <= 0) { 829 | return NULL; 830 | } 831 | return &ctx->_stat_stack[ctx->_stat_top - 1]; 832 | } 833 | -------------------------------------------------------------------------------- /src/json/json.c: -------------------------------------------------------------------------------- 1 | /* vim: set et ts=3 sw=3 ft=c: 2 | * 3 | * Copyright (C) 2012 James McLaughlin et al. All rights reserved. 4 | * https://github.com/udp/json-parser 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | */ 29 | 30 | #include "json.h" 31 | 32 | #ifdef _MSC_VER 33 | #ifndef _CRT_SECURE_NO_WARNINGS 34 | #define _CRT_SECURE_NO_WARNINGS 35 | #endif 36 | #endif 37 | 38 | #ifdef __cplusplus 39 | const struct _json_value json_value_none; /* zero-d by ctor */ 40 | #else 41 | const struct _json_value json_value_none = { 0 }; 42 | #endif 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | typedef unsigned short json_uchar; 50 | 51 | static unsigned char hex_value (json_char c) 52 | { 53 | if (isdigit(c)) 54 | return c - '0'; 55 | 56 | switch (c) { 57 | case 'a': case 'A': return 0x0A; 58 | case 'b': case 'B': return 0x0B; 59 | case 'c': case 'C': return 0x0C; 60 | case 'd': case 'D': return 0x0D; 61 | case 'e': case 'E': return 0x0E; 62 | case 'f': case 'F': return 0x0F; 63 | default: return 0xFF; 64 | } 65 | } 66 | 67 | typedef struct 68 | { 69 | unsigned long used_memory; 70 | 71 | unsigned int uint_max; 72 | unsigned long ulong_max; 73 | 74 | json_settings settings; 75 | int first_pass; 76 | 77 | } json_state; 78 | 79 | static void * default_alloc (size_t size, int zero, void * user_data) 80 | { 81 | return zero ? calloc (size, 1) : malloc (size); 82 | } 83 | 84 | static void default_free (void * ptr, void * user_data) 85 | { 86 | free (ptr); 87 | } 88 | 89 | static void * json_alloc (json_state * state, unsigned long size, int zero) 90 | { 91 | if ((state->ulong_max - state->used_memory) < size) 92 | return 0; 93 | 94 | if (state->settings.max_memory 95 | && (state->used_memory += size) > state->settings.max_memory) 96 | { 97 | return 0; 98 | } 99 | 100 | return state->settings.mem_alloc (size, zero, state->settings.user_data); 101 | } 102 | 103 | static int new_value 104 | (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) 105 | { 106 | json_value * value; 107 | int values_size; 108 | 109 | if (!state->first_pass) 110 | { 111 | value = *top = *alloc; 112 | *alloc = (*alloc)->_reserved.next_alloc; 113 | 114 | if (!*root) 115 | *root = value; 116 | 117 | switch (value->type) 118 | { 119 | case json_array: 120 | 121 | if (! (value->u.array.values = (json_value **) json_alloc 122 | (state, value->u.array.length * sizeof (json_value *), 0)) ) 123 | { 124 | return 0; 125 | } 126 | 127 | value->u.array.length = 0; 128 | break; 129 | 130 | case json_object: 131 | 132 | values_size = sizeof (*value->u.object.values) * value->u.object.length; 133 | 134 | if (! ((*(void **) &value->u.object.values) = json_alloc 135 | (state, values_size + ((unsigned long) value->u.object.values), 0)) ) 136 | { 137 | return 0; 138 | } 139 | 140 | value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; 141 | 142 | value->u.object.length = 0; 143 | break; 144 | 145 | case json_string: 146 | 147 | if (! (value->u.string.ptr = (json_char *) json_alloc 148 | (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) 149 | { 150 | return 0; 151 | } 152 | 153 | value->u.string.length = 0; 154 | break; 155 | 156 | default: 157 | break; 158 | }; 159 | 160 | return 1; 161 | } 162 | 163 | value = (json_value *) json_alloc (state, sizeof (json_value), 1); 164 | 165 | if (!value) 166 | return 0; 167 | 168 | if (!*root) 169 | *root = value; 170 | 171 | value->type = type; 172 | value->parent = *top; 173 | 174 | if (*alloc) 175 | (*alloc)->_reserved.next_alloc = value; 176 | 177 | *alloc = *top = value; 178 | 179 | return 1; 180 | } 181 | 182 | #define e_off \ 183 | ((int) (i - cur_line_begin)) 184 | 185 | #define whitespace \ 186 | case '\n': ++ cur_line; cur_line_begin = i; \ 187 | case ' ': case '\t': case '\r' 188 | 189 | #define string_add(b) \ 190 | do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); 191 | 192 | const static long 193 | flag_next = 1 << 0, 194 | flag_reproc = 1 << 1, 195 | flag_need_comma = 1 << 2, 196 | flag_seek_value = 1 << 3, 197 | flag_escaped = 1 << 4, 198 | flag_string = 1 << 5, 199 | flag_need_colon = 1 << 6, 200 | flag_done = 1 << 7, 201 | flag_num_negative = 1 << 8, 202 | flag_num_zero = 1 << 9, 203 | flag_num_e = 1 << 10, 204 | flag_num_e_got_sign = 1 << 11, 205 | flag_num_e_negative = 1 << 12; 206 | 207 | json_value * json_parse_ex (json_settings * settings, 208 | const json_char * json, 209 | size_t length, 210 | char * error_buf) 211 | { 212 | json_char error [128]; 213 | unsigned int cur_line; 214 | const json_char * cur_line_begin, * i, * end; 215 | json_value * top, * root, * alloc = 0; 216 | json_state state = { 0 }; 217 | long flags; 218 | long num_digits = 0, num_e = 0; 219 | json_int_t num_fraction = 0; 220 | 221 | /* Skip UTF-8 BOM 222 | */ 223 | if (length >= 3 && json [0] == 0xEF 224 | && json [1] == 0xBB 225 | && json [2] == 0xBF) 226 | { 227 | json += 3; 228 | length -= 3; 229 | } 230 | 231 | error[0] = '\0'; 232 | end = (json + length); 233 | 234 | memcpy (&state.settings, settings, sizeof (json_settings)); 235 | 236 | if (!state.settings.mem_alloc) 237 | state.settings.mem_alloc = default_alloc; 238 | 239 | if (!state.settings.mem_free) 240 | state.settings.mem_free = default_free; 241 | 242 | memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); 243 | memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); 244 | 245 | state.uint_max -= 8; /* limit of how much can be added before next check */ 246 | state.ulong_max -= 8; 247 | 248 | for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) 249 | { 250 | json_uchar uchar; 251 | unsigned char uc_b1, uc_b2, uc_b3, uc_b4; 252 | json_char * string = 0; 253 | unsigned int string_length = 0; 254 | 255 | top = root = 0; 256 | flags = flag_seek_value; 257 | 258 | cur_line = 1; 259 | cur_line_begin = json; 260 | 261 | for (i = json ;; ++ i) 262 | { 263 | json_char b = (i == end ? 0 : *i); 264 | 265 | if (flags & flag_done) 266 | { 267 | if (!b) 268 | break; 269 | 270 | switch (b) 271 | { 272 | whitespace: 273 | continue; 274 | 275 | default: 276 | sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); 277 | goto e_failed; 278 | }; 279 | } 280 | 281 | if (flags & flag_string) 282 | { 283 | if (!b) 284 | { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); 285 | goto e_failed; 286 | } 287 | 288 | if (string_length > state.uint_max) 289 | goto e_overflow; 290 | 291 | if (flags & flag_escaped) 292 | { 293 | flags &= ~ flag_escaped; 294 | 295 | switch (b) 296 | { 297 | case 'b': string_add ('\b'); break; 298 | case 'f': string_add ('\f'); break; 299 | case 'n': string_add ('\n'); break; 300 | case 'r': string_add ('\r'); break; 301 | case 't': string_add ('\t'); break; 302 | case 'u': 303 | 304 | if ((uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF 305 | || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF) 306 | { 307 | sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); 308 | goto e_failed; 309 | } 310 | 311 | uc_b1 = uc_b1 * 16 + uc_b2; 312 | uc_b2 = uc_b3 * 16 + uc_b4; 313 | 314 | uchar = ((json_char) uc_b1) * 256 + uc_b2; 315 | 316 | if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F)) 317 | { 318 | string_add ((json_char) uchar); 319 | break; 320 | } 321 | 322 | if (uchar <= 0x7FF) 323 | { 324 | if (state.first_pass) 325 | string_length += 2; 326 | else 327 | { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2); 328 | string [string_length ++] = 0x80 | (uc_b2 & 0x3F); 329 | } 330 | 331 | break; 332 | } 333 | 334 | if (state.first_pass) 335 | string_length += 3; 336 | else 337 | { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4); 338 | string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6); 339 | string [string_length ++] = 0x80 | (uc_b2 & 0x3F); 340 | } 341 | 342 | break; 343 | 344 | default: 345 | string_add (b); 346 | }; 347 | 348 | continue; 349 | } 350 | 351 | if (b == '\\') 352 | { 353 | flags |= flag_escaped; 354 | continue; 355 | } 356 | 357 | if (b == '"') 358 | { 359 | if (!state.first_pass) 360 | string [string_length] = 0; 361 | 362 | flags &= ~ flag_string; 363 | string = 0; 364 | 365 | switch (top->type) 366 | { 367 | case json_string: 368 | 369 | top->u.string.length = string_length; 370 | flags |= flag_next; 371 | 372 | break; 373 | 374 | case json_object: 375 | 376 | if (state.first_pass) 377 | (*(json_char **) &top->u.object.values) += string_length + 1; 378 | else 379 | { 380 | top->u.object.values [top->u.object.length].name 381 | = (json_char *) top->_reserved.object_mem; 382 | 383 | (*(json_char **) &top->_reserved.object_mem) += string_length + 1; 384 | } 385 | 386 | flags |= flag_seek_value | flag_need_colon; 387 | continue; 388 | 389 | default: 390 | break; 391 | }; 392 | } 393 | else 394 | { 395 | string_add (b); 396 | continue; 397 | } 398 | } 399 | 400 | if (flags & flag_seek_value) 401 | { 402 | switch (b) 403 | { 404 | whitespace: 405 | continue; 406 | 407 | case ']': 408 | 409 | if (top->type == json_array) 410 | flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; 411 | else if (!state.settings.settings & json_relaxed_commas) 412 | { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off); 413 | goto e_failed; 414 | } 415 | 416 | break; 417 | 418 | default: 419 | 420 | if (flags & flag_need_comma) 421 | { 422 | if (b == ',') 423 | { flags &= ~ flag_need_comma; 424 | continue; 425 | } 426 | else 427 | { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b); 428 | goto e_failed; 429 | } 430 | } 431 | 432 | if (flags & flag_need_colon) 433 | { 434 | if (b == ':') 435 | { flags &= ~ flag_need_colon; 436 | continue; 437 | } 438 | else 439 | { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b); 440 | goto e_failed; 441 | } 442 | } 443 | 444 | flags &= ~ flag_seek_value; 445 | 446 | switch (b) 447 | { 448 | case '{': 449 | 450 | if (!new_value (&state, &top, &root, &alloc, json_object)) 451 | goto e_alloc_failure; 452 | 453 | continue; 454 | 455 | case '[': 456 | 457 | if (!new_value (&state, &top, &root, &alloc, json_array)) 458 | goto e_alloc_failure; 459 | 460 | flags |= flag_seek_value; 461 | continue; 462 | 463 | case '"': 464 | 465 | if (!new_value (&state, &top, &root, &alloc, json_string)) 466 | goto e_alloc_failure; 467 | 468 | flags |= flag_string; 469 | 470 | string = top->u.string.ptr; 471 | string_length = 0; 472 | 473 | continue; 474 | 475 | case 't': 476 | 477 | if ((end - i) < 3 || *(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e') 478 | goto e_unknown_value; 479 | 480 | if (!new_value (&state, &top, &root, &alloc, json_boolean)) 481 | goto e_alloc_failure; 482 | 483 | top->u.boolean = 1; 484 | 485 | flags |= flag_next; 486 | break; 487 | 488 | case 'f': 489 | 490 | if ((end - i) < 4 || *(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e') 491 | goto e_unknown_value; 492 | 493 | if (!new_value (&state, &top, &root, &alloc, json_boolean)) 494 | goto e_alloc_failure; 495 | 496 | flags |= flag_next; 497 | break; 498 | 499 | case 'n': 500 | 501 | if ((end - i) < 3 || *(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l') 502 | goto e_unknown_value; 503 | 504 | if (!new_value (&state, &top, &root, &alloc, json_null)) 505 | goto e_alloc_failure; 506 | 507 | flags |= flag_next; 508 | break; 509 | 510 | default: 511 | 512 | if (isdigit (b) || b == '-') 513 | { 514 | if (!new_value (&state, &top, &root, &alloc, json_integer)) 515 | goto e_alloc_failure; 516 | 517 | if (!state.first_pass) 518 | { 519 | while (isdigit (b) || b == '+' || b == '-' 520 | || b == 'e' || b == 'E' || b == '.') 521 | { 522 | if ( (++ i) == end) 523 | { 524 | b = 0; 525 | break; 526 | } 527 | 528 | b = *i; 529 | } 530 | 531 | flags |= flag_next | flag_reproc; 532 | break; 533 | } 534 | 535 | flags &= ~ (flag_num_negative | flag_num_e | 536 | flag_num_e_got_sign | flag_num_e_negative | 537 | flag_num_zero); 538 | 539 | num_digits = 0; 540 | num_fraction = 0; 541 | num_e = 0; 542 | 543 | if (b != '-') 544 | { 545 | flags |= flag_reproc; 546 | break; 547 | } 548 | 549 | flags |= flag_num_negative; 550 | continue; 551 | } 552 | else 553 | { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); 554 | goto e_failed; 555 | } 556 | }; 557 | }; 558 | } 559 | else 560 | { 561 | switch (top->type) 562 | { 563 | case json_object: 564 | 565 | switch (b) 566 | { 567 | whitespace: 568 | continue; 569 | 570 | case '"': 571 | 572 | if (flags & flag_need_comma && (!state.settings.settings & json_relaxed_commas)) 573 | { 574 | sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off); 575 | goto e_failed; 576 | } 577 | 578 | flags |= flag_string; 579 | 580 | string = (json_char *) top->_reserved.object_mem; 581 | string_length = 0; 582 | 583 | break; 584 | 585 | case '}': 586 | 587 | flags = (flags & ~ flag_need_comma) | flag_next; 588 | break; 589 | 590 | case ',': 591 | 592 | if (flags & flag_need_comma) 593 | { 594 | flags &= ~ flag_need_comma; 595 | break; 596 | } 597 | 598 | default: 599 | 600 | sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); 601 | goto e_failed; 602 | }; 603 | 604 | break; 605 | 606 | case json_integer: 607 | case json_double: 608 | 609 | if (isdigit (b)) 610 | { 611 | ++ num_digits; 612 | 613 | if (top->type == json_integer || flags & flag_num_e) 614 | { 615 | if (! (flags & flag_num_e)) 616 | { 617 | if (flags & flag_num_zero) 618 | { sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b); 619 | goto e_failed; 620 | } 621 | 622 | if (num_digits == 1 && b == '0') 623 | flags |= flag_num_zero; 624 | } 625 | else 626 | { 627 | flags |= flag_num_e_got_sign; 628 | num_e = (num_e * 10) + (b - '0'); 629 | continue; 630 | } 631 | 632 | top->u.integer = (top->u.integer * 10) + (b - '0'); 633 | continue; 634 | } 635 | 636 | num_fraction = (num_fraction * 10) + (b - '0'); 637 | continue; 638 | } 639 | 640 | if (b == '+' || b == '-') 641 | { 642 | if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) 643 | { 644 | flags |= flag_num_e_got_sign; 645 | 646 | if (b == '-') 647 | flags |= flag_num_e_negative; 648 | 649 | continue; 650 | } 651 | } 652 | else if (b == '.' && top->type == json_integer) 653 | { 654 | if (!num_digits) 655 | { sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off); 656 | goto e_failed; 657 | } 658 | 659 | top->type = json_double; 660 | top->u.dbl = (double) top->u.integer; 661 | 662 | num_digits = 0; 663 | continue; 664 | } 665 | 666 | if (! (flags & flag_num_e)) 667 | { 668 | if (top->type == json_double) 669 | { 670 | if (!num_digits) 671 | { sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off); 672 | goto e_failed; 673 | } 674 | 675 | top->u.dbl += ((double) num_fraction) / (pow (10, (double) num_digits)); 676 | } 677 | 678 | if (b == 'e' || b == 'E') 679 | { 680 | flags |= flag_num_e; 681 | 682 | if (top->type == json_integer) 683 | { 684 | top->type = json_double; 685 | top->u.dbl = (double) top->u.integer; 686 | } 687 | 688 | num_digits = 0; 689 | flags &= ~ flag_num_zero; 690 | 691 | continue; 692 | } 693 | } 694 | else 695 | { 696 | if (!num_digits) 697 | { sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off); 698 | goto e_failed; 699 | } 700 | 701 | top->u.dbl *= pow (10, (double) (flags & flag_num_e_negative ? - num_e : num_e)); 702 | } 703 | 704 | if (flags & flag_num_negative) 705 | { 706 | if (top->type == json_integer) 707 | top->u.integer = - top->u.integer; 708 | else 709 | top->u.dbl = - top->u.dbl; 710 | } 711 | 712 | flags |= flag_next | flag_reproc; 713 | break; 714 | 715 | default: 716 | break; 717 | }; 718 | } 719 | 720 | if (flags & flag_reproc) 721 | { 722 | flags &= ~ flag_reproc; 723 | -- i; 724 | } 725 | 726 | if (flags & flag_next) 727 | { 728 | flags = (flags & ~ flag_next) | flag_need_comma; 729 | 730 | if (!top->parent) 731 | { 732 | /* root value done */ 733 | 734 | flags |= flag_done; 735 | continue; 736 | } 737 | 738 | if (top->parent->type == json_array) 739 | flags |= flag_seek_value; 740 | 741 | if (!state.first_pass) 742 | { 743 | json_value * parent = top->parent; 744 | 745 | switch (parent->type) 746 | { 747 | case json_object: 748 | 749 | parent->u.object.values 750 | [parent->u.object.length].value = top; 751 | 752 | break; 753 | 754 | case json_array: 755 | 756 | parent->u.array.values 757 | [parent->u.array.length] = top; 758 | 759 | break; 760 | 761 | default: 762 | break; 763 | }; 764 | } 765 | 766 | if ( (++ top->parent->u.array.length) > state.uint_max) 767 | goto e_overflow; 768 | 769 | top = top->parent; 770 | 771 | continue; 772 | } 773 | } 774 | 775 | alloc = root; 776 | } 777 | 778 | return root; 779 | 780 | e_unknown_value: 781 | 782 | sprintf (error, "%d:%d: Unknown value", cur_line, e_off); 783 | goto e_failed; 784 | 785 | e_alloc_failure: 786 | 787 | strcpy (error, "Memory allocation failure"); 788 | goto e_failed; 789 | 790 | e_overflow: 791 | 792 | sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off); 793 | goto e_failed; 794 | 795 | e_failed: 796 | 797 | if (error_buf) 798 | { 799 | if (*error) 800 | strcpy (error_buf, error); 801 | else 802 | strcpy (error_buf, "Unknown error"); 803 | } 804 | 805 | if (state.first_pass) 806 | alloc = root; 807 | 808 | while (alloc) 809 | { 810 | top = alloc->_reserved.next_alloc; 811 | state.settings.mem_free (alloc, state.settings.user_data); 812 | alloc = top; 813 | } 814 | 815 | if (!state.first_pass) 816 | json_value_free_ex (&state.settings, root); 817 | 818 | return 0; 819 | } 820 | 821 | json_value * json_parse (const json_char * json, size_t length) 822 | { 823 | json_settings settings = { 0 }; 824 | return json_parse_ex (&settings, json, length, 0); 825 | } 826 | 827 | void json_value_free_ex (json_settings * settings, json_value * value) 828 | { 829 | json_value * cur_value; 830 | 831 | if (!value) 832 | return; 833 | 834 | value->parent = 0; 835 | 836 | while (value) 837 | { 838 | switch (value->type) 839 | { 840 | case json_array: 841 | 842 | if (!value->u.array.length) 843 | { 844 | settings->mem_free (value->u.array.values, settings->user_data); 845 | break; 846 | } 847 | 848 | value = value->u.array.values [-- value->u.array.length]; 849 | continue; 850 | 851 | case json_object: 852 | 853 | if (!value->u.object.length) 854 | { 855 | settings->mem_free (value->u.object.values, settings->user_data); 856 | break; 857 | } 858 | 859 | value = value->u.object.values [-- value->u.object.length].value; 860 | continue; 861 | 862 | case json_string: 863 | 864 | settings->mem_free (value->u.string.ptr, settings->user_data); 865 | break; 866 | 867 | default: 868 | break; 869 | }; 870 | 871 | cur_value = value; 872 | value = value->parent; 873 | settings->mem_free (cur_value, settings->user_data); 874 | } 875 | } 876 | 877 | void json_value_free (json_value * value) 878 | { 879 | json_settings settings = { 0 }; 880 | settings.mem_free = default_free; 881 | json_value_free_ex (&settings, value); 882 | } 883 | 884 | --------------------------------------------------------------------------------