Skip to content
Browse files

No more submodules

  • Loading branch information...
1 parent 354e71d commit 1e0ec98c514175373a9ca4e4f4633e6070657902 Mark Ellzey committed Jul 8, 2011
Showing with 36,694 additions and 0 deletions.
  1. +24 −0 evthr/Makefile
  2. +53 −0 evthr/README
  3. +468 −0 evthr/evthr.c
  4. +48 −0 evthr/evthr.h
  5. +49 −0 evthr/test.c
  6. +4 −0 http_parser/.gitignore
  7. +4 −0 http_parser/CONTRIBUTIONS
  8. +19 −0 http_parser/LICENSE-MIT
  9. +41 −0 http_parser/Makefile
  10. +171 −0 http_parser/README.md
  11. +1,644 −0 http_parser/http_parser.c
  12. +183 −0 http_parser/http_parser.h
  13. +1,952 −0 http_parser/test.c
  14. +1 −0 oniguruma/AUTHORS
  15. +49 −0 oniguruma/CMakeLists.txt
  16. +28 −0 oniguruma/COPYING
  17. +2,052 −0 oniguruma/HISTORY
  18. +236 −0 oniguruma/INSTALL
  19. +94 −0 oniguruma/Makefile.am
  20. +1,238 −0 oniguruma/Makefile.in
  21. +189 −0 oniguruma/README
  22. +195 −0 oniguruma/README.ja
  23. +34 −0 oniguruma/config.h.in
  24. +58 −0 oniguruma/enc/ascii.c
  25. +162 −0 oniguruma/enc/big5.c
  26. +200 −0 oniguruma/enc/cp1251.c
  27. +285 −0 oniguruma/enc/euc_jp.c
  28. +158 −0 oniguruma/enc/euc_kr.c
  29. +129 −0 oniguruma/enc/euc_tw.c
  30. +495 −0 oniguruma/enc/gb18030.c
  31. +272 −0 oniguruma/enc/iso8859_1.c
  32. +239 −0 oniguruma/enc/iso8859_10.c
  33. +96 −0 oniguruma/enc/iso8859_11.c
  34. +228 −0 oniguruma/enc/iso8859_13.c
  35. +241 −0 oniguruma/enc/iso8859_14.c
  36. +235 −0 oniguruma/enc/iso8859_15.c
  37. +237 −0 oniguruma/enc/iso8859_16.c
  38. +235 −0 oniguruma/enc/iso8859_2.c
  39. +235 −0 oniguruma/enc/iso8859_3.c
  40. +237 −0 oniguruma/enc/iso8859_4.c
  41. +226 −0 oniguruma/enc/iso8859_5.c
  42. +96 −0 oniguruma/enc/iso8859_6.c
  43. +222 −0 oniguruma/enc/iso8859_7.c
  44. +96 −0 oniguruma/enc/iso8859_8.c
  45. +228 −0 oniguruma/enc/iso8859_9.c
  46. +250 −0 oniguruma/enc/koi8.c
  47. +212 −0 oniguruma/enc/koi8_r.c
  48. +1,162 −0 oniguruma/enc/mktable.c
  49. +318 −0 oniguruma/enc/sjis.c
  50. +11,356 −0 oniguruma/enc/unicode.c
  51. +225 −0 oniguruma/enc/utf16_be.c
  52. +226 −0 oniguruma/enc/utf16_le.c
  53. +184 −0 oniguruma/enc/utf32_be.c
  54. +184 −0 oniguruma/enc/utf32_le.c
  55. +305 −0 oniguruma/enc/utf8.c
  56. +78 −0 oniguruma/onig-config.in
  57. +85 −0 oniguruma/oniggnu.h
  58. +169 −0 oniguruma/onigposix.h
  59. +822 −0 oniguruma/oniguruma.h
  60. +6,254 −0 oniguruma/regcomp.c
  61. +902 −0 oniguruma/regenc.c
  62. +189 −0 oniguruma/regenc.h
  63. +387 −0 oniguruma/regerror.c
Sorry, we could not display the entire diff because it was too big.
View
24 evthr/Makefile
@@ -0,0 +1,24 @@
+SRC = evthr.c
+OUT = libevthr.a
+OBJ = $(SRC:.c=.o)
+INCLUDES = -I.
+CFLAGS += -Wall -ggdb
+LDFLAGS += -ggdb
+CC = gcc
+
+.SUFFIXES: .c
+
+default: $(OUT)
+
+.c.o:
+ $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@
+
+$(OUT): $(OBJ)
+ ar rcs $(OUT) $(OBJ)
+
+test: $(OUT) test.c
+ $(CC) $(INCLUDES) $(CFLAGS) test.c -o test $(OUT) -levent -levent_pthreads -lpthread
+
+clean:
+ rm -f $(OBJ) $(OUT) test
+
View
53 evthr/README
@@ -0,0 +1,53 @@
+Libevthr is an API which manages threads and thread-pools in an event based
+manner. This API requires libevent with threading support.
+
+Libevthr works a bit differently than most thread management systems. Instead of
+conditional signalling and some type of pre-thread queue, Libevthr uses a
+deferral type mechanism. That is, a thread is always running, abstracted to a
+point where you "defer" your function *into* a thread.
+
+For example you can start up a single thread with a backlog of 10 (a backlog
+being the max number of outstanding callbacks to run within the thread), and
+execute a function you would like to run inside the thread one or many times.
+The act of deferrals is non-blocking.
+
+Example Code for evthrs:
+
+ evthr_t * thr = evthr_new(10, NULL);
+
+ if (evthr_start(thr) < 0) {
+ exit(1);
+ }
+
+ evthr_defer(thr, my_cb_1, NULL);
+ evthr_defer(thr, my_cb_2, NULL);
+ evthr_defer(thr, my_cb_3, NULL);
+
+ sleep(n_seconds);
+
+ evthr_stop(thr);
+
+Libevthr also has the ability to create pools using the same methods that a
+single evthr has. For example, if you would like to create 10 threads, each
+with a backlog of 5:
+
+ evthr_pool_t * thr_pool = evthr_pool_new(10, 5, NULL);
+
+ if (evthr_pool_start(thr_pool) < 0) {
+ exit(1);
+ }
+
+ evthr_pool_defer(thr_pool, my_cb_1, NULL);
+ evthr_pool_defer(thr_pool, my_cb_2, NULL);
+ evthr_pool_defer(thr_pool, my_cb_3, NULL);
+
+Your callback functions which you defer must be of type "evthr_cb", or
+"void cb_name(void * arg, void * shared)". In this case, the "arg" variable is
+the data you passed as the third argument to either evthr_pool_defer, or
+evthr_defer. The "shared" variable is the data that was either the second
+variable in evthr_new(), or the third variable in evthr_pool_new().
+
+The gist of this is to allow a global dataset, along with deferred specific
+data.
+
+See test.c for a quick example.
View
468 evthr/evthr.c
@@ -0,0 +1,468 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <pthread.h>
+
+#include <event.h>
+#include <event2/thread.h>
+
+#include "evthr.h"
+
+#define _EVTHR_MAGIC 0x4d52
+
+typedef struct evthr_cmd evthr_cmd_t;
+typedef struct evthr_pool_slist evthr_pool_slist_t;
+
+struct evthr_cmd {
+ uint16_t magic;
+ uint8_t stop;
+ void * args;
+ evthr_cb cb;
+};
+
+TAILQ_HEAD(evthr_pool_slist, evthr);
+
+struct evthr_pool {
+ int nthreads;
+ int nprocs;
+ evthr_pool_slist_t threads;
+};
+
+struct evthr {
+ int cur_backlog;
+ int proc_to_use;
+ int rdr;
+ int wdr;
+ char err;
+ ev_t * event;
+ evbase_t * evbase;
+ pthread_mutex_t * lock;
+ pthread_mutex_t * stat_lock;
+ pthread_mutex_t * rlock;
+ pthread_t * thr;
+ void * args;
+
+ TAILQ_ENTRY(evthr) next;
+};
+
+void
+evthr_inc_backlog(evthr_t * evthr) {
+ __sync_fetch_and_add(&evthr->cur_backlog, 1);
+}
+
+void
+evthr_dec_backlog(evthr_t * evthr) {
+ __sync_fetch_and_sub(&evthr->cur_backlog, 1);
+}
+
+int
+evthr_get_backlog(evthr_t * evthr) {
+ return __sync_add_and_fetch(&evthr->cur_backlog, 0);
+}
+
+static void
+_evthr_read_cmd(int sock, short which, void * args) {
+ evthr_t * thread;
+ evthr_cmd_t cmd;
+ int avail = 0;
+ ssize_t recvd;
+
+ if (!(thread = (evthr_t *)args)) {
+ return;
+ }
+
+ if (pthread_mutex_trylock(thread->lock) != 0) {
+ return;
+ }
+
+ if (ioctl(sock, FIONREAD, &avail) < 0) {
+ goto error;
+ }
+
+ if (avail <= 0) {
+ goto end;
+ }
+
+ if (avail < (int)sizeof(evthr_cmd_t)) {
+ goto end;
+ }
+
+ pthread_mutex_lock(thread->rlock);
+
+ if ((recvd = recv(sock, &cmd, sizeof(evthr_cmd_t), 0)) <= 0) {
+ pthread_mutex_unlock(thread->rlock);
+ if (errno == EAGAIN) {
+ goto end;
+ } else {
+ goto error;
+ }
+ }
+
+ pthread_mutex_unlock(thread->rlock);
+
+ if (recvd != sizeof(evthr_cmd_t)) {
+ goto error;
+ }
+
+ if (cmd.magic != _EVTHR_MAGIC) {
+ goto error;
+ }
+
+ if (cmd.stop == 1) {
+ goto stop;
+ }
+
+ if (cmd.cb != NULL) {
+ cmd.cb(thread, cmd.args, thread->args);
+ goto done;
+ } else {
+ goto done;
+ }
+
+stop:
+ event_base_loopbreak(thread->evbase);
+done:
+ evthr_dec_backlog(thread);
+end:
+ pthread_mutex_unlock(thread->lock);
+ return;
+error:
+ pthread_mutex_lock(thread->stat_lock);
+ thread->cur_backlog = -1;
+ thread->err = 1;
+ pthread_mutex_unlock(thread->stat_lock);
+ pthread_mutex_unlock(thread->lock);
+ event_base_loopbreak(thread->evbase);
+ return;
+} /* _evthr_read_cmd */
+
+static int
+_evthr_get_num_procs(void) {
+ return sysconf(_SC_NPROCESSORS_ONLN);
+}
+
+static void *
+_evthr_loop(void * args) {
+ evthr_t * thread;
+
+ if (!(thread = (evthr_t *)args)) {
+ return NULL;
+ }
+
+ if (thread == NULL || thread->thr == NULL) {
+ pthread_exit(NULL);
+ }
+
+ thread->evbase = event_base_new();
+ thread->event = event_new(thread->evbase, thread->rdr,
+ EV_READ | EV_PERSIST, _evthr_read_cmd, args);
+
+ event_add(thread->event, NULL);
+ event_base_loop(thread->evbase, 0);
+
+ if (thread->err == 1) {
+ fprintf(stderr, "FATAL ERROR!\n");
+ }
+
+ evthr_free(thread);
+ pthread_exit(NULL);
+}
+
+evthr_res
+evthr_defer(evthr_t * thread, evthr_cb cb, void * arg) {
+ int cur_backlog;
+ evthr_cmd_t cmd = { 0 };
+
+ cur_backlog = evthr_get_backlog(thread);
+
+ if (cur_backlog == -1) {
+ return EVTHR_RES_FATAL;
+ }
+
+ evthr_inc_backlog(thread);
+
+ cmd.magic = _EVTHR_MAGIC;
+ cmd.cb = cb;
+ cmd.args = arg;
+ cmd.stop = 0;
+
+ pthread_mutex_lock(thread->rlock);
+
+ if (send(thread->wdr, &cmd, sizeof(evthr_cmd_t), 0) <= 0) {
+ pthread_mutex_unlock(thread->rlock);
+ return EVTHR_RES_RETRY;
+ }
+
+ pthread_mutex_unlock(thread->rlock);
+
+ return EVTHR_RES_OK;
+}
+
+evthr_res
+evthr_stop(evthr_t * thread) {
+ evthr_cmd_t cmd = { 0 };
+
+ cmd.magic = _EVTHR_MAGIC;
+ cmd.cb = NULL;
+ cmd.args = NULL;
+ cmd.stop = 1;
+
+ pthread_mutex_lock(thread->rlock);
+
+ if (write(thread->wdr, &cmd, sizeof(evthr_cmd_t)) < 0) {
+ pthread_mutex_unlock(thread->rlock);
+ return EVTHR_RES_RETRY;
+ }
+
+ pthread_mutex_unlock(thread->rlock);
+
+ return EVTHR_RES_OK;
+}
+
+evbase_t *
+evthr_get_base(evthr_t * thr) {
+ return thr->evbase;
+}
+
+evthr_t *
+evthr_new(void * args, int proc_to_use) {
+ evthr_t * thread;
+ int fds[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
+ return NULL;
+ }
+
+ if (!(thread = calloc(sizeof(evthr_t), sizeof(char)))) {
+ return NULL;
+ }
+
+ thread->stat_lock = malloc(sizeof(pthread_mutex_t));
+ thread->rlock = malloc(sizeof(pthread_mutex_t));
+ thread->lock = malloc(sizeof(pthread_mutex_t));
+ thread->thr = malloc(sizeof(pthread_t));
+ thread->args = args;
+ thread->rdr = fds[0];
+ thread->wdr = fds[1];
+ thread->proc_to_use = proc_to_use;
+
+ if (pthread_mutex_init(thread->lock, NULL)) {
+ evthr_free(thread);
+ return NULL;
+ }
+
+ if (pthread_mutex_init(thread->stat_lock, NULL)) {
+ evthr_free(thread);
+ return NULL;
+ }
+
+ if (pthread_mutex_init(thread->rlock, NULL)) {
+ evthr_free(thread);
+ return NULL;
+ }
+
+ fcntl(thread->rdr, F_SETFL, O_NONBLOCK);
+ fcntl(thread->wdr, F_SETFL, O_NONBLOCK);
+
+ return thread;
+} /* evthr_new */
+
+int
+evthr_start(evthr_t * thread) {
+ if (thread == NULL || thread->thr == NULL) {
+ return -1;
+ }
+
+ if (pthread_create(thread->thr, NULL, _evthr_loop, (void *)thread)) {
+ return -1;
+ }
+
+ return pthread_detach(*thread->thr);
+}
+
+void
+evthr_free(evthr_t * thread) {
+ if (thread == NULL) {
+ return;
+ }
+
+ if (thread->rdr > 0) {
+ close(thread->rdr);
+ }
+
+ if (thread->wdr > 0) {
+ close(thread->wdr);
+ }
+
+ if (thread->lock) {
+ pthread_mutex_destroy(thread->lock);
+ free(thread->lock);
+ }
+
+ if (thread->stat_lock) {
+ pthread_mutex_destroy(thread->stat_lock);
+ }
+
+ if (thread->rlock) {
+ pthread_mutex_destroy(thread->rlock);
+ }
+
+ if (thread->thr) {
+ free(thread->thr);
+ }
+
+ if (thread->event) {
+ event_free(thread->event);
+ }
+
+ if (thread->evbase) {
+ event_base_free(thread->evbase);
+ }
+
+ free(thread);
+}
+
+void
+evthr_pool_free(evthr_pool_t * pool) {
+ evthr_t * thread;
+ evthr_t * save;
+
+ if (pool == NULL) {
+ return;
+ }
+
+ for (thread = TAILQ_FIRST(&pool->threads); thread != NULL; thread = save) {
+ save = TAILQ_NEXT(thread, next);
+
+ TAILQ_REMOVE(&pool->threads, thread, next);
+
+ evthr_free(thread);
+ }
+
+ free(pool);
+}
+
+evthr_res
+evthr_pool_stop(evthr_pool_t * pool) {
+ evthr_t * thr;
+
+ if (pool == NULL) {
+ return EVTHR_RES_FATAL;
+ }
+
+ TAILQ_FOREACH(thr, &pool->threads, next) {
+ evthr_stop(thr);
+ }
+
+ memset(&pool->threads, 0, sizeof(pool->threads));
+
+ return EVTHR_RES_OK;
+}
+
+evthr_res
+evthr_pool_defer(evthr_pool_t * pool, evthr_cb cb, void * arg) {
+ evthr_t * min_thr = NULL;
+ evthr_t * thr = NULL;
+
+ if (pool == NULL) {
+ return EVTHR_RES_FATAL;
+ }
+
+ if (cb == NULL) {
+ return EVTHR_RES_NOCB;
+ }
+
+ /* find the thread with the smallest backlog */
+ TAILQ_FOREACH(thr, &pool->threads, next) {
+ evthr_t * m_save;
+ evthr_t * t_save;
+ int thr_backlog = 0;
+ int min_backlog = 0;
+
+ thr_backlog = evthr_get_backlog(thr);
+
+ if (min_thr) {
+ min_backlog = evthr_get_backlog(min_thr);
+ }
+
+ m_save = min_thr;
+ t_save = thr;
+
+ if (min_thr == NULL) {
+ min_thr = thr;
+ } else if (thr_backlog == 0) {
+ min_thr = thr;
+ } else if (thr_backlog < min_backlog) {
+ min_thr = thr;
+ }
+
+ if (evthr_get_backlog(min_thr) == 0) {
+ break;
+ }
+ }
+
+ return evthr_defer(min_thr, cb, arg);
+} /* evthr_pool_defer */
+
+evthr_pool_t *
+evthr_pool_new(int nthreads, void * shared) {
+ evthr_pool_t * pool;
+ int i;
+
+ if (nthreads == 0) {
+ return NULL;
+ }
+
+ if (!(pool = calloc(sizeof(evthr_pool_t), sizeof(char)))) {
+ return NULL;
+ }
+
+ pool->nprocs = _evthr_get_num_procs();
+ pool->nthreads = nthreads;
+ TAILQ_INIT(&pool->threads);
+
+ for (i = 0; i < nthreads; i++) {
+ evthr_t * thread;
+ int proc = i % pool->nprocs;
+
+ if (!(thread = evthr_new(shared, proc))) {
+ evthr_pool_free(pool);
+ return NULL;
+ }
+
+ TAILQ_INSERT_TAIL(&pool->threads, thread, next);
+ }
+
+ return pool;
+}
+
+int
+evthr_pool_start(evthr_pool_t * pool) {
+ evthr_t * evthr = NULL;
+
+ if (pool == NULL) {
+ return -1;
+ }
+
+ TAILQ_FOREACH(evthr, &pool->threads, next) {
+ if (evthr_start(evthr) < 0) {
+ return -1;
+ }
+
+ usleep(300);
+ }
+
+ return 0;
+}
+
View
48 evthr/evthr.h
@@ -0,0 +1,48 @@
+#define _GNU_SOURCE
+#ifndef __EVTHR_H__
+#define __EVTHR_H__
+
+#include <sched.h>
+#include <pthread.h>
+#include <sys/queue.h>
+#include <event.h>
+#include <event2/thread.h>
+
+struct evthr_pool;
+struct evthr;
+
+typedef struct event_base evbase_t;
+typedef struct event ev_t;
+
+typedef struct evthr_pool evthr_pool_t;
+typedef struct evthr evthr_t;
+typedef enum evthr_res evthr_res;
+
+typedef void (*evthr_cb)(evthr_t * thr, void * cmd_arg, void * shared);
+
+enum evthr_res {
+ EVTHR_RES_OK = 0,
+ EVTHR_RES_BACKLOG,
+ EVTHR_RES_RETRY,
+ EVTHR_RES_NOCB,
+ EVTHR_RES_FATAL
+};
+
+evthr_t * evthr_new(void * arg, int proc_to_use);
+evbase_t * evthr_get_base(evthr_t * thr);
+int evthr_start(evthr_t * evthr);
+evthr_res evthr_stop(evthr_t * evthr);
+evthr_res evthr_defer(evthr_t * evthr, evthr_cb cb, void * arg);
+void evthr_free(evthr_t * evthr);
+void evthr_inc_backlog(evthr_t * evthr);
+void evthr_dec_backlog(evthr_t * evthr);
+int evthr_get_backlog(evthr_t * evthr);
+
+evthr_pool_t * evthr_pool_new(int nthreads, void * shared);
+int evthr_pool_start(evthr_pool_t * pool);
+evthr_res evthr_pool_stop(evthr_pool_t * pool);
+evthr_res evthr_pool_defer(evthr_pool_t * pool, evthr_cb cb, void * arg);
+void evthr_pool_free(evthr_pool_t * pool);
+
+#endif /* __EVTHR_H__ */
+
View
49 evthr/test.c
@@ -0,0 +1,49 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <evthr.h>
+
+static void
+_test_cb_1(evthr_t * thr, void * cmdarg, void * shared) {
+ printf("START _test_cb_1 (%u)\n", (unsigned int)pthread_self());
+ sleep(1);
+ printf("END _test_cb_1 (%u)\n", (unsigned int)pthread_self());
+}
+
+int
+main(int argc, char ** argv) {
+ evthr_pool_t * pool = NULL;
+ int i = 0;
+
+ evthread_use_pthreads();
+ evthread_enable_lock_debuging();
+ pool = evthr_pool_new(8, NULL);
+
+ evthr_pool_start(pool);
+
+ while (1) {
+ if (i++ >= 5) {
+ break;
+ }
+
+ printf("Iter %d\n", i);
+
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+ printf("%d\n", evthr_pool_defer(pool, _test_cb_1, "derp"));
+
+ sleep(2);
+ }
+
+ evthr_pool_stop(pool);
+ evthr_pool_free(pool);
+ return 0;
+}
+
View
4 http_parser/.gitignore
@@ -0,0 +1,4 @@
+tags
+*.o
+test
+test_g
View
4 http_parser/CONTRIBUTIONS
@@ -0,0 +1,4 @@
+Contributors must agree to the Contributor License Agreement before patches
+can be accepted.
+
+http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
View
19 http_parser/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
View
41 http_parser/Makefile
@@ -0,0 +1,41 @@
+OPT_DEBUG=-O0 -g -Wall -Wextra -Werror -I.
+OPT_FAST=-O3 -DHTTP_PARSER_STRICT=0 -I.
+
+CC?=gcc
+
+
+test: test_g
+ ./test_g
+
+test_g: http_parser_g.o test_g.o
+ $(CC) $(OPT_DEBUG) http_parser_g.o test_g.o -o $@
+
+test_g.o: test.c http_parser.h Makefile
+ $(CC) $(OPT_DEBUG) -c test.c -o $@
+
+test.o: test.c http_parser.h Makefile
+ $(CC) $(OPT_FAST) -c test.c -o $@
+
+http_parser_g.o: http_parser.c http_parser.h Makefile
+ $(CC) $(OPT_DEBUG) -c http_parser.c -o $@
+
+test-valgrind: test_g
+ valgrind ./test_g
+
+http_parser.o: http_parser.c http_parser.h Makefile
+ $(CC) $(OPT_FAST) -c http_parser.c
+
+test_fast: http_parser.o test.c http_parser.h
+ $(CC) $(OPT_FAST) http_parser.o test.c -o $@
+
+test-run-timed: test_fast
+ while(true) do time ./test_fast > /dev/null; done
+
+
+tags: http_parser.c http_parser.h test.c
+ ctags $^
+
+clean:
+ rm -f *.o test test_fast test_g http_parser.tar tags
+
+.PHONY: clean package test-run test-run-timed test-valgrind
View
171 http_parser/README.md
@@ -0,0 +1,171 @@
+HTTP Parser
+===========
+
+This is a parser for HTTP messages written in C. It parses both requests and
+responses. The parser is designed to be used in performance HTTP
+applications. It does not make any syscalls nor allocations, it does not
+buffer data, it can be interrupted at anytime. Depending on your
+architecture, it only requires about 40 bytes of data per message
+stream (in a web server that is per connection).
+
+Features:
+
+ * No dependencies
+ * Handles persistent streams (keep-alive).
+ * Decodes chunked encoding.
+ * Upgrade support
+ * Defends against buffer overflow attacks.
+
+The parser extracts the following information from HTTP messages:
+
+ * Header fields and values
+ * Content-Length
+ * Request method
+ * Response status code
+ * Transfer-Encoding
+ * HTTP version
+ * Request path, query string, fragment
+ * Message body
+
+
+Usage
+-----
+
+One `http_parser` object is used per TCP connection. Initialize the struct
+using `http_parser_init()` and set the callbacks. That might look something
+like this for a request parser:
+
+ http_parser_settings settings;
+ settings.on_path = my_path_callback;
+ settings.on_header_field = my_header_field_callback;
+ /* ... */
+
+ http_parser *parser = malloc(sizeof(http_parser));
+ http_parser_init(parser, HTTP_REQUEST);
+ parser->data = my_socket;
+
+When data is received on the socket execute the parser and check for errors.
+
+ size_t len = 80*1024, nparsed;
+ char buf[len];
+ ssize_t recved;
+
+ recved = recv(fd, buf, len, 0);
+
+ if (recved < 0) {
+ /* Handle error. */
+ }
+
+ /* Start up / continue the parser.
+ * Note we pass recved==0 to signal that EOF has been recieved.
+ */
+ nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+ if (parser->upgrade) {
+ /* handle new protocol */
+ } else if (nparsed != recved) {
+ /* Handle error. Usually just close the connection. */
+ }
+
+HTTP needs to know where the end of the stream is. For example, sometimes
+servers send responses without Content-Length and expect the client to
+consume input (for the body) until EOF. To tell http_parser about EOF, give
+`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
+can still be encountered during an EOF, so one must still be prepared
+to receive them.
+
+Scalar valued message information such as `status_code`, `method`, and the
+HTTP version are stored in the parser structure. This data is only
+temporally stored in `http_parser` and gets reset on each new message. If
+this information is needed later, copy it out of the structure during the
+`headers_complete` callback.
+
+The parser decodes the transfer-encoding for both requests and responses
+transparently. That is, a chunked encoding is decoded before being sent to
+the on_body callback.
+
+
+The Special Problem of Upgrade
+------------------------------
+
+HTTP supports upgrading the connection to a different protocol. An
+increasingly common example of this is the Web Socket protocol which sends
+a request like
+
+ GET /demo HTTP/1.1
+ Upgrade: WebSocket
+ Connection: Upgrade
+ Host: example.com
+ Origin: http://example.com
+ WebSocket-Protocol: sample
+
+followed by non-HTTP data.
+
+(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
+information the Web Socket protocol.)
+
+To support this, the parser will treat this as a normal HTTP message without a
+body. Issuing both on_headers_complete and on_message_complete callbacks. However
+http_parser_execute() will stop parsing at the end of the headers and return.
+
+The user is expected to check if `parser->upgrade` has been set to 1 after
+`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
+offset by the return value of `http_parser_execute()`.
+
+
+Callbacks
+---------
+
+During the `http_parser_execute()` call, the callbacks set in
+`http_parser_settings` will be executed. The parser maintains state and
+never looks behind, so buffering the data is not necessary. If you need to
+save certain data for later usage, you can do that from the callbacks.
+
+There are two types of callbacks:
+
+* notification `typedef int (*http_cb) (http_parser*);`
+ Callbacks: on_message_begin, on_headers_complete, on_message_complete.
+* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
+ Callbacks: (requests only) on_path, on_query_string, on_uri, on_fragment,
+ (common) on_header_field, on_header_value, on_body;
+
+Callbacks must return 0 on success. Returning a non-zero value indicates
+error to the parser, making it exit immediately.
+
+In case you parse HTTP message in chunks (i.e. `read()` request line
+from socket, parse, read half headers, parse, etc) your data callbacks
+may be called more than once. Http-parser guarantees that data pointer is only
+valid for the lifetime of callback. You can also `read()` into a heap allocated
+buffer to avoid copying memory around if this fits your application.
+
+Reading headers may be a tricky task if you read/parse headers partially.
+Basically, you need to remember whether last header callback was field or value
+and apply following logic:
+
+ (on_header_field and on_header_value shortened to on_h_*)
+ ------------------------ ------------ --------------------------------------------
+ | State (prev. callback) | Callback | Description/action |
+ ------------------------ ------------ --------------------------------------------
+ | nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
+ | | | into it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_field | New header started. |
+ | | | Copy current name,value buffers to headers |
+ | | | list and allocate new buffer for new name |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_field | Previous name continues. Reallocate name |
+ | | | buffer and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_value | Value for current header started. Allocate |
+ | | | new buffer and copy callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_value | Value continues. Reallocate value buffer |
+ | | | and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
+
+
+See examples of reading in headers:
+
+* [partial example](http://gist.github.com/155877) in C
+* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C
+* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript
View
1,644 http_parser/http_parser.c
@@ -0,0 +1,1644 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <http_parser.h>
+#include <assert.h>
+#include <stddef.h>
+
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+
+#define CALLBACK2(FOR) \
+do { \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser)) return (p - data); \
+ } \
+} while (0)
+
+
+#define MARK(FOR) \
+do { \
+ FOR##_mark = p; \
+} while (0)
+
+#define CALLBACK_NOCLEAR(FOR) \
+do { \
+ if (FOR##_mark) { \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser, \
+ FOR##_mark, \
+ p - FOR##_mark)) \
+ { \
+ return (p - data); \
+ } \
+ } \
+ } \
+} while (0)
+
+
+#define CALLBACK(FOR) \
+do { \
+ CALLBACK_NOCLEAR(FOR); \
+ FOR##_mark = NULL; \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+ { "DELETE"
+ , "GET"
+ , "HEAD"
+ , "POST"
+ , "PUT"
+ , "CONNECT"
+ , "OPTIONS"
+ , "TRACE"
+ , "COPY"
+ , "LOCK"
+ , "MKCOL"
+ , "MOVE"
+ , "PROPFIND"
+ , "PROPPATCH"
+ , "UNLOCK"
+ , "REPORT"
+ , "MKACTIVITY"
+ , "CHECKOUT"
+ , "MERGE"
+ , "M-SEARCH"
+ , "NOTIFY"
+ , "SUBSCRIBE"
+ , "UNSUBSCRIBE"
+ };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ ' ', '!', '"', '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', '/',
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'x', 'y', 'z', 0, 0, 0, '^', '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'x', 'y', 'z', 0, '|', '}', '~', 0 };
+
+
+static const int8_t unhex[256] =
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+
+static const uint8_t normal_url_char[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, 1, 1, 0, 1, 1, 1, 1,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1, 1, 1, 1, 1, 1, 1, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1, 1, 1, 1, 1, 1, 1, 1,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1, 1, 1, 1, 1, 1, 1, 0,
+
+/* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8
+ encoded paths. This is out of spec, but clients generate this and most other
+ HTTP servers support it. We should, too. */
+
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1 };
+
+
+enum state
+ { s_dead = 1 /* important that this is > 0 */
+
+ , s_start_req_or_res
+ , s_res_or_resp_H
+ , s_start_res
+ , s_res_H
+ , s_res_HT
+ , s_res_HTT
+ , s_res_HTTP
+ , s_res_first_http_major
+ , s_res_http_major
+ , s_res_first_http_minor
+ , s_res_http_minor
+ , s_res_first_status_code
+ , s_res_status_code
+ , s_res_status
+ , s_res_line_almost_done
+
+ , s_start_req
+
+ , s_req_method
+ , s_req_spaces_before_url
+ , s_req_schema
+ , s_req_schema_slash
+ , s_req_schema_slash_slash
+ , s_req_host
+ , s_req_port
+ , s_req_path
+ , s_req_query_string_start
+ , s_req_query_string
+ , s_req_fragment_start
+ , s_req_fragment
+ , s_req_http_start
+ , s_req_http_H
+ , s_req_http_HT
+ , s_req_http_HTT
+ , s_req_http_HTTP
+ , s_req_first_http_major
+ , s_req_http_major
+ , s_req_first_http_minor
+ , s_req_http_minor
+ , s_req_line_almost_done
+
+ , s_header_field_start
+ , s_header_field
+ , s_header_value_start
+ , s_header_value
+
+ , s_header_almost_done
+
+ , s_chunk_size_start
+ , s_chunk_size
+ , s_chunk_parameters
+ , s_chunk_size_almost_done
+
+ , s_headers_almost_done
+ /* Important: 's_headers_almost_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+
+ , s_chunk_data
+ , s_chunk_data_almost_done
+ , s_chunk_data_done
+
+ , s_body_identity
+ , s_body_identity_eof
+ };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_almost_done)
+
+
+enum header_states
+ { h_general = 0
+ , h_C
+ , h_CO
+ , h_CON
+
+ , h_matching_connection
+ , h_matching_proxy_connection
+ , h_matching_content_length
+ , h_matching_transfer_encoding
+ , h_matching_upgrade
+
+ , h_connection
+ , h_content_length
+ , h_transfer_encoding
+ , h_upgrade
+
+ , h_matching_transfer_encoding_chunked
+ , h_matching_connection_keep_alive
+ , h_matching_connection_close
+
+ , h_transfer_encoding_chunked
+ , h_connection_keep_alive
+ , h_connection_close
+ };
+
+
+enum flags
+ { F_CHUNKED = 1 << 0
+ , F_CONNECTION_KEEP_ALIVE = 1 << 1
+ , F_CONNECTION_CLOSE = 1 << 2
+ , F_TRAILING = 1 << 3
+ , F_UPGRADE = 1 << 4
+ , F_SKIPBODY = 1 << 5
+ };
+
+
+#define CR '\r'
+#define LF '\n'
+#define LOWER(c) (unsigned char)(c | 0x20)
+#define TOKEN(c) tokens[(unsigned char)c]
+
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond) if (cond) goto error
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+size_t http_parser_execute (http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len)
+{
+ char c, ch;
+ const char *p = data, *pe;
+ int64_t to_read;
+
+ enum state state = (enum state) parser->state;
+ enum header_states header_state = (enum header_states) parser->header_state;
+ uint64_t index = parser->index;
+ uint64_t nread = parser->nread;
+
+ if (len == 0) {
+ switch (state) {
+ case s_body_identity_eof:
+ CALLBACK2(message_complete);
+ return 0;
+
+ case s_dead:
+ case s_start_req_or_res:
+ case s_start_res:
+ case s_start_req:
+ return 0;
+
+ default:
+ return 1; // error
+ }
+ }
+
+ /* technically we could combine all of these (except for url_mark) into one
+ variable, saving stack space, but it seems more clear to have them
+ separated. */
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *fragment_mark = 0;
+ const char *query_string_mark = 0;
+ const char *path_mark = 0;
+ const char *url_mark = 0;
+
+ if (state == s_header_field)
+ header_field_mark = data;
+ if (state == s_header_value)
+ header_value_mark = data;
+ if (state == s_req_fragment)
+ fragment_mark = data;
+ if (state == s_req_query_string)
+ query_string_mark = data;
+ if (state == s_req_path)
+ path_mark = data;
+ if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash
+ || state == s_req_schema_slash_slash || state == s_req_port
+ || state == s_req_query_string_start || state == s_req_query_string
+ || state == s_req_host
+ || state == s_req_fragment_start || state == s_req_fragment)
+ url_mark = data;
+
+ for (p=data, pe=data+len; p != pe; p++) {
+ ch = *p;
+
+ if (PARSING_HEADER(state)) {
+ ++nread;
+ /* Buffer overflow attack */
+ if (nread > HTTP_MAX_HEADER_SIZE) goto error;
+ }
+
+ switch (state) {
+
+ case s_dead:
+ /* this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ goto error;
+
+ case s_start_req_or_res:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = -1;
+
+ CALLBACK2(message_begin);
+
+ if (ch == 'H')
+ state = s_res_or_resp_H;
+ else {
+ parser->type = HTTP_REQUEST;
+ goto start_req_method_assign;
+ }
+ break;
+ }
+
+ case s_res_or_resp_H:
+ if (ch == 'T') {
+ parser->type = HTTP_RESPONSE;
+ state = s_res_HT;
+ } else {
+ if (ch != 'E') goto error;
+ parser->type = HTTP_REQUEST;
+ parser->method = HTTP_HEAD;
+ index = 2;
+ state = s_req_method;
+ }
+ break;
+
+ case s_start_res:
+ {
+ parser->flags = 0;
+ parser->content_length = -1;
+
+ CALLBACK2(message_begin);
+
+ switch (ch) {
+ case 'H':
+ state = s_res_H;
+ break;
+
+ case CR:
+ case LF:
+ break;
+
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_res_H:
+ STRICT_CHECK(ch != 'T');
+ state = s_res_HT;
+ break;
+
+ case s_res_HT:
+ STRICT_CHECK(ch != 'T');
+ state = s_res_HTT;
+ break;
+
+ case s_res_HTT:
+ STRICT_CHECK(ch != 'P');
+ state = s_res_HTTP;
+ break;
+
+ case s_res_HTTP:
+ STRICT_CHECK(ch != '/');
+ state = s_res_first_http_major;
+ break;
+
+ case s_res_first_http_major:
+ if (ch < '1' || ch > '9') goto error;
+ parser->http_major = ch - '0';
+ state = s_res_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_res_http_major:
+ {
+ if (ch == '.') {
+ state = s_res_first_http_minor;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') goto error;
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) goto error;
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_res_first_http_minor:
+ if (ch < '0' || ch > '9') goto error;
+ parser->http_minor = ch - '0';
+ state = s_res_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_res_http_minor:
+ {
+ if (ch == ' ') {
+ state = s_res_first_status_code;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') goto error;
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) goto error;
+ break;
+ }
+
+ case s_res_first_status_code:
+ {
+ if (ch < '0' || ch > '9') {
+ if (ch == ' ') {
+ break;
+ }
+ goto error;
+ }
+ parser->status_code = ch - '0';
+ state = s_res_status_code;
+ break;
+ }
+
+ case s_res_status_code:
+ {
+ if (ch < '0' || ch > '9') {
+ switch (ch) {
+ case ' ':
+ state = s_res_status;
+ break;
+ case CR:
+ state = s_res_line_almost_done;
+ break;
+ case LF:
+ state = s_header_field_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ parser->status_code *= 10;
+ parser->status_code += ch - '0';
+
+ if (parser->status_code > 999) goto error;
+ break;
+ }
+
+ case s_res_status:
+ /* the human readable status. e.g. "NOT FOUND"
+ * we are not humans so just ignore this */
+ if (ch == CR) {
+ state = s_res_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ state = s_header_field_start;
+ break;
+ }
+ break;
+
+ case s_res_line_almost_done:
+ STRICT_CHECK(ch != LF);
+ state = s_header_field_start;
+ break;
+
+ case s_start_req:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = -1;
+
+ CALLBACK2(message_begin);
+
+ if (ch < 'A' || 'Z' < ch) goto error;
+
+ start_req_method_assign:
+ parser->method = (enum http_method) 0;
+ index = 1;
+ switch (ch) {
+ case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+ case 'D': parser->method = HTTP_DELETE; break;
+ case 'G': parser->method = HTTP_GET; break;
+ case 'H': parser->method = HTTP_HEAD; break;
+ case 'L': parser->method = HTTP_LOCK; break;
+ case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
+ case 'N': parser->method = HTTP_NOTIFY; break;
+ case 'O': parser->method = HTTP_OPTIONS; break;
+ case 'P': parser->method = HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break;
+ case 'R': parser->method = HTTP_REPORT; break;
+ case 'S': parser->method = HTTP_SUBSCRIBE; break;
+ case 'T': parser->method = HTTP_TRACE; break;
+ case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
+ default: goto error;
+ }
+ state = s_req_method;
+ break;
+ }
+
+ case s_req_method:
+ {
+ if (ch == '\0')
+ goto error;
+
+ const char *matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[index] == '\0') {
+ state = s_req_spaces_before_url;
+ } else if (ch == matcher[index]) {
+ ; /* nada */
+ } else if (parser->method == HTTP_CONNECT) {
+ if (index == 1 && ch == 'H') {
+ parser->method = HTTP_CHECKOUT;
+ } else if (index == 2 && ch == 'P') {
+ parser->method = HTTP_COPY;
+ }
+ } else if (parser->method == HTTP_MKCOL) {
+ if (index == 1 && ch == 'O') {
+ parser->method = HTTP_MOVE;
+ } else if (index == 1 && ch == 'E') {
+ parser->method = HTTP_MERGE;
+ } else if (index == 1 && ch == '-') {
+ parser->method = HTTP_MSEARCH;
+ } else if (index == 2 && ch == 'A') {
+ parser->method = HTTP_MKACTIVITY;
+ }
+ } else if (index == 1 && parser->method == HTTP_POST && ch == 'R') {
+ parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+ } else if (index == 1 && parser->method == HTTP_POST && ch == 'U') {
+ parser->method = HTTP_PUT;
+ } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {
+ parser->method = HTTP_UNSUBSCRIBE;
+ } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+ parser->method = HTTP_PROPPATCH;
+ } else {
+ goto error;
+ }
+
+ ++index;
+ break;
+ }
+ case s_req_spaces_before_url:
+ {
+ if (ch == ' ') break;
+
+ if (ch == '/' || ch == '*') {
+ MARK(url);
+ MARK(path);
+ state = s_req_path;
+ break;
+ }
+
+ c = LOWER(ch);
+
+ if (c >= 'a' && c <= 'z') {
+ MARK(url);
+ state = s_req_schema;
+ break;
+ }
+
+ goto error;
+ }
+
+ case s_req_schema:
+ {
+ c = LOWER(ch);
+
+ if (c >= 'a' && c <= 'z') break;
+
+ if (ch == ':') {
+ state = s_req_schema_slash;
+ break;
+ } else if (ch == '.') {
+ state = s_req_host;
+ break;
+ } else if ('0' <= ch && ch <= '9') {
+ state = s_req_host;
+ break;
+ }
+
+ goto error;
+ }
+
+ case s_req_schema_slash:
+ STRICT_CHECK(ch != '/');
+ state = s_req_schema_slash_slash;
+ break;
+
+ case s_req_schema_slash_slash:
+ STRICT_CHECK(ch != '/');
+ state = s_req_host;
+ break;
+
+ case s_req_host:
+ {
+ c = LOWER(ch);
+ if (c >= 'a' && c <= 'z') break;
+ if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break;
+ switch (ch) {
+ case ':':
+ state = s_req_port;
+ break;
+ case '/':
+ MARK(path);
+ state = s_req_path;
+ break;
+ case ' ':
+ /* The request line looks like:
+ * "GET http://foo.bar.com HTTP/1.1"
+ * That is, there is no path.
+ */
+ CALLBACK(url);
+ state = s_req_http_start;
+ break;
+ case '?':
+ state = s_req_query_string_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_port:
+ {
+ if (ch >= '0' && ch <= '9') break;
+ switch (ch) {
+ case '/':
+ MARK(path);
+ state = s_req_path;
+ break;
+ case ' ':
+ /* The request line looks like:
+ * "GET http://foo.bar.com:1234 HTTP/1.1"
+ * That is, there is no path.
+ */
+ CALLBACK(url);
+ state = s_req_http_start;
+ break;
+ case '?':
+ state = s_req_query_string_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_path:
+ {
+ if (normal_url_char[(unsigned char)ch]) break;
+
+ switch (ch) {
+ case ' ':
+ CALLBACK(url);
+ CALLBACK(path);
+ state = s_req_http_start;
+ break;
+ case CR:
+ CALLBACK(url);
+ CALLBACK(path);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_req_line_almost_done;
+ break;
+ case LF:
+ CALLBACK(url);
+ CALLBACK(path);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_header_field_start;
+ break;
+ case '?':
+ CALLBACK(path);
+ state = s_req_query_string_start;
+ break;
+ case '#':
+ CALLBACK(path);
+ state = s_req_fragment_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_query_string_start:
+ {
+ if (normal_url_char[(unsigned char)ch]) {
+ MARK(query_string);
+ state = s_req_query_string;
+ break;
+ }
+
+ switch (ch) {
+ case '?':
+ break; /* XXX ignore extra '?' ... is this right? */
+ case ' ':
+ CALLBACK(url);
+ state = s_req_http_start;
+ break;
+ case CR:
+ CALLBACK(url);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_req_line_almost_done;
+ break;
+ case LF:
+ CALLBACK(url);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_header_field_start;
+ break;
+ case '#':
+ state = s_req_fragment_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_query_string:
+ {
+ if (normal_url_char[(unsigned char)ch]) break;
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ break;
+ case ' ':
+ CALLBACK(url);
+ CALLBACK(query_string);
+ state = s_req_http_start;
+ break;
+ case CR:
+ CALLBACK(url);
+ CALLBACK(query_string);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_req_line_almost_done;
+ break;
+ case LF:
+ CALLBACK(url);
+ CALLBACK(query_string);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_header_field_start;
+ break;
+ case '#':
+ CALLBACK(query_string);
+ state = s_req_fragment_start;
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_fragment_start:
+ {
+ if (normal_url_char[(unsigned char)ch]) {
+ MARK(fragment);
+ state = s_req_fragment;
+ break;
+ }
+
+ switch (ch) {
+ case ' ':
+ CALLBACK(url);
+ state = s_req_http_start;
+ break;
+ case CR:
+ CALLBACK(url);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_req_line_almost_done;
+ break;
+ case LF:
+ CALLBACK(url);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_header_field_start;
+ break;
+ case '?':
+ MARK(fragment);
+ state = s_req_fragment;
+ break;
+ case '#':
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_fragment:
+ {
+ if (normal_url_char[(unsigned char)ch]) break;
+
+ switch (ch) {
+ case ' ':
+ CALLBACK(url);
+ CALLBACK(fragment);
+ state = s_req_http_start;
+ break;
+ case CR:
+ CALLBACK(url);
+ CALLBACK(fragment);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_req_line_almost_done;
+ break;
+ case LF:
+ CALLBACK(url);
+ CALLBACK(fragment);
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ state = s_header_field_start;
+ break;
+ case '?':
+ case '#':
+ break;
+ default:
+ goto error;
+ }
+ break;
+ }
+
+ case s_req_http_start:
+ switch (ch) {
+ case 'H':
+ state = s_req_http_H;
+ break;
+ case ' ':
+ break;
+ default:
+ goto error;
+ }
+ break;
+
+ case s_req_http_H:
+ STRICT_CHECK(ch != 'T');
+ state = s_req_http_HT;
+ break;
+
+ case s_req_http_HT:
+ STRICT_CHECK(ch != 'T');
+ state = s_req_http_HTT;
+ break;
+
+ case s_req_http_HTT:
+ STRICT_CHECK(ch != 'P');
+ state = s_req_http_HTTP;
+ break;
+
+ case s_req_http_HTTP:
+ STRICT_CHECK(ch != '/');
+ state = s_req_first_http_major;
+ break;
+
+ /* first digit of major HTTP version */
+ case s_req_first_http_major:
+ if (ch < '1' || ch > '9') goto error;
+ parser->http_major = ch - '0';
+ state = s_req_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_req_http_major:
+ {
+ if (ch == '.') {
+ state = s_req_first_http_minor;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') goto error;
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) goto error;
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_req_first_http_minor:
+ if (ch < '0' || ch > '9') goto error;
+ parser->http_minor = ch - '0';
+ state = s_req_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_req_http_minor:
+ {
+ if (ch == CR) {
+ state = s_req_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ state = s_header_field_start;
+ break;
+ }
+
+ /* XXX allow spaces after digit? */
+
+ if (ch < '0' || ch > '9') goto error;
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) goto error;
+ break;
+ }
+
+ /* end of request line */
+ case s_req_line_almost_done:
+ {
+ if (ch != LF) goto error;
+ state = s_header_field_start;
+ break;
+ }
+
+ case s_header_field_start:
+ {
+ if (ch == CR) {
+ state = s_headers_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ state = s_headers_almost_done;
+ goto headers_almost_done;
+ }
+
+ c = TOKEN(ch);
+
+ if (!c) goto error;
+
+ MARK(header_field);
+
+ index = 0;
+ state = s_header_field;
+
+ switch (c) {
+ case 'c':
+ header_state = h_C;
+ break;
+
+ case 'p':
+ header_state = h_matching_proxy_connection;
+ break;
+
+ case 't':
+ header_state = h_matching_transfer_encoding;
+ break;
+
+ case 'u':
+ header_state = h_matching_upgrade;
+ break;
+
+ default:
+ header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_field:
+ {
+ c = TOKEN(ch);
+
+ if (c) {
+ switch (header_state) {
+ case h_general:
+ break;
+
+ case h_C:
+ index++;
+ header_state = (c == 'o' ? h_CO : h_general);
+ break;
+
+ case h_CO:
+ index++;
+ header_state = (c == 'n' ? h_CON : h_general);
+ break;
+
+ case h_CON:
+ index++;
+ switch (c) {
+ case 'n':
+ header_state = h_matching_connection;
+ break;
+ case 't':
+ header_state = h_matching_content_length;
+ break;
+ default:
+ header_state = h_general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case h_matching_connection:
+ index++;
+ if (index > sizeof(CONNECTION)-1
+ || c != CONNECTION[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(CONNECTION)-2) {
+ header_state = h_connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case h_matching_proxy_connection:
+ index++;
+ if (index > sizeof(PROXY_CONNECTION)-1
+ || c != PROXY_CONNECTION[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(PROXY_CONNECTION)-2) {
+ header_state = h_connection;
+ }
+ break;
+
+ /* content-length */
+
+ case h_matching_content_length:
+ index++;
+ if (index > sizeof(CONTENT_LENGTH)-1
+ || c != CONTENT_LENGTH[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(CONTENT_LENGTH)-2) {
+ header_state = h_content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case h_matching_transfer_encoding:
+ index++;
+ if (index > sizeof(TRANSFER_ENCODING)-1
+ || c != TRANSFER_ENCODING[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(TRANSFER_ENCODING)-2) {
+ header_state = h_transfer_encoding;
+ }
+ break;
+
+ /* upgrade */
+
+ case h_matching_upgrade:
+ index++;
+ if (index > sizeof(UPGRADE)-1
+ || c != UPGRADE[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(UPGRADE)-2) {
+ header_state = h_upgrade;
+ }
+ break;
+
+ case h_connection:
+ case h_content_length:
+ case h_transfer_encoding:
+ case h_upgrade:
+ if (ch != ' ') header_state = h_general;
+ break;
+
+ default:
+ assert(0 && "Unknown header_state");
+ break;
+ }
+ break;
+ }
+
+ if (ch == ':') {
+ CALLBACK(header_field);
+ state = s_header_value_start;
+ break;
+ }
+
+ if (ch == CR) {
+ state = s_header_almost_done;
+ CALLBACK(header_field);
+ break;
+ }
+
+ if (ch == LF) {
+ CALLBACK(header_field);
+ state = s_header_field_start;
+ break;
+ }
+
+ goto error;
+ }
+
+ case s_header_value_start:
+ {
+ if (ch == ' ') break;
+
+ MARK(header_value);
+
+ state = s_header_value;
+ index = 0;
+
+ c = LOWER(ch);
+
+ if (ch == CR) {
+ CALLBACK(header_value);
+ header_state = h_general;
+ state = s_header_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ CALLBACK(header_value);
+ state = s_header_field_start;
+ break;
+ }
+
+ switch (header_state) {
+ case h_upgrade:
+ parser->flags |= F_UPGRADE;
+ header_state = h_general;
+ break;
+
+ case h_transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ header_state = h_matching_transfer_encoding_chunked;
+ } else {
+ header_state = h_general;
+ }
+ break;
+
+ case h_content_length:
+ if (ch < '0' || ch > '9') goto error;
+ parser->content_length = ch - '0';
+ break;
+
+ case h_connection:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ header_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ header_state = h_matching_connection_close;
+ } else {
+ header_state = h_general;
+ }
+ break;
+
+ default:
+ header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_value:
+ {
+ c = LOWER(ch);
+
+ if (ch == CR) {
+ CALLBACK(header_value);
+ state = s_header_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ CALLBACK(header_value);
+ goto header_almost_done;
+ }
+
+ switch (header_state) {
+ case h_general:
+ break;
+
+ case h_connection:
+ case h_transfer_encoding:
+ assert(0 && "Shouldn't get here.");
+ break;
+
+ case h_content_length:
+ if (ch == ' ') break;
+ if (ch < '0' || ch > '9') goto error;
+ parser->content_length *= 10;
+ parser->content_length += ch - '0';
+ break;
+
+ /* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_chunked:
+ index++;
+ if (index > sizeof(CHUNKED)-1
+ || c != CHUNKED[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(CHUNKED)-2) {
+ header_state = h_transfer_encoding_chunked;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case h_matching_connection_keep_alive:
+ index++;
+ if (index > sizeof(KEEP_ALIVE)-1
+ || c != KEEP_ALIVE[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(KEEP_ALIVE)-2) {
+ header_state = h_connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case h_matching_connection_close:
+ index++;
+ if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {
+ header_state = h_general;
+ } else if (index == sizeof(CLOSE)-2) {
+ header_state = h_connection_close;
+ }
+ break;
+
+ case h_transfer_encoding_chunked:
+ case h_connection_keep_alive:
+ case h_connection_close:
+ if (ch != ' ') header_state = h_general;
+ break;
+
+ default:
+ state = s_header_value;
+ header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_almost_done:
+ header_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ state = s_header_field_start;
+
+ switch (header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case s_headers_almost_done:
+ headers_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ if (parser->flags & F_TRAILING) {
+ /* End of a chunked request */
+ CALLBACK2(message_complete);
+ state = NEW_MESSAGE();
+ break;
+ }
+
+ nread = 0;
+
+ if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) {
+ parser->upgrade = 1;
+ }
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ */
+ if (settings->on_headers_complete) {
+ switch (settings->on_headers_complete(parser)) {
+ case 0:
+ break;
+
+ case 1:
+ parser->flags |= F_SKIPBODY;
+ break;
+
+ default:
+ parser->state = state;
+ return p - data; /* Error */
+ }
+ }
+
+ /* Exit, the rest of the connect is in a different protocol. */
+ if (parser->upgrade) {
+ CALLBACK2(message_complete);
+ return (p - data);
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ CALLBACK2(message_complete);
+ state = NEW_MESSAGE();
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header */
+ state = s_chunk_size_start;
+ } else {
+ if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ CALLBACK2(message_complete);
+ state = NEW_MESSAGE();
+ } else if (parser->content_length > 0) {
+ /* Content-Length header given and non-zero */
+ state = s_body_identity;
+ } else {
+ if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) {
+ /* Assume content-length 0 - read the next */
+ CALLBACK2(message_complete);
+ state = NEW_MESSAGE();
+ } else {
+ /* Read body until EOF */
+ state = s_body_identity_eof;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case s_body_identity:
+ to_read = MIN(pe - p, (int64_t)parser->content_length);
+ if (to_read > 0) {
+ if (settings->on_body) settings->on_body(parser, p, to_read);
+ p += to_read - 1;
+ parser->content_length -= to_read;
+ if (parser->content_length == 0) {
+ CALLBACK2(message_complete);
+ state = NEW_MESSAGE();
+ }
+ }
+ break;
+
+ /* read until EOF */
+ case s_body_identity_eof:
+ to_read = pe - p;
+ if (to_read > 0) {
+ if (settings->on_body) settings->on_body(parser, p, to_read);
+ p += to_read - 1;
+ }
+ break;
+
+ case s_chunk_size_start:
+ {
+ assert(nread == 1);
+ assert(parser->flags & F_CHUNKED);
+
+ c = unhex[(unsigned char)ch];
+ if (c == -1) goto error;
+ parser->content_length = c;
+ state = s_chunk_size;
+ break;
+ }
+
+ case s_chunk_size:
+ {
+ assert(parser->flags & F_CHUNKED);
+
+ if (ch == CR) {
+ state = s_chunk_size_almost_done;
+ break;
+ }
+
+ c = unhex[(unsigned char)ch];
+
+ if (c == -1) {
+ if (ch == ';' || ch == ' ') {
+ state = s_chunk_parameters;
+ break;
+ }
+ goto error;
+ }
+
+ parser->content_length *= 16;
+ parser->content_length += c;
+ break;
+ }
+
+ case s_chunk_parameters:
+ {