diff options
Diffstat (limited to 'debian/vendor-h2o/deps/picohttpparser')
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/.clang-format | 7 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/.travis.yml | 6 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/Jamfile | 7 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/Makefile | 40 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/README.md | 116 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/bench.c | 66 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/picohttpparser.c | 620 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/picohttpparser.h | 89 | ||||
-rw-r--r-- | debian/vendor-h2o/deps/picohttpparser/test.c | 419 |
9 files changed, 1370 insertions, 0 deletions
diff --git a/debian/vendor-h2o/deps/picohttpparser/.clang-format b/debian/vendor-h2o/deps/picohttpparser/.clang-format new file mode 100644 index 0000000..9640123 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/.clang-format @@ -0,0 +1,7 @@ +# requires clang-format >= 3.6 +BasedOnStyle: "LLVM" +IndentWidth: 4 +ColumnLimit: 132 +BreakBeforeBraces: Linux +AllowShortFunctionsOnASingleLine: None +SortIncludes: false diff --git a/debian/vendor-h2o/deps/picohttpparser/.travis.yml b/debian/vendor-h2o/deps/picohttpparser/.travis.yml new file mode 100644 index 0000000..78af54b --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/.travis.yml @@ -0,0 +1,6 @@ +language: c +compiler: + - gcc + - clang +script: + - make test diff --git a/debian/vendor-h2o/deps/picohttpparser/Jamfile b/debian/vendor-h2o/deps/picohttpparser/Jamfile new file mode 100644 index 0000000..5acbe55 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/Jamfile @@ -0,0 +1,7 @@ +project picohttpparser ; + +lib picohttpparser : picohttpparser.c ; + +unit-test test + : picohttpparser picotest/picotest.c test.c + : <testing.launcher>prove ; diff --git a/debian/vendor-h2o/deps/picohttpparser/Makefile b/debian/vendor-h2o/deps/picohttpparser/Makefile new file mode 100644 index 0000000..9e42298 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/Makefile @@ -0,0 +1,40 @@ +# +# Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, +# Shigeo Mitsunari +# +# The software is licensed under either the MIT License (below) or the Perl +# license. +# +# 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. + +CC?=gcc +PROVE?=prove + +all: + +test: test-bin + $(PROVE) -v ./test-bin + +test-bin: picohttpparser.c picotest/picotest.c test.c + $(CC) -Wall $(CFLAGS) $(LDFLAGS) -o $@ $^ + +clean: + rm -f test-bin + +.PHONY: test diff --git a/debian/vendor-h2o/deps/picohttpparser/README.md b/debian/vendor-h2o/deps/picohttpparser/README.md new file mode 100644 index 0000000..cb32f58 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/README.md @@ -0,0 +1,116 @@ +PicoHTTPParser +============= + +Copyright (c) 2009-2014 [Kazuho Oku](https://github.com/kazuho), [Tokuhiro Matsuno](https://github.com/tokuhirom), [Daisuke Murase](https://github.com/typester), [Shigeo Mitsunari](https://github.com/herumi) + +PicoHTTPParser is a tiny, primitive, fast HTTP request/response parser. + +Unlike most parsers, it is stateless and does not allocate memory by itself. +All it does is accept pointer to buffer and the output structure, and setups the pointers in the latter to point at the necessary portions of the buffer. + +The code is widely deployed within Perl applications through popular modules that use it, including [Plack](https://metacpan.org/pod/Plack), [Starman](https://metacpan.org/pod/Starman), [Starlet](https://metacpan.org/pod/Starlet), [Furl](https://metacpan.org/pod/Furl). It is also the HTTP/1 parser of [H2O](https://github.com/h2o/h2o). + +Check out [test.c] to find out how to use the parser. + +The software is dual-licensed under the Perl License or the MIT License. + +Usage +----- + +The library exposes four functions: `phr_parse_request`, `phr_parse_response`, `phr_parse_headers`, `phr_decode_chunked`. + +### phr_parse_request + +The example below reads an HTTP request from socket `sock` using `read(2)`, parses it using `phr_parse_request`, and prints the details. + +```c +char buf[4096], *method, *path; +int pret, minor_version; +struct phr_header headers[100]; +size_t buflen = 0, prevbuflen = 0, method_len, path_len, num_headers; +ssize_t rret; + +while (1) { + /* read the request */ + while ((rret = read(sock, buf + buflen, sizeof(buf) - buflen)) == -1 && errno == EINTR) + ; + if (rret <= 0) + return IOError; + prevbuflen = buflen; + buflen += rret; + /* parse the request */ + num_headers = sizeof(headers) / sizeof(headers[0]); + pret = phr_parse_request(buf, buflen, &method, &method_len, &path, &path_len, + &minor_version, headers, &num_headers, prevbuflen); + if (pret > 0) + break; /* successfully parsed the request */ + else if (pret == -1) + return ParseError; + /* request is incomplete, continue the loop */ + assert(pret == -2); + if (buflen == sizeof(buf)) + return RequestIsTooLongError; +} + +printf("request is %d bytes long\n", pret); +printf("method is %.*s\n", (int)method_len, method); +printf("path is %.*s\n", (int)path_len, path); +printf("HTTP version is 1.%d\n", minor_version); +printf("headers:\n"); +for (i = 0; i != num_headers; ++i) { + printf("%.*s: %.*s\n", (int)headers[i].name_len, headers[i].name, + (int)headers[i].value_len, headers[i].value); +} +``` + +### phr_parse_response, phr_parse_headers + +`phr_parse_response` and `phr_parse_headers` provide similar interfaces as `phr_parse_request`. `phr_parse_response` parses an HTTP response, and `phr_parse_headers` parses the headers only. + +### phr_decode_chunked + +The example below decodes incoming data in chunked-encoding. The data is decoded in-place. + +```c +struct phr_chunked_decoder decoder = {}; /* zero-clear */ +char *buf = malloc(4096); +size_t size = 0, capacity = 4096, rsize; +ssize_t rret, pret; + +/* set consume_trailer to 1 to discard the trailing header, or the application + * should call phr_parse_headers to parse the trailing header */ +decoder.consume_trailer = 1; + +do { + /* expand the buffer if necessary */ + if (size == capacity) { + capacity *= 2; + buf = realloc(buf, capacity); + assert(buf != NULL); + } + /* read */ + while ((rret = read(sock, buf + size, capacity - size)) == -1 && errno == EINTR) + ; + if (rret <= 0) + return IOError; + /* decode */ + rsize = rret; + pret = phr_decode_chunked(&decoder, buf + size, &rsize); + if (pret == -1) + return ParseError; + size += rsize; +} while (pret == -2); + +/* successfully decoded the chunked data */ +assert(pret >= 0); +printf("decoded data is at %p (%zu bytes)\n", buf, size); +``` + +Benchmark +--------- + +![benchmark results](http://i.gyazo.com/a85c18d3162dfb46b485bb41e0ad443a.png) + +The benchmark code is from [fukamachi/fast-http@6b91103](https://github.com/fukamachi/fast-http/tree/6b9110347c7a3407310c08979aefd65078518478). + +The internals of picohttpparser has been described to some extent in [my blog entry]( http://blog.kazuhooku.com/2014/11/the-internals-h2o-or-how-to-write-fast.html). diff --git a/debian/vendor-h2o/deps/picohttpparser/bench.c b/debian/vendor-h2o/deps/picohttpparser/bench.c new file mode 100644 index 0000000..8dec06c --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/bench.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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 <assert.h> +#include <stdio.h> +#include "picohttpparser.h" + +#define REQ \ + "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n" \ + "Host: www.kittyhell.com\r\n" \ + "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 " \ + "Pathtraq/0.9\r\n" \ + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" \ + "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n" \ + "Accept-Encoding: gzip,deflate\r\n" \ + "Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n" \ + "Keep-Alive: 115\r\n" \ + "Connection: keep-alive\r\n" \ + "Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; " \ + "__utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; " \ + "__utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n" \ + "\r\n" + +int main(void) +{ + const char *method; + size_t method_len; + const char *path; + size_t path_len; + int minor_version; + struct phr_header headers[32]; + size_t num_headers; + int i, ret; + + for (i = 0; i < 10000000; i++) { + num_headers = sizeof(headers) / sizeof(headers[0]); + ret = phr_parse_request(REQ, sizeof(REQ) - 1, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, + 0); + assert(ret == sizeof(REQ) - 1); + } + + return 0; +} diff --git a/debian/vendor-h2o/deps/picohttpparser/picohttpparser.c b/debian/vendor-h2o/deps/picohttpparser/picohttpparser.c new file mode 100644 index 0000000..6a2d872 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/picohttpparser.c @@ -0,0 +1,620 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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 <assert.h> +#include <stddef.h> +#include <string.h> +#ifdef __SSE4_2__ +#ifdef _MSC_VER +#include <nmmintrin.h> +#else +#include <x86intrin.h> +#endif +#endif +#include "picohttpparser.h" + +/* $Id: a707070d11d499609f99d09f97535642cec910a8 $ */ + +#if __GNUC__ >= 3 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifdef _MSC_VER +#define ALIGNED(n) _declspec(align(n)) +#else +#define ALIGNED(n) __attribute__((aligned(n))) +#endif + +#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) + +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } + +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } + +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); + +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) + +static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\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\0\0\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\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) +{ + *found = 0; +#if __SSE4_2__ + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i *)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#else + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; +#endif + return buf; +} + +static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) +{ + const char *token_start = buf; + +#ifdef __SSE4_2__ + static const char ranges1[] = "\0\010" + /* allow HT */ + "\012\037" + /* allow SP and up to but not including DEL */ + "\177\177" + /* allow chars w. MSB set */ + ; + int found; + buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + if (found) + goto FOUND_CTL; +#else + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); +#undef DOIT + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + ++buf; + } +#endif + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + } + } +FOUND_CTL: + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; + return NULL; + } + *token = token_start; + + return buf; +} + +static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) +{ + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; + + while (1) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; + } else if (*buf == '\012') { + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; + } + if (ret_cnt == 2) { + return buf; + } + } + + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) +{ + /* we want at least [HTTP/1.<two chars>] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + for (;; ++*num_headers) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + break; + } else if (*buf == '\012') { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ + headers[*num_headers].name = buf; + static const char ALIGNED(16) ranges1[] = "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\377"; /* 0x7b-0xff */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == ':') { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + if ((headers[*num_headers].name_len = buf - headers[*num_headers].name) == 0) { + *ret = -1; + return NULL; + } + ++buf; + for (;; ++buf) { + CHECK_EOF(); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } + } else { + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; + } + if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, &headers[*num_headers].value_len, ret)) == NULL) { + return NULL; + } + } + return buf; +} + +static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } + + /* parse request line */ + ADVANCE_TOKEN(*method, *method_len); + ++buf; + ADVANCE_TOKEN(*path, *path_len); + ++buf; + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r; + + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; + + /* if last_len != 0, check if the request is complete (a fast countermeasure + againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, + size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) +{ + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf++ != ' ') { + *ret = -1; + return NULL; + } + /* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* skip space */ + if (*buf++ != ' ') { + *ret = -1; + return NULL; + } + /* get message */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +enum { + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE +}; + +static int decode_hex(int ch) +{ + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } +} + +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) +{ + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ + + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; + } + break; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + } else { + goto Complete; + } + } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); + } + } + +Complete: + ret = bufsz - src; +Exit: + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; +} + +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) +{ + return decoder->_state == CHUNKED_IN_CHUNK_DATA; +} + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef ADVANCE_TOKEN diff --git a/debian/vendor-h2o/deps/picohttpparser/picohttpparser.h b/debian/vendor-h2o/deps/picohttpparser/picohttpparser.h new file mode 100644 index 0000000..a8fad71 --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/picohttpparser.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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. + */ + +#ifndef picohttpparser_h +#define picohttpparser_h + +#include <sys/types.h> + +#ifdef _MSC_VER +#define ssize_t intptr_t +#endif + +/* $Id: 67fd3ee74103ada60258d8a16e868f483abcca87 $ */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* contains name and value of a header (name == NULL if is a continuing line + * of a multiline header */ +struct phr_header { + const char *name; + size_t name_len; + const char *value; + size_t value_len; +}; + +/* returns number of bytes consumed if successful, -2 if request is partial, + * -1 if failed */ +int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* should be zero-filled before start */ +struct phr_chunked_decoder { + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; +}; + +/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- + * encoding headers. When the function returns without an error, bufsz is + * updated to the length of the decoded data available. Applications should + * repeatedly call the function while it returns -2 (incomplete) every time + * supplying newly arrived data. If the end of the chunked-encoded data is + * found, the function returns a non-negative number indicating the number of + * octets left undecoded at the tail of the supplied buffer. Returns -1 on + * error. + */ +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); + +/* returns if the chunked decoder is in middle of chunked data */ +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/debian/vendor-h2o/deps/picohttpparser/test.c b/debian/vendor-h2o/deps/picohttpparser/test.c new file mode 100644 index 0000000..8f808fd --- /dev/null +++ b/debian/vendor-h2o/deps/picohttpparser/test.c @@ -0,0 +1,419 @@ +/* use `make test` to run the test */ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "picotest/picotest.h" +#include "picohttpparser.h" + +static int bufis(const char *s, size_t l, const char *t) +{ + return strlen(t) == l && memcmp(s, t, l) == 0; +} + +static void test_request(void) +{ + const char *method; + size_t method_len; + const char *path; + size_t path_len; + int minor_version; + struct phr_header headers[4]; + size_t num_headers; + +#define PARSE(s, last_len, exp, comment) \ + do { \ + note(comment); \ + num_headers = sizeof(headers) / sizeof(headers[0]); \ + ok(phr_parse_request(s, sizeof(s) - 1, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, \ + last_len) == (exp == 0 ? strlen(s) : exp)); \ + } while (0) + + PARSE("GET / HTTP/1.0\r\n\r\n", 0, 0, "simple"); + ok(num_headers == 0); + ok(bufis(method, method_len, "GET")); + ok(bufis(path, path_len, "/")); + ok(minor_version == 0); + + PARSE("GET / HTTP/1.0\r\n\r", 0, -2, "partial"); + + PARSE("GET /hoge HTTP/1.1\r\nHost: example.com\r\nCookie: \r\n\r\n", 0, 0, "parse headers"); + ok(num_headers == 2); + ok(bufis(method, method_len, "GET")); + ok(bufis(path, path_len, "/hoge")); + ok(minor_version == 1); + ok(bufis(headers[0].name, headers[0].name_len, "Host")); + ok(bufis(headers[0].value, headers[0].value_len, "example.com")); + ok(bufis(headers[1].name, headers[1].name_len, "Cookie")); + ok(bufis(headers[1].value, headers[1].value_len, "")); + + PARSE("GET /hoge HTTP/1.1\r\nHost: example.com\r\nUser-Agent: \343\201\262\343/1.0\r\n\r\n", 0, 0, "multibyte included"); + ok(num_headers == 2); + ok(bufis(method, method_len, "GET")); + ok(bufis(path, path_len, "/hoge")); + ok(minor_version == 1); + ok(bufis(headers[0].name, headers[0].name_len, "Host")); + ok(bufis(headers[0].value, headers[0].value_len, "example.com")); + ok(bufis(headers[1].name, headers[1].name_len, "User-Agent")); + ok(bufis(headers[1].value, headers[1].value_len, "\343\201\262\343/1.0")); + + PARSE("GET / HTTP/1.0\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n", 0, 0, "parse multiline"); + ok(num_headers == 3); + ok(bufis(method, method_len, "GET")); + ok(bufis(path, path_len, "/")); + ok(minor_version == 0); + ok(bufis(headers[0].name, headers[0].name_len, "foo")); + ok(bufis(headers[0].value, headers[0].value_len, "")); + ok(bufis(headers[1].name, headers[1].name_len, "foo")); + ok(bufis(headers[1].value, headers[1].value_len, "b")); + ok(headers[2].name == NULL); + ok(bufis(headers[2].value, headers[2].value_len, " \tc")); + + PARSE("GET / HTTP/1.0\r\nfoo : ab\r\n\r\n", 0, -1, "parse header name with trailing space"); + + PARSE("GET", 0, -2, "incomplete 1"); + ok(method == NULL); + PARSE("GET ", 0, -2, "incomplete 2"); + ok(bufis(method, method_len, "GET")); + PARSE("GET /", 0, -2, "incomplete 3"); + ok(path == NULL); + PARSE("GET / ", 0, -2, "incomplete 4"); + ok(bufis(path, path_len, "/")); + PARSE("GET / H", 0, -2, "incomplete 5"); + PARSE("GET / HTTP/1.", 0, -2, "incomplete 6"); + PARSE("GET / HTTP/1.0", 0, -2, "incomplete 7"); + ok(minor_version == -1); + PARSE("GET / HTTP/1.0\r", 0, -2, "incomplete 8"); + ok(minor_version == 0); + + PARSE("GET /hoge HTTP/1.0\r\n\r", strlen("GET /hoge HTTP/1.0\r\n\r") - 1, -2, "slowloris (incomplete)"); + PARSE("GET /hoge HTTP/1.0\r\n\r\n", strlen("GET /hoge HTTP/1.0\r\n\r\n") - 1, 0, "slowloris (complete)"); + + PARSE("GET / HTTP/1.0\r\n:a\r\n\r\n", 0, -1, "empty header name"); + PARSE("GET / HTTP/1.0\r\n :a\r\n\r\n", 0, -1, "header name (space only)"); + + PARSE("G\0T / HTTP/1.0\r\n\r\n", 0, -1, "NUL in method"); + PARSE("G\tT / HTTP/1.0\r\n\r\n", 0, -1, "tab in method"); + PARSE("GET /\x7fhello HTTP/1.0\r\n\r\n", 0, -1, "DEL in uri-path"); + PARSE("GET / HTTP/1.0\r\na\0b: c\r\n\r\n", 0, -1, "NUL in header name"); + PARSE("GET / HTTP/1.0\r\nab: c\0d\r\n\r\n", 0, -1, "NUL in header value"); + PARSE("GET / HTTP/1.0\r\na\033b: c\r\n\r\n", 0, -1, "CTL in header name"); + PARSE("GET / HTTP/1.0\r\nab: c\033\r\n\r\n", 0, -1, "CTL in header value"); + PARSE("GET / HTTP/1.0\r\n/: 1\r\n\r\n", 0, -1, "invalid char in header value"); + PARSE("GET /\xa0 HTTP/1.0\r\nh: c\xa2y\r\n\r\n", 0, 0, "accept MSB chars"); + ok(num_headers == 1); + ok(bufis(method, method_len, "GET")); + ok(bufis(path, path_len, "/\xa0")); + ok(minor_version == 0); + ok(bufis(headers[0].name, headers[0].name_len, "h")); + ok(bufis(headers[0].value, headers[0].value_len, "c\xa2y")); + + PARSE("GET / HTTP/1.0\r\n\x7c\x7e: 1\r\n\r\n", 0, 0, "accept |~ (though forbidden by SSE)"); + ok(num_headers == 1); + ok(bufis(headers[0].name, headers[0].name_len, "\x7c\x7e")); + ok(bufis(headers[0].value, headers[0].value_len, "1")); + + PARSE("GET / HTTP/1.0\r\n\x7b: 1\r\n\r\n", 0, -1, "disallow {"); + +#undef PARSE +} + +static void test_response(void) +{ + int minor_version; + int status; + const char *msg; + size_t msg_len; + struct phr_header headers[4]; + size_t num_headers; + +#define PARSE(s, last_len, exp, comment) \ + do { \ + note(comment); \ + num_headers = sizeof(headers) / sizeof(headers[0]); \ + ok(phr_parse_response(s, strlen(s), &minor_version, &status, &msg, &msg_len, headers, &num_headers, last_len) == \ + (exp == 0 ? strlen(s) : exp)); \ + } while (0) + + PARSE("HTTP/1.0 200 OK\r\n\r\n", 0, 0, "simple"); + ok(num_headers == 0); + ok(status == 200); + ok(minor_version == 0); + ok(bufis(msg, msg_len, "OK")); + + PARSE("HTTP/1.0 200 OK\r\n\r", 0, -2, "partial"); + + PARSE("HTTP/1.1 200 OK\r\nHost: example.com\r\nCookie: \r\n\r\n", 0, 0, "parse headers"); + ok(num_headers == 2); + ok(minor_version == 1); + ok(status == 200); + ok(bufis(msg, msg_len, "OK")); + ok(bufis(headers[0].name, headers[0].name_len, "Host")); + ok(bufis(headers[0].value, headers[0].value_len, "example.com")); + ok(bufis(headers[1].name, headers[1].name_len, "Cookie")); + ok(bufis(headers[1].value, headers[1].value_len, "")); + + PARSE("HTTP/1.0 200 OK\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n", 0, 0, "parse multiline"); + ok(num_headers == 3); + ok(minor_version == 0); + ok(status == 200); + ok(bufis(msg, msg_len, "OK")); + ok(bufis(headers[0].name, headers[0].name_len, "foo")); + ok(bufis(headers[0].value, headers[0].value_len, "")); + ok(bufis(headers[1].name, headers[1].name_len, "foo")); + ok(bufis(headers[1].value, headers[1].value_len, "b")); + ok(headers[2].name == NULL); + ok(bufis(headers[2].value, headers[2].value_len, " \tc")); + + PARSE("HTTP/1.0 500 Internal Server Error\r\n\r\n", 0, 0, "internal server error"); + ok(num_headers == 0); + ok(minor_version == 0); + ok(status == 500); + ok(bufis(msg, msg_len, "Internal Server Error")); + ok(msg_len == sizeof("Internal Server Error") - 1); + + PARSE("H", 0, -2, "incomplete 1"); + PARSE("HTTP/1.", 0, -2, "incomplete 2"); + PARSE("HTTP/1.1", 0, -2, "incomplete 3"); + ok(minor_version == -1); + PARSE("HTTP/1.1 ", 0, -2, "incomplete 4"); + ok(minor_version == 1); + PARSE("HTTP/1.1 2", 0, -2, "incomplete 5"); + PARSE("HTTP/1.1 200", 0, -2, "incomplete 6"); + ok(status == 0); + PARSE("HTTP/1.1 200 ", 0, -2, "incomplete 7"); + ok(status == 200); + PARSE("HTTP/1.1 200 O", 0, -2, "incomplete 8"); + PARSE("HTTP/1.1 200 OK\r", 0, -2, "incomplete 9"); + ok(msg == NULL); + PARSE("HTTP/1.1 200 OK\r\n", 0, -2, "incomplete 10"); + ok(bufis(msg, msg_len, "OK")); + PARSE("HTTP/1.1 200 OK\n", 0, -2, "incomplete 11"); + ok(bufis(msg, msg_len, "OK")); + + PARSE("HTTP/1.1 200 OK\r\nA: 1\r", 0, -2, "incomplete 11"); + ok(num_headers == 0); + PARSE("HTTP/1.1 200 OK\r\nA: 1\r\n", 0, -2, "incomplete 12"); + ok(num_headers == 1); + ok(bufis(headers[0].name, headers[0].name_len, "A")); + ok(bufis(headers[0].value, headers[0].value_len, "1")); + + PARSE("HTTP/1.0 200 OK\r\n\r", strlen("HTTP/1.0 200 OK\r\n\r") - 1, -2, "slowloris (incomplete)"); + PARSE("HTTP/1.0 200 OK\r\n\r\n", strlen("HTTP/1.0 200 OK\r\n\r\n") - 1, 0, "slowloris (complete)"); + + PARSE("HTTP/1. 200 OK\r\n\r\n", 0, -1, "invalid http version"); + PARSE("HTTP/1.2z 200 OK\r\n\r\n", 0, -1, "invalid http version 2"); + PARSE("HTTP/1.1 OK\r\n\r\n", 0, -1, "no status code"); + +#undef PARSE +} + +static void test_headers(void) +{ + /* only test the interface; the core parser is tested by the tests above */ + + struct phr_header headers[4]; + size_t num_headers; + +#define PARSE(s, last_len, exp, comment) \ + do { \ + note(comment); \ + num_headers = sizeof(headers) / sizeof(headers[0]); \ + ok(phr_parse_headers(s, strlen(s), headers, &num_headers, last_len) == (exp == 0 ? strlen(s) : exp)); \ + } while (0) + + PARSE("Host: example.com\r\nCookie: \r\n\r\n", 0, 0, "simple"); + ok(num_headers == 2); + ok(bufis(headers[0].name, headers[0].name_len, "Host")); + ok(bufis(headers[0].value, headers[0].value_len, "example.com")); + ok(bufis(headers[1].name, headers[1].name_len, "Cookie")); + ok(bufis(headers[1].value, headers[1].value_len, "")); + + PARSE("Host: example.com\r\nCookie: \r\n\r\n", 1, 0, "slowloris"); + ok(num_headers == 2); + ok(bufis(headers[0].name, headers[0].name_len, "Host")); + ok(bufis(headers[0].value, headers[0].value_len, "example.com")); + ok(bufis(headers[1].name, headers[1].name_len, "Cookie")); + ok(bufis(headers[1].value, headers[1].value_len, "")); + + PARSE("Host: example.com\r\nCookie: \r\n\r", 0, -2, "partial"); + + PARSE("Host: e\7fample.com\r\nCookie: \r\n\r", 0, -1, "error"); + +#undef PARSE +} + +static void test_chunked_at_once(int line, int consume_trailer, const char *encoded, const char *decoded, ssize_t expected) +{ + struct phr_chunked_decoder dec = {0}; + char *buf; + size_t bufsz; + ssize_t ret; + + dec.consume_trailer = consume_trailer; + + note("testing at-once, source at line %d", line); + + buf = strdup(encoded); + bufsz = strlen(buf); + + ret = phr_decode_chunked(&dec, buf, &bufsz); + + ok(ret == expected); + ok(bufsz == strlen(decoded)); + ok(bufis(buf, bufsz, decoded)); + if (expected >= 0) { + if (ret == expected) + ok(bufis(buf + bufsz, ret, encoded + strlen(encoded) - ret)); + else + ok(0); + } + + free(buf); +} + +static void test_chunked_per_byte(int line, int consume_trailer, const char *encoded, const char *decoded, ssize_t expected) +{ + struct phr_chunked_decoder dec = {0}; + char *buf = malloc(strlen(encoded) + 1); + size_t bytes_to_consume = strlen(encoded) - (expected >= 0 ? expected : 0), bytes_ready = 0, bufsz, i; + ssize_t ret; + + dec.consume_trailer = consume_trailer; + + note("testing per-byte, source at line %d", line); + + for (i = 0; i < bytes_to_consume - 1; ++i) { + buf[bytes_ready] = encoded[i]; + bufsz = 1; + ret = phr_decode_chunked(&dec, buf + bytes_ready, &bufsz); + if (ret != -2) { + ok(0); + goto cleanup; + } + bytes_ready += bufsz; + } + strcpy(buf + bytes_ready, encoded + bytes_to_consume - 1); + bufsz = strlen(buf + bytes_ready); + ret = phr_decode_chunked(&dec, buf + bytes_ready, &bufsz); + ok(ret == expected); + bytes_ready += bufsz; + ok(bytes_ready == strlen(decoded)); + ok(bufis(buf, bytes_ready, decoded)); + if (expected >= 0) { + if (ret == expected) + ok(bufis(buf + bytes_ready, expected, encoded + bytes_to_consume)); + else + ok(0); + } + +cleanup: + free(buf); +} + +static void test_chunked_failure(int line, const char *encoded, ssize_t expected) +{ + struct phr_chunked_decoder dec = {0}; + char *buf = strdup(encoded); + size_t bufsz, i; + ssize_t ret; + + note("testing failure at-once, source at line %d", line); + bufsz = strlen(buf); + ret = phr_decode_chunked(&dec, buf, &bufsz); + ok(ret == expected); + + note("testing failure per-byte, source at line %d", line); + memset(&dec, 0, sizeof(dec)); + for (i = 0; encoded[i] != '\0'; ++i) { + buf[0] = encoded[i]; + bufsz = 1; + ret = phr_decode_chunked(&dec, buf, &bufsz); + if (ret == -1) { + ok(ret == expected); + goto cleanup; + } else if (ret == -2) { + /* continue */ + } else { + ok(0); + goto cleanup; + } + } + ok(ret == expected); + +cleanup: + free(buf); +} + +static void (*chunked_test_runners[])(int, int, const char *, const char *, ssize_t) = {test_chunked_at_once, test_chunked_per_byte, + NULL}; + +static void test_chunked(void) +{ + size_t i; + + for (i = 0; chunked_test_runners[i] != NULL; ++i) { + chunked_test_runners[i](__LINE__, 0, "b\r\nhello world\r\n0\r\n", "hello world", 0); + chunked_test_runners[i](__LINE__, 0, "6\r\nhello \r\n5\r\nworld\r\n0\r\n", "hello world", 0); + chunked_test_runners[i](__LINE__, 0, "6;comment=hi\r\nhello \r\n5\r\nworld\r\n0\r\n", "hello world", 0); + chunked_test_runners[i](__LINE__, 0, "6\r\nhello \r\n5\r\nworld\r\n0\r\na: b\r\nc: d\r\n\r\n", "hello world", + sizeof("a: b\r\nc: d\r\n\r\n") - 1); + chunked_test_runners[i](__LINE__, 0, "b\r\nhello world\r\n0\r\n", "hello world", 0); + } + + note("failures"); + test_chunked_failure(__LINE__, "z\r\nabcdefg", -1); + if (sizeof(size_t) == 8) { + test_chunked_failure(__LINE__, "6\r\nhello \r\nffffffffffffffff\r\nabcdefg", -2); + test_chunked_failure(__LINE__, "6\r\nhello \r\nfffffffffffffffff\r\nabcdefg", -1); + } +} + +static void test_chunked_consume_trailer(void) +{ + size_t i; + + for (i = 0; chunked_test_runners[i] != NULL; ++i) { + chunked_test_runners[i](__LINE__, 1, "b\r\nhello world\r\n0\r\n", "hello world", -2); + chunked_test_runners[i](__LINE__, 1, "6\r\nhello \r\n5\r\nworld\r\n0\r\n", "hello world", -2); + chunked_test_runners[i](__LINE__, 1, "6;comment=hi\r\nhello \r\n5\r\nworld\r\n0\r\n", "hello world", -2); + chunked_test_runners[i](__LINE__, 1, "b\r\nhello world\r\n0\r\n\r\n", "hello world", 0); + chunked_test_runners[i](__LINE__, 1, "b\nhello world\n0\n\n", "hello world", 0); + chunked_test_runners[i](__LINE__, 1, "6\r\nhello \r\n5\r\nworld\r\n0\r\na: b\r\nc: d\r\n\r\n", "hello world", 0); + } +} + +int main(int argc, char **argv) +{ + subtest("request", test_request); + subtest("response", test_response); + subtest("headers", test_headers); + subtest("chunked", test_chunked); + subtest("chunked-consume-trailer", test_chunked_consume_trailer); + return done_testing(); +} |