├── .codecov.yml ├── .github └── workflows │ └── d.yml ├── .gitignore ├── LICENSE ├── README.md ├── bench ├── .gitignore ├── Makefile ├── README.md ├── bench.d ├── extern │ ├── Makefile │ ├── README.md │ ├── http_parser │ │ ├── http_parser.c │ │ └── http_parser.h │ ├── llhttp │ │ ├── api.c │ │ ├── http.c │ │ ├── llhttp.c │ │ └── llhttp.h │ └── picohttpparser │ │ ├── picohttpparser.c │ │ └── picohttpparser.h ├── httpparser.d ├── llhttp.d ├── picohttpparser.d ├── requests.txt └── results.png ├── dub.sdl ├── dub.selections.json ├── meson.build ├── scripts └── ci.sh └── source └── httparsed.d /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Documentation: https://github.com/codecov/support/wiki/codecov.yml 2 | # Validate with: curl --data-binary @.codecov.yml https://codecov.io/validate 3 | 4 | codecov: 5 | notify: 6 | # We don't want to wait for the CodeCov report 7 | # See https://github.com/codecov/support/issues/312 8 | require_ci_to_pass: false 9 | after_n_builds: 1 # send notifications after the first upload 10 | wait_for_ci: false 11 | 12 | # At Travis, the PR is merged into `master` before the testsuite is run. 13 | # This allows CodeCov to adjust the resulting coverage diff, s.t. it matches 14 | # with the GitHub diff. 15 | # https://github.com/codecov/support/issues/363 16 | # https://docs.codecov.io/v4.3.6/docs/comparing-commits 17 | allow_coverage_offsets: true 18 | 19 | coverage: 20 | precision: 3 21 | round: down 22 | range: 50...100 23 | 24 | status: 25 | # Learn more at https://codecov.io/docs#yaml_default_commit_status 26 | project: off 27 | patch: 28 | default: 29 | informational: true 30 | changes: off 31 | 32 | comment: false 33 | -------------------------------------------------------------------------------- /.github/workflows/d.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: D 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | jobs: 14 | test: 15 | name: Dub tests 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | dc: [dmd-latest, ldc-latest, dmd-2.094.2, ldc-1.24.0, dmd-2.091.1] 20 | include: 21 | - { os: macOS-latest, dc: dmd-latest, arch: x86_64 } 22 | - { os: macOS-latest, dc: ldc-latest, arch: x86_64 } 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Install D compiler 27 | uses: dlang-community/setup-dlang@v2 28 | with: 29 | compiler: ${{ matrix.dc }} 30 | - name: Run tests 31 | run: scripts/ci.sh 32 | - name: Upload codecov 33 | if: matrix.os == 'ubuntu-latest' && matrix.dc == 'dmd-latest' 34 | env: 35 | COVERAGE: true 36 | run: scripts/ci.sh 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | *test* 3 | *.a 4 | *.o 5 | *.exe 6 | *.so 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # httparsed 2 | [![Actions Status](https://github.com/tchaloupka/httparsed/workflows/D/badge.svg)](https://github.com/tchaloupka/httparsed/actions) 3 | [![Latest version](https://img.shields.io/dub/v/httparsed.svg)](https://code.dlang.org/packages/httparsed) 4 | [![Dub downloads](https://img.shields.io/dub/dt/httparsed.svg)](http://code.dlang.org/packages/httparsed) 5 | [![codecov](https://codecov.io/gh/tchaloupka/httparsed/branch/master/graph/badge.svg)](https://codecov.io/gh/tchaloupka/httparsed) 6 | [![license](https://img.shields.io/github/license/tchaloupka/httparsed.svg)](https://github.com/tchaloupka/httparsed/blob/master/LICENSE) 7 | 8 | Push parser of HTTP/1.x requests and responses. 9 | Other [internet message](https://tools.ietf.org/html/rfc5322) like protocols (ie [RTSP](https://tools.ietf.org/html/rfc7826)) are supported too. 10 | 11 | Inspired by [picohttpparser](https://github.com/h2o/picohttpparser). 12 | 13 | ## Features 14 | 15 | * doesn't allocate anything on it's own (`nothrow @nogc`) 16 | * works with `betterC` 17 | * uses compile time introspection to pass parsed message parts to callbacks 18 | * doesn't store any internal state 19 | * handles incomplete messages and can continue parsing from previous buffer index 20 | * no dependencies 21 | * uses SSE4.2 with LDC2 compiler and SSE4.2 enabled target CPU 22 | 23 | ## Usage 24 | 25 | ```D 26 | // define our message content handler 27 | struct Header 28 | { 29 | const(char)[] name; 30 | const(char)[] value; 31 | } 32 | 33 | // Just store slices of parsed message header 34 | struct Msg 35 | { 36 | @safe pure nothrow @nogc: 37 | void onMethod(const(char)[] method) { this.method = method; } 38 | void onUri(const(char)[] uri) { this.uri = uri; } 39 | int onVersion(const(char)[] ver) 40 | { 41 | minorVer = parseHttpVersion(ver); 42 | return minorVer >= 0 ? 0 : minorVer; 43 | } 44 | void onHeader(const(char)[] name, const(char)[] value) { 45 | this.m_headers[m_headersLength].name = name; 46 | this.m_headers[m_headersLength++].value = value; 47 | } 48 | void onStatus(int status) { this.status = status; } 49 | void onStatusMsg(const(char)[] statusMsg) { this.statusMsg = statusMsg; } 50 | 51 | const(char)[] method; 52 | const(char)[] uri; 53 | int minorVer; 54 | int status; 55 | const(char)[] statusMsg; 56 | 57 | private { 58 | Header[32] m_headers; 59 | size_t m_headersLength; 60 | } 61 | 62 | Header[] headers() return { return m_headers[0..m_headersLength]; } 63 | } 64 | 65 | // init parser 66 | auto reqParser = initParser!Msg(); // or `MsgParser!MSG reqParser;` 67 | auto resParser = initParser!Msg(); // or `MsgParser!MSG resParser;` 68 | 69 | // parse request 70 | string data = "GET /foo HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n"; 71 | // returns parsed message header length when parsed sucessfully, -ParserError on error 72 | int res = reqParser.parseRequest(data); 73 | assert(res == data.length); 74 | assert(reqParser.method == "GET"); 75 | assert(reqParser.uri == "/foo"); 76 | assert(reqParser.minorVer == 1); // HTTP/1.1 77 | assert(reqParser.headers.length == 1); 78 | assert(reqParser.headers[0].name == "Host"); 79 | assert(reqParser.headers[0].value == "127.0.0.1:8090"); 80 | 81 | // parse response 82 | data = "HTTP/1.0 200 OK\r\n"; 83 | uint lastPos; // store last parsed position for next run 84 | res = resParser.parseResponse(data, lastPos); 85 | assert(res == -ParserError.partial); // no complete message header yet 86 | data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nfoo"; 87 | res = resParser.parseResponse(data, lastPos); // starts parsing from previous position 88 | assert(res == data.length - 3); // whole message header parsed, body left to be handled based on actual header values 89 | assert(resParser.minorVer == 0); // HTTP/1.0 90 | assert(resParser.status == 200); 91 | assert(resParser.statusMsg == "OK"); 92 | assert(resParser.headers.length == 2); 93 | assert(resParser.headers[0].name == "Content-Type"); 94 | assert(resParser.headers[0].value == "text/plain"); 95 | assert(resParser.headers[1].name == "Content-Length"); 96 | assert(resParser.headers[1].value == "3"); 97 | ``` 98 | 99 | ### Adding library to your project 100 | 101 | To use this library just add `dependency "httparsed" version=">=1.1.0"` to your `dub.sdl` project configuration. 102 | 103 | Or just copypaste the whole file and use it directly. 104 | 105 | ### SSE4.2 106 | 107 | To use SSE4.2 use this in your `dub.sdl`: 108 | 109 | ``` 110 | dflags "-mcpu=native" platform="ldc" 111 | ``` 112 | 113 | ## Performance 114 | 115 | * Tested on: `AMD Ryzen 7 3700X 8-Core Processor` 116 | * Compilers: ldc-1.29.0, gcc-12.1.1 20220507 117 | * Best of 5 runs for each parser 118 | * tested parsers: 119 | * httparsed (noop) - this parser but with a provided message context with no callbacks - it just parses through requests, but doesn't use anything 120 | * httparsed - this parser with a simple msg struct as in example above 121 | * [picohttpparser](https://github.com/h2o/picohttpparser) 122 | * [http_parser](https://github.com/nodejs/http-parser) 123 | * [llhttp](https://github.com/nodejs/llhttp) - replacement of [http_parser](https://github.com/nodejs/http-parser) 124 | * [vibe-d](https://github.com/vibe-d/vibe.d/blob/02011889fb72e334639c7773f5227dd31197b5fa/http/vibe/http/server.d#L2334) - stripped down version of HTTP request parser used in vibe-d 125 | * [arsd](https://github.com/adamdruppe/arsd/blob/402ea062b81197410b05df7f75c299e5e3eef0d8/cgi.d#L1737) - stripped down HTTP request parser of arsd's `cgi.d` package 126 | 127 | ![results](bench/results.png) 128 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | bench 2 | -------------------------------------------------------------------------------- /bench/Makefile: -------------------------------------------------------------------------------- 1 | DC = ldc2 2 | DFLAGS += -release -O3 -boundscheck=off -ffast-math -mcpu=native 3 | LIBS = extern/http_parser.o extern/picohttpparser.o extern/llhttp.o extern/api.o extern/http.o 4 | SRC = bench.d ../source/httparsed.d \ 5 | 6 | # vibe-d deps 7 | SRC += \ 8 | -I .dub/packages/mir-core-1.1.106/mir-core/source \ 9 | -I .dub/packages/stdx-allocator-3.1.0-beta.2/stdx-allocator/source \ 10 | -I .dub/packages/taggedalgebraic-0.11.22/taggedalgebraic/source \ 11 | -I .dub/packages/eventcore-0.9.20/eventcore/source \ 12 | -I .dub/packages/vibe-core-1.22.4/vibe-core/source \ 13 | -I .dub/packages/vibe-d-0.9.5-beta.1/vibe-d/inet \ 14 | -I .dub/packages/vibe-d-0.9.5-beta.1/vibe-d/stream \ 15 | -I .dub/packages/vibe-d-0.9.5-beta.1/vibe-d/utils \ 16 | .dub/packages/vibe-d-0.9.5-beta.1/vibe-d/inet/libvibe-d_inet.a 17 | 18 | .PHONY: all 19 | all: bench 20 | ./bench 21 | 22 | .PHONY: bench 23 | bench: extern 24 | dub fetch mir-core@1.1.106 --cache=local 25 | dub fetch stdx-allocator@3.1.0-beta.2 --cache=local 26 | dub fetch taggedalgebraic@0.11.22 --cache=local 27 | dub fetch eventcore@0.9.20 --cache=local 28 | dub fetch vibe-core@1.22.4 --cache=local 29 | dub fetch vibe-d@0.9.5-beta.1 --cache=local 30 | dub build -b release --combined --compiler=$(DC) --root .dub/packages/vibe-d-0.9.5-beta.1/vibe-d :inet 31 | $(DC) $(DFLAGS) -of bench $(SRC) $(LIBS) -J . 32 | 33 | .PHONY: extern 34 | extern: 35 | $(MAKE) -C extern 36 | 37 | .PHONY: clean 38 | clean: 39 | $(MAKE) -C extern clean 40 | rm -f bench 41 | rm -f *.o 42 | rm -f *.exe 43 | rm -rf .dub 44 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # bench 2 | 3 | Test data are in `requests.txt` file. 4 | 5 | To run benchmarks just run: `make`. 6 | -------------------------------------------------------------------------------- /bench/bench.d: -------------------------------------------------------------------------------- 1 | import std.algorithm; 2 | import std.conv; 3 | import std.datetime.stopwatch; 4 | import std.exception; 5 | import std.range; 6 | import std.stdio; 7 | 8 | import httparsed; 9 | import httpparser; 10 | import picohttpparser; 11 | import llhttp; 12 | 13 | immutable string requests = import("requests.txt"); 14 | enum REQNUM = 275; // number of requests in the requests.txt 15 | enum LOOPS = 100_000; 16 | 17 | void main() 18 | { 19 | static void writeRes(string name, Duration dur) 20 | { 21 | immutable nsecs = dur.total!"nsecs"; 22 | immutable secs = (cast(double)nsecs) / 1_000_000_000; 23 | enum bytes = requests.length * LOOPS; 24 | enum totalReq = REQNUM * LOOPS; 25 | writeln( 26 | name, 27 | cast(double)nsecs/totalReq, " ns/req, ", 28 | cast(double)bytes/secs/1024/1024, " MB/s, ", 29 | cast(size_t)(totalReq / secs), " rps" 30 | ); 31 | } 32 | 33 | auto res = benchmark!( 34 | testHttparsed!Msg, 35 | testHttparsed!NoopMsg, 36 | testPicoHttpParser, 37 | testHttpParser, 38 | testLLHTTP, 39 | testVibeD, 40 | testArsd 41 | )(LOOPS)[].zip( 42 | ["httparsed", "httparsed (noop)", "picohttp", "http_parser", "llhttp", "vibe-d", "arsd"] 43 | ) 44 | .array.sort!((a,b) => a[0] < b[0]); 45 | 46 | immutable maxlen = res.maxElement!(a => a[1].length)[1].length; 47 | foreach (r; res) 48 | writeRes( 49 | r[1] ~ ": " ~ ' '.repeat(maxlen - r[1].length).text, 50 | r[0] 51 | ); 52 | } 53 | 54 | void testHttparsed(M)() 55 | { 56 | const(char)[] data = requests; 57 | uint reqNum; 58 | auto p = initParser!M(); 59 | while (data.length) 60 | { 61 | static if (is(M == Msg)) p.msg.m_headersLength = 0; 62 | immutable res = p.parseRequest(data); 63 | enforce(res > 0, "Unexpected response: " ~ res.to!string); 64 | ++reqNum; 65 | data = data[res..$]; 66 | } 67 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 68 | } 69 | 70 | void testPicoHttpParser() 71 | { 72 | const(char)* method; 73 | size_t method_len; 74 | const(char)* path; 75 | size_t path_len; 76 | int minor_version; 77 | phr_header[32] headers; 78 | size_t num_headers; 79 | 80 | const(char)[] data = requests; 81 | uint reqNum; 82 | while (data.length) 83 | { 84 | num_headers = headers.length; 85 | immutable ret = phr_parse_request( 86 | &data[0], data.length, 87 | &method, &method_len, 88 | &path, &path_len, 89 | &minor_version, 90 | &headers[0], &num_headers, 91 | 0 92 | ); 93 | enforce(ret > 0, "Unexpected response: " ~ ret.to!string); 94 | ++reqNum; 95 | data = data[ret..$]; 96 | } 97 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 98 | } 99 | 100 | void testHttpParser() 101 | { 102 | static uint reqNum; 103 | 104 | extern (C) 105 | static int onComplete(http_parser* p) { 106 | ++reqNum; 107 | return 0; 108 | } 109 | 110 | http_parser parser; 111 | http_parser_settings settings; 112 | settings.on_message_complete = &onComplete; 113 | 114 | reqNum = 0; 115 | const(char)[] data = requests; 116 | http_parser_init(&parser, http_parser_type.HTTP_REQUEST); 117 | immutable res = http_parser_execute(&parser, &settings, &data[0], data.length); 118 | enforce(res == requests.length, "Unexpected response: " ~ res.to!string); 119 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 120 | } 121 | 122 | void testLLHTTP() 123 | { 124 | static uint reqNum; 125 | 126 | extern (C) 127 | static int onLLComplete(llhttp_t* p) { 128 | ++reqNum; 129 | return 0; 130 | } 131 | 132 | llhttp_t parser; 133 | llhttp_settings_t settings; 134 | settings.on_message_complete = &onLLComplete; 135 | 136 | reqNum = 0; 137 | const(char)[] data = requests; 138 | llhttp_init(&parser, llhttp_type.HTTP_REQUEST, &settings); 139 | immutable res = llhttp_execute(&parser, &data[0], data.length); 140 | enforce(res == llhttp_errno.HPE_OK, "Unexpected response: " ~ res.to!string); 141 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 142 | } 143 | 144 | // stripped down version of https://github.com/vibe-d/vibe.d/blob/02011889fb72e334639c7773f5227dd31197b5fa/http/vibe/http/server.d#L2334 145 | void testVibeD() 146 | { 147 | import std.string : indexOf; 148 | import vibe.inet.message : InetHeaderMap, parseRFC5322Header; 149 | import vibe.internal.allocator; 150 | import vibe.internal.utilallocator: RegionListAllocator; 151 | import vibe.stream.memory : createMemoryStream; 152 | import vibe.stream.operations : readLine; 153 | 154 | enum MaxHTTPHeaderLineLength = 4096; 155 | 156 | scope alloc = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance); 157 | auto stream = createMemoryStream(cast(ubyte[])requests); 158 | uint reqNum; 159 | 160 | string method; 161 | string requestURI; 162 | string httpVersion; 163 | InetHeaderMap headers; 164 | 165 | while (!stream.empty) 166 | { 167 | auto reqln = () @trusted { return cast(string)stream.readLine(MaxHTTPHeaderLineLength, "\r\n", alloc); }(); 168 | 169 | //Method 170 | auto pos = reqln.indexOf(' '); 171 | enforce(pos >= 0, "invalid request method"); 172 | 173 | method = reqln[0 .. pos]; 174 | reqln = reqln[pos+1 .. $]; 175 | 176 | //Path 177 | pos = reqln.indexOf(' '); 178 | enforce(pos >= 0, "invalid request path"); 179 | 180 | requestURI = reqln[0 .. pos]; 181 | reqln = reqln[pos+1 .. $]; 182 | httpVersion = reqln; 183 | 184 | //headers 185 | parseRFC5322Header(stream, headers, MaxHTTPHeaderLineLength, alloc, false); 186 | 187 | reqNum++; 188 | headers = InetHeaderMap.init; 189 | } 190 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 191 | } 192 | 193 | // stripped down version of https://github.com/adamdruppe/arsd/blob/402ea062b81197410b05df7f75c299e5e3eef0d8/cgi.d#L1737 194 | void testArsd() 195 | { 196 | import al = std.algorithm; 197 | import std.string; 198 | 199 | const(char)[] requestMethod; 200 | string requestUri; 201 | string hdrName, hdrValue; 202 | bool http10; 203 | 204 | const(char)[] data = requests; 205 | uint reqNum; 206 | int headerNumber = 0; 207 | foreach (line; al.splitter(data, "\r\n")) 208 | { 209 | if (line.length) { 210 | headerNumber++; 211 | auto header = cast(string) line.idup; 212 | if (headerNumber == 1) { 213 | // request line 214 | auto parts = al.splitter(header, " "); 215 | requestMethod = parts.front; 216 | parts.popFront(); 217 | requestUri = parts.front; 218 | 219 | if(header.indexOf("HTTP/1.0") != -1) { 220 | http10 = true; 221 | } 222 | } 223 | else 224 | { 225 | // other header 226 | auto colon = header.indexOf(":"); 227 | if(colon == -1) 228 | throw new Exception("HTTP headers should have a colon!"); 229 | hdrName = header[0..colon].toLower; 230 | hdrValue = header[colon+2..$]; // skip the colon and the space 231 | } 232 | continue; 233 | } 234 | 235 | // message header completed 236 | ++reqNum; 237 | headerNumber = 0; 238 | } 239 | --reqNum; // last empty line 240 | enforce(reqNum == REQNUM, "Expected " ~ REQNUM.to!string ~ " requests parsed, but got: " ~ reqNum.to!string); 241 | } 242 | 243 | struct Header 244 | { 245 | const(char)[] name; 246 | const(char)[] value; 247 | } 248 | 249 | struct Msg 250 | { 251 | nothrow @nogc @safe pure: 252 | void onMethod(const(char)[] method) { this.method = method; } 253 | void onUri(const(char)[] uri) { this.uri = uri; } 254 | void onVersion(const(char)[] ver) { this.ver = ver; } 255 | void onHeader(const(char)[] name, const(char)[] value) 256 | { 257 | this.m_headers[m_headersLength].name = name; 258 | this.m_headers[m_headersLength++].value = value; 259 | } 260 | void onStatus(int status) { this.status = status; } 261 | void onStatusMsg(const(char)[] statusMsg) { this.statusMsg = statusMsg; } 262 | 263 | const(char)[] method; 264 | const(char)[] uri; 265 | const(char)[] ver; 266 | int status; 267 | const(char)[] statusMsg; 268 | 269 | private { 270 | Header[10] m_headers; 271 | size_t m_headersLength; 272 | } 273 | 274 | Header[] headers() return { return m_headers[0..m_headersLength]; } 275 | } 276 | 277 | struct NoopMsg {} 278 | -------------------------------------------------------------------------------- /bench/extern/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-variable -O3 -march=native 2 | CC?=gcc 3 | 4 | all: picohttpparser.o http_parser.o llhttp.o 5 | 6 | picohttpparser.o: picohttpparser/picohttpparser.c 7 | $(CC) $(CFLAGS) $(LDFLAGS) -o picohttpparser.o -c picohttpparser/picohttpparser.c 8 | 9 | http_parser.o: http_parser/http_parser.c 10 | $(CC) $(CFLAGS) $(LDFLAGS) -o http_parser.o -c http_parser/http_parser.c 11 | 12 | llhttp.o: llhttp/llhttp.c llhttp/http.o llhttp/api.o 13 | $(CC) $(CFLAGS) $(LDFLAGS) -o llhttp.o -c llhttp/llhttp.c 14 | mv llhttp/*.o ./ 15 | 16 | llhttp/%.o: llhttp/%.c llhttp/llhttp.h llhttp/api.h 17 | $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@ 18 | 19 | clean: 20 | find . -type f -name "*.o" -exec rm -f {} \; 21 | -------------------------------------------------------------------------------- /bench/extern/README.md: -------------------------------------------------------------------------------- 1 | # External C http parsers 2 | 3 | * [picohttparser](https://github.com/h2o/picohttpparser) - commit 066d2b1e9ab820703db0837a7255d92d30f0c9f5 4 | * [http_parser](https://github.com/nodejs/http-parser) - commit ec8b5ee63f0e51191ea43bb0c6eac7bfbff3141d 5 | * [llhttp](https://github.com/nodejs/llhttp) - commit 75b45129db961e1fb3c56044e1b8f7721bfaee5d 6 | 7 | D bindings are generated using [dstep](https://github.com/jacob-carlborg/dstep) 8 | -------------------------------------------------------------------------------- /bench/extern/http_parser/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 9 30 | #define HTTP_PARSER_VERSION_PATCH 4 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && \ 34 | (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9) 45 | #include 46 | #else 47 | #include 48 | #endif 49 | 50 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 51 | * faster 52 | */ 53 | #ifndef HTTP_PARSER_STRICT 54 | # define HTTP_PARSER_STRICT 1 55 | #endif 56 | 57 | /* Maximium header size allowed. If the macro is not defined 58 | * before including this header then the default is used. To 59 | * change the maximum header size, define the macro in the build 60 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 61 | * the effective limit on the size of the header, define the macro 62 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 63 | */ 64 | #ifndef HTTP_MAX_HEADER_SIZE 65 | # define HTTP_MAX_HEADER_SIZE (80*1024) 66 | #endif 67 | 68 | typedef struct http_parser http_parser; 69 | typedef struct http_parser_settings http_parser_settings; 70 | 71 | 72 | /* Callbacks should return non-zero to indicate an error. The parser will 73 | * then halt execution. 74 | * 75 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 76 | * returning '1' from on_headers_complete will tell the parser that it 77 | * should not expect a body. This is used when receiving a response to a 78 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 79 | * chunked' headers that indicate the presence of a body. 80 | * 81 | * Returning `2` from on_headers_complete will tell parser that it should not 82 | * expect neither a body nor any futher responses on this connection. This is 83 | * useful for handling responses to a CONNECT request which may not contain 84 | * `Upgrade` or `Connection: upgrade` headers. 85 | * 86 | * http_data_cb does not return data chunks. It will be called arbitrarily 87 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 88 | * each providing just a few characters more data. 89 | */ 90 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 91 | typedef int (*http_cb) (http_parser*); 92 | 93 | 94 | /* Status Codes */ 95 | #define HTTP_STATUS_MAP(XX) \ 96 | XX(100, CONTINUE, Continue) \ 97 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 98 | XX(102, PROCESSING, Processing) \ 99 | XX(200, OK, OK) \ 100 | XX(201, CREATED, Created) \ 101 | XX(202, ACCEPTED, Accepted) \ 102 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ 103 | XX(204, NO_CONTENT, No Content) \ 104 | XX(205, RESET_CONTENT, Reset Content) \ 105 | XX(206, PARTIAL_CONTENT, Partial Content) \ 106 | XX(207, MULTI_STATUS, Multi-Status) \ 107 | XX(208, ALREADY_REPORTED, Already Reported) \ 108 | XX(226, IM_USED, IM Used) \ 109 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 110 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 111 | XX(302, FOUND, Found) \ 112 | XX(303, SEE_OTHER, See Other) \ 113 | XX(304, NOT_MODIFIED, Not Modified) \ 114 | XX(305, USE_PROXY, Use Proxy) \ 115 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 116 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 117 | XX(400, BAD_REQUEST, Bad Request) \ 118 | XX(401, UNAUTHORIZED, Unauthorized) \ 119 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 120 | XX(403, FORBIDDEN, Forbidden) \ 121 | XX(404, NOT_FOUND, Not Found) \ 122 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 123 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 124 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 125 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 126 | XX(409, CONFLICT, Conflict) \ 127 | XX(410, GONE, Gone) \ 128 | XX(411, LENGTH_REQUIRED, Length Required) \ 129 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 130 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 131 | XX(414, URI_TOO_LONG, URI Too Long) \ 132 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 133 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 134 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 135 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 136 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 137 | XX(423, LOCKED, Locked) \ 138 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 139 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 140 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 141 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 142 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 143 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 144 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 145 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 146 | XX(502, BAD_GATEWAY, Bad Gateway) \ 147 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 148 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 149 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 150 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 151 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 152 | XX(508, LOOP_DETECTED, Loop Detected) \ 153 | XX(510, NOT_EXTENDED, Not Extended) \ 154 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ 155 | 156 | enum http_status 157 | { 158 | #define XX(num, name, string) HTTP_STATUS_##name = num, 159 | HTTP_STATUS_MAP(XX) 160 | #undef XX 161 | }; 162 | 163 | 164 | /* Request Methods */ 165 | #define HTTP_METHOD_MAP(XX) \ 166 | XX(0, DELETE, DELETE) \ 167 | XX(1, GET, GET) \ 168 | XX(2, HEAD, HEAD) \ 169 | XX(3, POST, POST) \ 170 | XX(4, PUT, PUT) \ 171 | /* pathological */ \ 172 | XX(5, CONNECT, CONNECT) \ 173 | XX(6, OPTIONS, OPTIONS) \ 174 | XX(7, TRACE, TRACE) \ 175 | /* WebDAV */ \ 176 | XX(8, COPY, COPY) \ 177 | XX(9, LOCK, LOCK) \ 178 | XX(10, MKCOL, MKCOL) \ 179 | XX(11, MOVE, MOVE) \ 180 | XX(12, PROPFIND, PROPFIND) \ 181 | XX(13, PROPPATCH, PROPPATCH) \ 182 | XX(14, SEARCH, SEARCH) \ 183 | XX(15, UNLOCK, UNLOCK) \ 184 | XX(16, BIND, BIND) \ 185 | XX(17, REBIND, REBIND) \ 186 | XX(18, UNBIND, UNBIND) \ 187 | XX(19, ACL, ACL) \ 188 | /* subversion */ \ 189 | XX(20, REPORT, REPORT) \ 190 | XX(21, MKACTIVITY, MKACTIVITY) \ 191 | XX(22, CHECKOUT, CHECKOUT) \ 192 | XX(23, MERGE, MERGE) \ 193 | /* upnp */ \ 194 | XX(24, MSEARCH, M-SEARCH) \ 195 | XX(25, NOTIFY, NOTIFY) \ 196 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 197 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 198 | /* RFC-5789 */ \ 199 | XX(28, PATCH, PATCH) \ 200 | XX(29, PURGE, PURGE) \ 201 | /* CalDAV */ \ 202 | XX(30, MKCALENDAR, MKCALENDAR) \ 203 | /* RFC-2068, section 19.6.1.2 */ \ 204 | XX(31, LINK, LINK) \ 205 | XX(32, UNLINK, UNLINK) \ 206 | /* icecast */ \ 207 | XX(33, SOURCE, SOURCE) \ 208 | 209 | enum http_method 210 | { 211 | #define XX(num, name, string) HTTP_##name = num, 212 | HTTP_METHOD_MAP(XX) 213 | #undef XX 214 | }; 215 | 216 | 217 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 218 | 219 | 220 | /* Flag values for http_parser.flags field */ 221 | enum flags 222 | { F_CHUNKED = 1 << 0 223 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 224 | , F_CONNECTION_CLOSE = 1 << 2 225 | , F_CONNECTION_UPGRADE = 1 << 3 226 | , F_TRAILING = 1 << 4 227 | , F_UPGRADE = 1 << 5 228 | , F_SKIPBODY = 1 << 6 229 | , F_CONTENTLENGTH = 1 << 7 230 | }; 231 | 232 | 233 | /* Map for errno-related constants 234 | * 235 | * The provided argument should be a macro that takes 2 arguments. 236 | */ 237 | #define HTTP_ERRNO_MAP(XX) \ 238 | /* No error */ \ 239 | XX(OK, "success") \ 240 | \ 241 | /* Callback-related errors */ \ 242 | XX(CB_message_begin, "the on_message_begin callback failed") \ 243 | XX(CB_url, "the on_url callback failed") \ 244 | XX(CB_header_field, "the on_header_field callback failed") \ 245 | XX(CB_header_value, "the on_header_value callback failed") \ 246 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 247 | XX(CB_body, "the on_body callback failed") \ 248 | XX(CB_message_complete, "the on_message_complete callback failed") \ 249 | XX(CB_status, "the on_status callback failed") \ 250 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 251 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 252 | \ 253 | /* Parsing-related errors */ \ 254 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 255 | XX(HEADER_OVERFLOW, \ 256 | "too many header bytes seen; overflow detected") \ 257 | XX(CLOSED_CONNECTION, \ 258 | "data received after completed connection: close message") \ 259 | XX(INVALID_VERSION, "invalid HTTP version") \ 260 | XX(INVALID_STATUS, "invalid HTTP status code") \ 261 | XX(INVALID_METHOD, "invalid HTTP method") \ 262 | XX(INVALID_URL, "invalid URL") \ 263 | XX(INVALID_HOST, "invalid host") \ 264 | XX(INVALID_PORT, "invalid port") \ 265 | XX(INVALID_PATH, "invalid path") \ 266 | XX(INVALID_QUERY_STRING, "invalid query string") \ 267 | XX(INVALID_FRAGMENT, "invalid fragment") \ 268 | XX(LF_EXPECTED, "LF character expected") \ 269 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 270 | XX(INVALID_CONTENT_LENGTH, \ 271 | "invalid character in content-length header") \ 272 | XX(UNEXPECTED_CONTENT_LENGTH, \ 273 | "unexpected content-length header") \ 274 | XX(INVALID_CHUNK_SIZE, \ 275 | "invalid character in chunk size header") \ 276 | XX(INVALID_CONSTANT, "invalid constant string") \ 277 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 278 | XX(STRICT, "strict mode assertion failed") \ 279 | XX(PAUSED, "parser is paused") \ 280 | XX(UNKNOWN, "an unknown error occurred") \ 281 | XX(INVALID_TRANSFER_ENCODING, \ 282 | "request has invalid transfer-encoding") \ 283 | 284 | 285 | /* Define HPE_* values for each errno value above */ 286 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 287 | enum http_errno { 288 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 289 | }; 290 | #undef HTTP_ERRNO_GEN 291 | 292 | 293 | /* Get an http_errno value from an http_parser */ 294 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 295 | 296 | 297 | struct http_parser { 298 | /** PRIVATE **/ 299 | unsigned int type : 2; /* enum http_parser_type */ 300 | unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ 301 | unsigned int state : 7; /* enum state from http_parser.c */ 302 | unsigned int header_state : 7; /* enum header_state from http_parser.c */ 303 | unsigned int index : 5; /* index into current matcher */ 304 | unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */ 305 | unsigned int allow_chunked_length : 1; /* Allow headers with both 306 | * `Content-Length` and 307 | * `Transfer-Encoding: chunked` set */ 308 | unsigned int lenient_http_headers : 1; 309 | 310 | uint32_t nread; /* # bytes read in various scenarios */ 311 | uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) 312 | * if no Content-Length header. 313 | */ 314 | 315 | /** READ-ONLY **/ 316 | unsigned short http_major; 317 | unsigned short http_minor; 318 | unsigned int status_code : 16; /* responses only */ 319 | unsigned int method : 8; /* requests only */ 320 | unsigned int http_errno : 7; 321 | 322 | /* 1 = Upgrade header was present and the parser has exited because of that. 323 | * 0 = No upgrade header present. 324 | * Should be checked when http_parser_execute() returns in addition to 325 | * error checking. 326 | */ 327 | unsigned int upgrade : 1; 328 | 329 | /** PUBLIC **/ 330 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 331 | }; 332 | 333 | 334 | struct http_parser_settings { 335 | http_cb on_message_begin; 336 | http_data_cb on_url; 337 | http_data_cb on_status; 338 | http_data_cb on_header_field; 339 | http_data_cb on_header_value; 340 | http_cb on_headers_complete; 341 | http_data_cb on_body; 342 | http_cb on_message_complete; 343 | /* When on_chunk_header is called, the current chunk length is stored 344 | * in parser->content_length. 345 | */ 346 | http_cb on_chunk_header; 347 | http_cb on_chunk_complete; 348 | }; 349 | 350 | 351 | enum http_parser_url_fields 352 | { UF_SCHEMA = 0 353 | , UF_HOST = 1 354 | , UF_PORT = 2 355 | , UF_PATH = 3 356 | , UF_QUERY = 4 357 | , UF_FRAGMENT = 5 358 | , UF_USERINFO = 6 359 | , UF_MAX = 7 360 | }; 361 | 362 | 363 | /* Result structure for http_parser_parse_url(). 364 | * 365 | * Callers should index into field_data[] with UF_* values iff field_set 366 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 367 | * because we probably have padding left over), we convert any port to 368 | * a uint16_t. 369 | */ 370 | struct http_parser_url { 371 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 372 | uint16_t port; /* Converted UF_PORT string */ 373 | 374 | struct { 375 | uint16_t off; /* Offset into buffer in which field starts */ 376 | uint16_t len; /* Length of run in buffer */ 377 | } field_data[UF_MAX]; 378 | }; 379 | 380 | 381 | /* Returns the library version. Bits 16-23 contain the major version number, 382 | * bits 8-15 the minor version number and bits 0-7 the patch level. 383 | * Usage example: 384 | * 385 | * unsigned long version = http_parser_version(); 386 | * unsigned major = (version >> 16) & 255; 387 | * unsigned minor = (version >> 8) & 255; 388 | * unsigned patch = version & 255; 389 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 390 | */ 391 | unsigned long http_parser_version(void); 392 | 393 | void http_parser_init(http_parser *parser, enum http_parser_type type); 394 | 395 | 396 | /* Initialize http_parser_settings members to 0 397 | */ 398 | void http_parser_settings_init(http_parser_settings *settings); 399 | 400 | 401 | /* Executes the parser. Returns number of parsed bytes. Sets 402 | * `parser->http_errno` on error. */ 403 | size_t http_parser_execute(http_parser *parser, 404 | const http_parser_settings *settings, 405 | const char *data, 406 | size_t len); 407 | 408 | 409 | /* If http_should_keep_alive() in the on_headers_complete or 410 | * on_message_complete callback returns 0, then this should be 411 | * the last message on the connection. 412 | * If you are the server, respond with the "Connection: close" header. 413 | * If you are the client, close the connection. 414 | */ 415 | int http_should_keep_alive(const http_parser *parser); 416 | 417 | /* Returns a string version of the HTTP method. */ 418 | const char *http_method_str(enum http_method m); 419 | 420 | /* Returns a string version of the HTTP status code. */ 421 | const char *http_status_str(enum http_status s); 422 | 423 | /* Return a string name of the given error */ 424 | const char *http_errno_name(enum http_errno err); 425 | 426 | /* Return a string description of the given error */ 427 | const char *http_errno_description(enum http_errno err); 428 | 429 | /* Initialize all http_parser_url members to 0 */ 430 | void http_parser_url_init(struct http_parser_url *u); 431 | 432 | /* Parse a URL; return nonzero on failure */ 433 | int http_parser_parse_url(const char *buf, size_t buflen, 434 | int is_connect, 435 | struct http_parser_url *u); 436 | 437 | /* Pause or un-pause the parser; a nonzero value pauses */ 438 | void http_parser_pause(http_parser *parser, int paused); 439 | 440 | /* Checks if this is the final chunk of the body. */ 441 | int http_body_is_final(const http_parser *parser); 442 | 443 | /* Change the maximum header size provided at compile time. */ 444 | void http_parser_set_max_header_size(uint32_t size); 445 | 446 | #ifdef __cplusplus 447 | } 448 | #endif 449 | #endif 450 | -------------------------------------------------------------------------------- /bench/extern/llhttp/api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "llhttp.h" 6 | 7 | #define CALLBACK_MAYBE(PARSER, NAME) \ 8 | do { \ 9 | const llhttp_settings_t* settings; \ 10 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 11 | if (settings == NULL || settings->NAME == NULL) { \ 12 | err = 0; \ 13 | break; \ 14 | } \ 15 | err = settings->NAME((PARSER)); \ 16 | } while (0) 17 | 18 | #define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ 19 | do { \ 20 | const llhttp_settings_t* settings; \ 21 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 22 | if (settings == NULL || settings->NAME == NULL) { \ 23 | err = 0; \ 24 | break; \ 25 | } \ 26 | err = settings->NAME((PARSER), (START), (LEN)); \ 27 | if (err == -1) { \ 28 | err = HPE_USER; \ 29 | llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ 30 | } \ 31 | } while (0) 32 | 33 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 34 | const llhttp_settings_t* settings) { 35 | llhttp__internal_init(parser); 36 | 37 | parser->type = type; 38 | parser->settings = (void*) settings; 39 | } 40 | 41 | 42 | #if defined(__wasm__) 43 | 44 | extern int wasm_on_message_begin(llhttp_t * p); 45 | extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); 46 | extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); 47 | extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); 48 | extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); 49 | extern int wasm_on_headers_complete(llhttp_t * p, int status_code, 50 | uint8_t upgrade, int should_keep_alive); 51 | extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); 52 | extern int wasm_on_message_complete(llhttp_t * p); 53 | 54 | static int wasm_on_headers_complete_wrap(llhttp_t* p) { 55 | return wasm_on_headers_complete(p, p->status_code, p->upgrade, 56 | llhttp_should_keep_alive(p)); 57 | } 58 | 59 | const llhttp_settings_t wasm_settings = { 60 | wasm_on_message_begin, 61 | wasm_on_url, 62 | wasm_on_status, 63 | wasm_on_header_field, 64 | wasm_on_header_value, 65 | wasm_on_headers_complete_wrap, 66 | wasm_on_body, 67 | wasm_on_message_complete, 68 | NULL, 69 | NULL, 70 | }; 71 | 72 | 73 | llhttp_t* llhttp_alloc(llhttp_type_t type) { 74 | llhttp_t* parser = malloc(sizeof(llhttp_t)); 75 | llhttp_init(parser, type, &wasm_settings); 76 | return parser; 77 | } 78 | 79 | void llhttp_free(llhttp_t* parser) { 80 | free(parser); 81 | } 82 | 83 | /* Some getters required to get stuff from the parser */ 84 | 85 | uint8_t llhttp_get_type(llhttp_t* parser) { 86 | return parser->type; 87 | } 88 | 89 | uint8_t llhttp_get_http_major(llhttp_t* parser) { 90 | return parser->http_major; 91 | } 92 | 93 | uint8_t llhttp_get_http_minor(llhttp_t* parser) { 94 | return parser->http_minor; 95 | } 96 | 97 | uint8_t llhttp_get_method(llhttp_t* parser) { 98 | return parser->method; 99 | } 100 | 101 | int llhttp_get_status_code(llhttp_t* parser) { 102 | return parser->status_code; 103 | } 104 | 105 | uint8_t llhttp_get_upgrade(llhttp_t* parser) { 106 | return parser->upgrade; 107 | } 108 | 109 | #endif // defined(__wasm__) 110 | 111 | 112 | void llhttp_reset(llhttp_t* parser) { 113 | llhttp_type_t type = parser->type; 114 | const llhttp_settings_t* settings = parser->settings; 115 | void* data = parser->data; 116 | uint8_t lenient_flags = parser->lenient_flags; 117 | 118 | llhttp__internal_init(parser); 119 | 120 | parser->type = type; 121 | parser->settings = (void*) settings; 122 | parser->data = data; 123 | parser->lenient_flags = lenient_flags; 124 | } 125 | 126 | 127 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { 128 | return llhttp__internal_execute(parser, data, data + len); 129 | } 130 | 131 | 132 | void llhttp_settings_init(llhttp_settings_t* settings) { 133 | memset(settings, 0, sizeof(*settings)); 134 | } 135 | 136 | 137 | llhttp_errno_t llhttp_finish(llhttp_t* parser) { 138 | int err; 139 | 140 | /* We're in an error state. Don't bother doing anything. */ 141 | if (parser->error != 0) { 142 | return 0; 143 | } 144 | 145 | switch (parser->finish) { 146 | case HTTP_FINISH_SAFE_WITH_CB: 147 | CALLBACK_MAYBE(parser, on_message_complete); 148 | if (err != HPE_OK) return err; 149 | 150 | /* FALLTHROUGH */ 151 | case HTTP_FINISH_SAFE: 152 | return HPE_OK; 153 | case HTTP_FINISH_UNSAFE: 154 | parser->reason = "Invalid EOF state"; 155 | return HPE_INVALID_EOF_STATE; 156 | default: 157 | abort(); 158 | } 159 | } 160 | 161 | 162 | void llhttp_pause(llhttp_t* parser) { 163 | if (parser->error != HPE_OK) { 164 | return; 165 | } 166 | 167 | parser->error = HPE_PAUSED; 168 | parser->reason = "Paused"; 169 | } 170 | 171 | 172 | void llhttp_resume(llhttp_t* parser) { 173 | if (parser->error != HPE_PAUSED) { 174 | return; 175 | } 176 | 177 | parser->error = 0; 178 | } 179 | 180 | 181 | void llhttp_resume_after_upgrade(llhttp_t* parser) { 182 | if (parser->error != HPE_PAUSED_UPGRADE) { 183 | return; 184 | } 185 | 186 | parser->error = 0; 187 | } 188 | 189 | 190 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { 191 | return parser->error; 192 | } 193 | 194 | 195 | const char* llhttp_get_error_reason(const llhttp_t* parser) { 196 | return parser->reason; 197 | } 198 | 199 | 200 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { 201 | parser->reason = reason; 202 | } 203 | 204 | 205 | const char* llhttp_get_error_pos(const llhttp_t* parser) { 206 | return parser->error_pos; 207 | } 208 | 209 | 210 | const char* llhttp_errno_name(llhttp_errno_t err) { 211 | #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; 212 | switch (err) { 213 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 214 | default: abort(); 215 | } 216 | #undef HTTP_ERRNO_GEN 217 | } 218 | 219 | 220 | const char* llhttp_method_name(llhttp_method_t method) { 221 | #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; 222 | switch (method) { 223 | HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) 224 | default: abort(); 225 | } 226 | #undef HTTP_METHOD_GEN 227 | } 228 | 229 | 230 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { 231 | if (enabled) { 232 | parser->lenient_flags |= LENIENT_HEADERS; 233 | } else { 234 | parser->lenient_flags &= ~LENIENT_HEADERS; 235 | } 236 | } 237 | 238 | 239 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { 240 | if (enabled) { 241 | parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; 242 | } else { 243 | parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; 244 | } 245 | } 246 | 247 | 248 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { 249 | if (enabled) { 250 | parser->lenient_flags |= LENIENT_KEEP_ALIVE; 251 | } else { 252 | parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; 253 | } 254 | } 255 | 256 | /* Callbacks */ 257 | 258 | 259 | int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { 260 | int err; 261 | CALLBACK_MAYBE(s, on_message_begin); 262 | return err; 263 | } 264 | 265 | 266 | int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { 267 | int err; 268 | SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); 269 | return err; 270 | } 271 | 272 | 273 | int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { 274 | int err; 275 | CALLBACK_MAYBE(s, on_url_complete); 276 | return err; 277 | } 278 | 279 | 280 | int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { 281 | int err; 282 | SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); 283 | return err; 284 | } 285 | 286 | 287 | int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { 288 | int err; 289 | CALLBACK_MAYBE(s, on_status_complete); 290 | return err; 291 | } 292 | 293 | 294 | int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { 295 | int err; 296 | SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); 297 | return err; 298 | } 299 | 300 | 301 | int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { 302 | int err; 303 | CALLBACK_MAYBE(s, on_header_field_complete); 304 | return err; 305 | } 306 | 307 | 308 | int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { 309 | int err; 310 | SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); 311 | return err; 312 | } 313 | 314 | 315 | int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { 316 | int err; 317 | CALLBACK_MAYBE(s, on_header_value_complete); 318 | return err; 319 | } 320 | 321 | 322 | int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { 323 | int err; 324 | CALLBACK_MAYBE(s, on_headers_complete); 325 | return err; 326 | } 327 | 328 | 329 | int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { 330 | int err; 331 | CALLBACK_MAYBE(s, on_message_complete); 332 | return err; 333 | } 334 | 335 | 336 | int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { 337 | int err; 338 | SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); 339 | return err; 340 | } 341 | 342 | 343 | int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { 344 | int err; 345 | CALLBACK_MAYBE(s, on_chunk_header); 346 | return err; 347 | } 348 | 349 | 350 | int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { 351 | int err; 352 | CALLBACK_MAYBE(s, on_chunk_complete); 353 | return err; 354 | } 355 | 356 | 357 | /* Private */ 358 | 359 | 360 | void llhttp__debug(llhttp_t* s, const char* p, const char* endp, 361 | const char* msg) { 362 | if (p == endp) { 363 | fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, 364 | s->flags, msg); 365 | } else { 366 | fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, 367 | s->type, s->flags, *p, msg); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /bench/extern/llhttp/http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef LLHTTP__TEST 3 | # include "llhttp.h" 4 | #else 5 | # define llhttp_t llparse_t 6 | #endif /* */ 7 | 8 | int llhttp_message_needs_eof(const llhttp_t* parser); 9 | int llhttp_should_keep_alive(const llhttp_t* parser); 10 | 11 | int llhttp__before_headers_complete(llhttp_t* parser, const char* p, 12 | const char* endp) { 13 | /* Set this here so that on_headers_complete() callbacks can see it */ 14 | if ((parser->flags & F_UPGRADE) && 15 | (parser->flags & F_CONNECTION_UPGRADE)) { 16 | /* For responses, "Upgrade: foo" and "Connection: upgrade" are 17 | * mandatory only when it is a 101 Switching Protocols response, 18 | * otherwise it is purely informational, to announce support. 19 | */ 20 | parser->upgrade = 21 | (parser->type == HTTP_REQUEST || parser->status_code == 101); 22 | } else { 23 | parser->upgrade = (parser->method == HTTP_CONNECT); 24 | } 25 | return 0; 26 | } 27 | 28 | 29 | /* Return values: 30 | * 0 - No body, `restart`, message_complete 31 | * 1 - CONNECT request, `restart`, message_complete, and pause 32 | * 2 - chunk_size_start 33 | * 3 - body_identity 34 | * 4 - body_identity_eof 35 | * 5 - invalid transfer-encoding for request 36 | */ 37 | int llhttp__after_headers_complete(llhttp_t* parser, const char* p, 38 | const char* endp) { 39 | int hasBody; 40 | 41 | hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; 42 | if (parser->upgrade && (parser->method == HTTP_CONNECT || 43 | (parser->flags & F_SKIPBODY) || !hasBody)) { 44 | /* Exit, the rest of the message is in a different protocol. */ 45 | return 1; 46 | } 47 | 48 | if (parser->flags & F_SKIPBODY) { 49 | return 0; 50 | } else if (parser->flags & F_CHUNKED) { 51 | /* chunked encoding - ignore Content-Length header, prepare for a chunk */ 52 | return 2; 53 | } else if (parser->flags & F_TRANSFER_ENCODING) { 54 | if (parser->type == HTTP_REQUEST && 55 | (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0) { 56 | /* RFC 7230 3.3.3 */ 57 | 58 | /* If a Transfer-Encoding header field 59 | * is present in a request and the chunked transfer coding is not 60 | * the final encoding, the message body length cannot be determined 61 | * reliably; the server MUST respond with the 400 (Bad Request) 62 | * status code and then close the connection. 63 | */ 64 | return 5; 65 | } else { 66 | /* RFC 7230 3.3.3 */ 67 | 68 | /* If a Transfer-Encoding header field is present in a response and 69 | * the chunked transfer coding is not the final encoding, the 70 | * message body length is determined by reading the connection until 71 | * it is closed by the server. 72 | */ 73 | return 4; 74 | } 75 | } else { 76 | if (!(parser->flags & F_CONTENT_LENGTH)) { 77 | if (!llhttp_message_needs_eof(parser)) { 78 | /* Assume content-length 0 - read the next */ 79 | return 0; 80 | } else { 81 | /* Read body until EOF */ 82 | return 4; 83 | } 84 | } else if (parser->content_length == 0) { 85 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 86 | return 0; 87 | } else { 88 | /* Content-Length header given and non-zero */ 89 | return 3; 90 | } 91 | } 92 | } 93 | 94 | 95 | int llhttp__after_message_complete(llhttp_t* parser, const char* p, 96 | const char* endp) { 97 | int should_keep_alive; 98 | 99 | should_keep_alive = llhttp_should_keep_alive(parser); 100 | parser->finish = HTTP_FINISH_SAFE; 101 | parser->flags = 0; 102 | 103 | /* NOTE: this is ignored in loose parsing mode */ 104 | return should_keep_alive; 105 | } 106 | 107 | 108 | int llhttp_message_needs_eof(const llhttp_t* parser) { 109 | if (parser->type == HTTP_REQUEST) { 110 | return 0; 111 | } 112 | 113 | /* See RFC 2616 section 4.4 */ 114 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 115 | parser->status_code == 204 || /* No Content */ 116 | parser->status_code == 304 || /* Not Modified */ 117 | (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ 118 | return 0; 119 | } 120 | 121 | /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ 122 | if ((parser->flags & F_TRANSFER_ENCODING) && 123 | (parser->flags & F_CHUNKED) == 0) { 124 | return 1; 125 | } 126 | 127 | if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { 128 | return 0; 129 | } 130 | 131 | return 1; 132 | } 133 | 134 | 135 | int llhttp_should_keep_alive(const llhttp_t* parser) { 136 | if (parser->http_major > 0 && parser->http_minor > 0) { 137 | /* HTTP/1.1 */ 138 | if (parser->flags & F_CONNECTION_CLOSE) { 139 | return 0; 140 | } 141 | } else { 142 | /* HTTP/1.0 or earlier */ 143 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 144 | return 0; 145 | } 146 | } 147 | 148 | return !llhttp_message_needs_eof(parser); 149 | } 150 | -------------------------------------------------------------------------------- /bench/extern/llhttp/llhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_LLHTTP_H_ 2 | #define INCLUDE_LLHTTP_H_ 3 | 4 | #define LLHTTP_VERSION_MAJOR 6 5 | #define LLHTTP_VERSION_MINOR 0 6 | #define LLHTTP_VERSION_PATCH 5 7 | 8 | #ifndef LLHTTP_STRICT_MODE 9 | # define LLHTTP_STRICT_MODE 0 10 | #endif 11 | 12 | #ifndef INCLUDE_LLHTTP_ITSELF_H_ 13 | #define INCLUDE_LLHTTP_ITSELF_H_ 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | #include 19 | 20 | typedef struct llhttp__internal_s llhttp__internal_t; 21 | struct llhttp__internal_s { 22 | int32_t _index; 23 | void* _span_pos0; 24 | void* _span_cb0; 25 | int32_t error; 26 | const char* reason; 27 | const char* error_pos; 28 | void* data; 29 | void* _current; 30 | uint64_t content_length; 31 | uint8_t type; 32 | uint8_t method; 33 | uint8_t http_major; 34 | uint8_t http_minor; 35 | uint8_t header_state; 36 | uint8_t lenient_flags; 37 | uint8_t upgrade; 38 | uint8_t finish; 39 | uint16_t flags; 40 | uint16_t status_code; 41 | void* settings; 42 | }; 43 | 44 | int llhttp__internal_init(llhttp__internal_t* s); 45 | int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); 46 | 47 | #ifdef __cplusplus 48 | } /* extern "C" */ 49 | #endif 50 | #endif /* INCLUDE_LLHTTP_ITSELF_H_ */ 51 | 52 | #ifndef LLLLHTTP_C_HEADERS_ 53 | #define LLLLHTTP_C_HEADERS_ 54 | #ifdef __cplusplus 55 | extern "C" { 56 | #endif 57 | 58 | enum llhttp_errno { 59 | HPE_OK = 0, 60 | HPE_INTERNAL = 1, 61 | HPE_STRICT = 2, 62 | HPE_LF_EXPECTED = 3, 63 | HPE_UNEXPECTED_CONTENT_LENGTH = 4, 64 | HPE_CLOSED_CONNECTION = 5, 65 | HPE_INVALID_METHOD = 6, 66 | HPE_INVALID_URL = 7, 67 | HPE_INVALID_CONSTANT = 8, 68 | HPE_INVALID_VERSION = 9, 69 | HPE_INVALID_HEADER_TOKEN = 10, 70 | HPE_INVALID_CONTENT_LENGTH = 11, 71 | HPE_INVALID_CHUNK_SIZE = 12, 72 | HPE_INVALID_STATUS = 13, 73 | HPE_INVALID_EOF_STATE = 14, 74 | HPE_INVALID_TRANSFER_ENCODING = 15, 75 | HPE_CB_MESSAGE_BEGIN = 16, 76 | HPE_CB_HEADERS_COMPLETE = 17, 77 | HPE_CB_MESSAGE_COMPLETE = 18, 78 | HPE_CB_CHUNK_HEADER = 19, 79 | HPE_CB_CHUNK_COMPLETE = 20, 80 | HPE_PAUSED = 21, 81 | HPE_PAUSED_UPGRADE = 22, 82 | HPE_PAUSED_H2_UPGRADE = 23, 83 | HPE_USER = 24 84 | }; 85 | typedef enum llhttp_errno llhttp_errno_t; 86 | 87 | enum llhttp_flags { 88 | F_CONNECTION_KEEP_ALIVE = 0x1, 89 | F_CONNECTION_CLOSE = 0x2, 90 | F_CONNECTION_UPGRADE = 0x4, 91 | F_CHUNKED = 0x8, 92 | F_UPGRADE = 0x10, 93 | F_CONTENT_LENGTH = 0x20, 94 | F_SKIPBODY = 0x40, 95 | F_TRAILING = 0x80, 96 | F_TRANSFER_ENCODING = 0x200 97 | }; 98 | typedef enum llhttp_flags llhttp_flags_t; 99 | 100 | enum llhttp_lenient_flags { 101 | LENIENT_HEADERS = 0x1, 102 | LENIENT_CHUNKED_LENGTH = 0x2, 103 | LENIENT_KEEP_ALIVE = 0x4 104 | }; 105 | typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; 106 | 107 | enum llhttp_type { 108 | HTTP_BOTH = 0, 109 | HTTP_REQUEST = 1, 110 | HTTP_RESPONSE = 2 111 | }; 112 | typedef enum llhttp_type llhttp_type_t; 113 | 114 | enum llhttp_finish { 115 | HTTP_FINISH_SAFE = 0, 116 | HTTP_FINISH_SAFE_WITH_CB = 1, 117 | HTTP_FINISH_UNSAFE = 2 118 | }; 119 | typedef enum llhttp_finish llhttp_finish_t; 120 | 121 | enum llhttp_method { 122 | HTTP_DELETE = 0, 123 | HTTP_GET = 1, 124 | HTTP_HEAD = 2, 125 | HTTP_POST = 3, 126 | HTTP_PUT = 4, 127 | HTTP_CONNECT = 5, 128 | HTTP_OPTIONS = 6, 129 | HTTP_TRACE = 7, 130 | HTTP_COPY = 8, 131 | HTTP_LOCK = 9, 132 | HTTP_MKCOL = 10, 133 | HTTP_MOVE = 11, 134 | HTTP_PROPFIND = 12, 135 | HTTP_PROPPATCH = 13, 136 | HTTP_SEARCH = 14, 137 | HTTP_UNLOCK = 15, 138 | HTTP_BIND = 16, 139 | HTTP_REBIND = 17, 140 | HTTP_UNBIND = 18, 141 | HTTP_ACL = 19, 142 | HTTP_REPORT = 20, 143 | HTTP_MKACTIVITY = 21, 144 | HTTP_CHECKOUT = 22, 145 | HTTP_MERGE = 23, 146 | HTTP_MSEARCH = 24, 147 | HTTP_NOTIFY = 25, 148 | HTTP_SUBSCRIBE = 26, 149 | HTTP_UNSUBSCRIBE = 27, 150 | HTTP_PATCH = 28, 151 | HTTP_PURGE = 29, 152 | HTTP_MKCALENDAR = 30, 153 | HTTP_LINK = 31, 154 | HTTP_UNLINK = 32, 155 | HTTP_SOURCE = 33, 156 | HTTP_PRI = 34, 157 | HTTP_DESCRIBE = 35, 158 | HTTP_ANNOUNCE = 36, 159 | HTTP_SETUP = 37, 160 | HTTP_PLAY = 38, 161 | HTTP_PAUSE = 39, 162 | HTTP_TEARDOWN = 40, 163 | HTTP_GET_PARAMETER = 41, 164 | HTTP_SET_PARAMETER = 42, 165 | HTTP_REDIRECT = 43, 166 | HTTP_RECORD = 44, 167 | HTTP_FLUSH = 45 168 | }; 169 | typedef enum llhttp_method llhttp_method_t; 170 | 171 | #define HTTP_ERRNO_MAP(XX) \ 172 | XX(0, OK, OK) \ 173 | XX(1, INTERNAL, INTERNAL) \ 174 | XX(2, STRICT, STRICT) \ 175 | XX(3, LF_EXPECTED, LF_EXPECTED) \ 176 | XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ 177 | XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ 178 | XX(6, INVALID_METHOD, INVALID_METHOD) \ 179 | XX(7, INVALID_URL, INVALID_URL) \ 180 | XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ 181 | XX(9, INVALID_VERSION, INVALID_VERSION) \ 182 | XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ 183 | XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ 184 | XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ 185 | XX(13, INVALID_STATUS, INVALID_STATUS) \ 186 | XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ 187 | XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ 188 | XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ 189 | XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ 190 | XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ 191 | XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ 192 | XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ 193 | XX(21, PAUSED, PAUSED) \ 194 | XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ 195 | XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ 196 | XX(24, USER, USER) \ 197 | 198 | 199 | #define HTTP_METHOD_MAP(XX) \ 200 | XX(0, DELETE, DELETE) \ 201 | XX(1, GET, GET) \ 202 | XX(2, HEAD, HEAD) \ 203 | XX(3, POST, POST) \ 204 | XX(4, PUT, PUT) \ 205 | XX(5, CONNECT, CONNECT) \ 206 | XX(6, OPTIONS, OPTIONS) \ 207 | XX(7, TRACE, TRACE) \ 208 | XX(8, COPY, COPY) \ 209 | XX(9, LOCK, LOCK) \ 210 | XX(10, MKCOL, MKCOL) \ 211 | XX(11, MOVE, MOVE) \ 212 | XX(12, PROPFIND, PROPFIND) \ 213 | XX(13, PROPPATCH, PROPPATCH) \ 214 | XX(14, SEARCH, SEARCH) \ 215 | XX(15, UNLOCK, UNLOCK) \ 216 | XX(16, BIND, BIND) \ 217 | XX(17, REBIND, REBIND) \ 218 | XX(18, UNBIND, UNBIND) \ 219 | XX(19, ACL, ACL) \ 220 | XX(20, REPORT, REPORT) \ 221 | XX(21, MKACTIVITY, MKACTIVITY) \ 222 | XX(22, CHECKOUT, CHECKOUT) \ 223 | XX(23, MERGE, MERGE) \ 224 | XX(24, MSEARCH, M-SEARCH) \ 225 | XX(25, NOTIFY, NOTIFY) \ 226 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 227 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 228 | XX(28, PATCH, PATCH) \ 229 | XX(29, PURGE, PURGE) \ 230 | XX(30, MKCALENDAR, MKCALENDAR) \ 231 | XX(31, LINK, LINK) \ 232 | XX(32, UNLINK, UNLINK) \ 233 | XX(33, SOURCE, SOURCE) \ 234 | 235 | 236 | #define RTSP_METHOD_MAP(XX) \ 237 | XX(1, GET, GET) \ 238 | XX(3, POST, POST) \ 239 | XX(6, OPTIONS, OPTIONS) \ 240 | XX(35, DESCRIBE, DESCRIBE) \ 241 | XX(36, ANNOUNCE, ANNOUNCE) \ 242 | XX(37, SETUP, SETUP) \ 243 | XX(38, PLAY, PLAY) \ 244 | XX(39, PAUSE, PAUSE) \ 245 | XX(40, TEARDOWN, TEARDOWN) \ 246 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 247 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 248 | XX(43, REDIRECT, REDIRECT) \ 249 | XX(44, RECORD, RECORD) \ 250 | XX(45, FLUSH, FLUSH) \ 251 | 252 | 253 | #define HTTP_ALL_METHOD_MAP(XX) \ 254 | XX(0, DELETE, DELETE) \ 255 | XX(1, GET, GET) \ 256 | XX(2, HEAD, HEAD) \ 257 | XX(3, POST, POST) \ 258 | XX(4, PUT, PUT) \ 259 | XX(5, CONNECT, CONNECT) \ 260 | XX(6, OPTIONS, OPTIONS) \ 261 | XX(7, TRACE, TRACE) \ 262 | XX(8, COPY, COPY) \ 263 | XX(9, LOCK, LOCK) \ 264 | XX(10, MKCOL, MKCOL) \ 265 | XX(11, MOVE, MOVE) \ 266 | XX(12, PROPFIND, PROPFIND) \ 267 | XX(13, PROPPATCH, PROPPATCH) \ 268 | XX(14, SEARCH, SEARCH) \ 269 | XX(15, UNLOCK, UNLOCK) \ 270 | XX(16, BIND, BIND) \ 271 | XX(17, REBIND, REBIND) \ 272 | XX(18, UNBIND, UNBIND) \ 273 | XX(19, ACL, ACL) \ 274 | XX(20, REPORT, REPORT) \ 275 | XX(21, MKACTIVITY, MKACTIVITY) \ 276 | XX(22, CHECKOUT, CHECKOUT) \ 277 | XX(23, MERGE, MERGE) \ 278 | XX(24, MSEARCH, M-SEARCH) \ 279 | XX(25, NOTIFY, NOTIFY) \ 280 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 281 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 282 | XX(28, PATCH, PATCH) \ 283 | XX(29, PURGE, PURGE) \ 284 | XX(30, MKCALENDAR, MKCALENDAR) \ 285 | XX(31, LINK, LINK) \ 286 | XX(32, UNLINK, UNLINK) \ 287 | XX(33, SOURCE, SOURCE) \ 288 | XX(34, PRI, PRI) \ 289 | XX(35, DESCRIBE, DESCRIBE) \ 290 | XX(36, ANNOUNCE, ANNOUNCE) \ 291 | XX(37, SETUP, SETUP) \ 292 | XX(38, PLAY, PLAY) \ 293 | XX(39, PAUSE, PAUSE) \ 294 | XX(40, TEARDOWN, TEARDOWN) \ 295 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 296 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 297 | XX(43, REDIRECT, REDIRECT) \ 298 | XX(44, RECORD, RECORD) \ 299 | XX(45, FLUSH, FLUSH) \ 300 | 301 | 302 | #ifdef __cplusplus 303 | } /* extern "C" */ 304 | #endif 305 | #endif /* LLLLHTTP_C_HEADERS_ */ 306 | 307 | #ifndef INCLUDE_LLHTTP_API_H_ 308 | #define INCLUDE_LLHTTP_API_H_ 309 | #ifdef __cplusplus 310 | extern "C" { 311 | #endif 312 | #include 313 | 314 | #if defined(__wasm__) 315 | #define LLHTTP_EXPORT __attribute__((visibility("default"))) 316 | #else 317 | #define LLHTTP_EXPORT 318 | #endif 319 | 320 | typedef llhttp__internal_t llhttp_t; 321 | typedef struct llhttp_settings_s llhttp_settings_t; 322 | 323 | typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); 324 | typedef int (*llhttp_cb)(llhttp_t*); 325 | 326 | struct llhttp_settings_s { 327 | /* Possible return values 0, -1, `HPE_PAUSED` */ 328 | llhttp_cb on_message_begin; 329 | 330 | /* Possible return values 0, -1, HPE_USER */ 331 | llhttp_data_cb on_url; 332 | llhttp_data_cb on_status; 333 | llhttp_data_cb on_header_field; 334 | llhttp_data_cb on_header_value; 335 | 336 | /* Possible return values: 337 | * 0 - Proceed normally 338 | * 1 - Assume that request/response has no body, and proceed to parsing the 339 | * next message 340 | * 2 - Assume absence of body (as above) and make `llhttp_execute()` return 341 | * `HPE_PAUSED_UPGRADE` 342 | * -1 - Error 343 | * `HPE_PAUSED` 344 | */ 345 | llhttp_cb on_headers_complete; 346 | 347 | /* Possible return values 0, -1, HPE_USER */ 348 | llhttp_data_cb on_body; 349 | 350 | /* Possible return values 0, -1, `HPE_PAUSED` */ 351 | llhttp_cb on_message_complete; 352 | 353 | /* When on_chunk_header is called, the current chunk length is stored 354 | * in parser->content_length. 355 | * Possible return values 0, -1, `HPE_PAUSED` 356 | */ 357 | llhttp_cb on_chunk_header; 358 | llhttp_cb on_chunk_complete; 359 | 360 | /* Information-only callbacks, return value is ignored */ 361 | llhttp_cb on_url_complete; 362 | llhttp_cb on_status_complete; 363 | llhttp_cb on_header_field_complete; 364 | llhttp_cb on_header_value_complete; 365 | }; 366 | 367 | /* Initialize the parser with specific type and user settings. 368 | * 369 | * NOTE: lifetime of `settings` has to be at least the same as the lifetime of 370 | * the `parser` here. In practice, `settings` has to be either a static 371 | * variable or be allocated with `malloc`, `new`, etc. 372 | */ 373 | LLHTTP_EXPORT 374 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 375 | const llhttp_settings_t* settings); 376 | 377 | #if defined(__wasm__) 378 | 379 | LLHTTP_EXPORT 380 | llhttp_t* llhttp_alloc(llhttp_type_t type); 381 | 382 | LLHTTP_EXPORT 383 | void llhttp_free(llhttp_t* parser); 384 | 385 | LLHTTP_EXPORT 386 | uint8_t llhttp_get_type(llhttp_t* parser); 387 | 388 | LLHTTP_EXPORT 389 | uint8_t llhttp_get_http_major(llhttp_t* parser); 390 | 391 | LLHTTP_EXPORT 392 | uint8_t llhttp_get_http_minor(llhttp_t* parser); 393 | 394 | LLHTTP_EXPORT 395 | uint8_t llhttp_get_method(llhttp_t* parser); 396 | 397 | LLHTTP_EXPORT 398 | int llhttp_get_status_code(llhttp_t* parser); 399 | 400 | LLHTTP_EXPORT 401 | uint8_t llhttp_get_upgrade(llhttp_t* parser); 402 | 403 | #endif // defined(__wasm__) 404 | 405 | /* Reset an already initialized parser back to the start state, preserving the 406 | * existing parser type, callback settings, user data, and lenient flags. 407 | */ 408 | LLHTTP_EXPORT 409 | void llhttp_reset(llhttp_t* parser); 410 | 411 | /* Initialize the settings object */ 412 | LLHTTP_EXPORT 413 | void llhttp_settings_init(llhttp_settings_t* settings); 414 | 415 | /* Parse full or partial request/response, invoking user callbacks along the 416 | * way. 417 | * 418 | * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing 419 | * interrupts, and such errno is returned from `llhttp_execute()`. If 420 | * `HPE_PAUSED` was used as a errno, the execution can be resumed with 421 | * `llhttp_resume()` call. 422 | * 423 | * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` 424 | * is returned after fully parsing the request/response. If the user wishes to 425 | * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. 426 | * 427 | * NOTE: if this function ever returns a non-pause type error, it will continue 428 | * to return the same error upon each successive call up until `llhttp_init()` 429 | * is called. 430 | */ 431 | LLHTTP_EXPORT 432 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); 433 | 434 | /* This method should be called when the other side has no further bytes to 435 | * send (e.g. shutdown of readable side of the TCP connection.) 436 | * 437 | * Requests without `Content-Length` and other messages might require treating 438 | * all incoming bytes as the part of the body, up to the last byte of the 439 | * connection. This method will invoke `on_message_complete()` callback if the 440 | * request was terminated safely. Otherwise a error code would be returned. 441 | */ 442 | LLHTTP_EXPORT 443 | llhttp_errno_t llhttp_finish(llhttp_t* parser); 444 | 445 | /* Returns `1` if the incoming message is parsed until the last byte, and has 446 | * to be completed by calling `llhttp_finish()` on EOF 447 | */ 448 | LLHTTP_EXPORT 449 | int llhttp_message_needs_eof(const llhttp_t* parser); 450 | 451 | /* Returns `1` if there might be any other messages following the last that was 452 | * successfully parsed. 453 | */ 454 | LLHTTP_EXPORT 455 | int llhttp_should_keep_alive(const llhttp_t* parser); 456 | 457 | /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set 458 | * appropriate error reason. 459 | * 460 | * Important: do not call this from user callbacks! User callbacks must return 461 | * `HPE_PAUSED` if pausing is required. 462 | */ 463 | LLHTTP_EXPORT 464 | void llhttp_pause(llhttp_t* parser); 465 | 466 | /* Might be called to resume the execution after the pause in user's callback. 467 | * See `llhttp_execute()` above for details. 468 | * 469 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. 470 | */ 471 | LLHTTP_EXPORT 472 | void llhttp_resume(llhttp_t* parser); 473 | 474 | /* Might be called to resume the execution after the pause in user's callback. 475 | * See `llhttp_execute()` above for details. 476 | * 477 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` 478 | */ 479 | LLHTTP_EXPORT 480 | void llhttp_resume_after_upgrade(llhttp_t* parser); 481 | 482 | /* Returns the latest return error */ 483 | LLHTTP_EXPORT 484 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); 485 | 486 | /* Returns the verbal explanation of the latest returned error. 487 | * 488 | * Note: User callback should set error reason when returning the error. See 489 | * `llhttp_set_error_reason()` for details. 490 | */ 491 | LLHTTP_EXPORT 492 | const char* llhttp_get_error_reason(const llhttp_t* parser); 493 | 494 | /* Assign verbal description to the returned error. Must be called in user 495 | * callbacks right before returning the errno. 496 | * 497 | * Note: `HPE_USER` error code might be useful in user callbacks. 498 | */ 499 | LLHTTP_EXPORT 500 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason); 501 | 502 | /* Returns the pointer to the last parsed byte before the returned error. The 503 | * pointer is relative to the `data` argument of `llhttp_execute()`. 504 | * 505 | * Note: this method might be useful for counting the number of parsed bytes. 506 | */ 507 | LLHTTP_EXPORT 508 | const char* llhttp_get_error_pos(const llhttp_t* parser); 509 | 510 | /* Returns textual name of error code */ 511 | LLHTTP_EXPORT 512 | const char* llhttp_errno_name(llhttp_errno_t err); 513 | 514 | /* Returns textual name of HTTP method */ 515 | LLHTTP_EXPORT 516 | const char* llhttp_method_name(llhttp_method_t method); 517 | 518 | 519 | /* Enables/disables lenient header value parsing (disabled by default). 520 | * 521 | * Lenient parsing disables header value token checks, extending llhttp's 522 | * protocol support to highly non-compliant clients/server. No 523 | * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when 524 | * lenient parsing is "on". 525 | * 526 | * **(USE AT YOUR OWN RISK)** 527 | */ 528 | LLHTTP_EXPORT 529 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); 530 | 531 | 532 | /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and 533 | * `Content-Length` headers (disabled by default). 534 | * 535 | * Normally `llhttp` would error when `Transfer-Encoding` is present in 536 | * conjunction with `Content-Length`. This error is important to prevent HTTP 537 | * request smuggling, but may be less desirable for small number of cases 538 | * involving legacy servers. 539 | * 540 | * **(USE AT YOUR OWN RISK)** 541 | */ 542 | LLHTTP_EXPORT 543 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); 544 | 545 | 546 | /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 547 | * requests responses. 548 | * 549 | * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) 550 | * the HTTP request/response after the request/response with `Connection: close` 551 | * and `Content-Length`. This is important to prevent cache poisoning attacks, 552 | * but might interact badly with outdated and insecure clients. With this flag 553 | * the extra request/response will be parsed normally. 554 | * 555 | * **(USE AT YOUR OWN RISK)** 556 | */ 557 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); 558 | 559 | #ifdef __cplusplus 560 | } /* extern "C" */ 561 | #endif 562 | #endif /* INCLUDE_LLHTTP_API_H_ */ 563 | 564 | #endif /* INCLUDE_LLHTTP_H_ */ 565 | -------------------------------------------------------------------------------- /bench/extern/picohttpparser/picohttpparser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, 3 | * Shigeo Mitsunari 4 | * 5 | * The software is licensed under either the MIT License (below) or the Perl 6 | * license. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #ifdef __SSE4_2__ 31 | #ifdef _MSC_VER 32 | #include 33 | #else 34 | #include 35 | #endif 36 | #endif 37 | #include "picohttpparser.h" 38 | 39 | #if __GNUC__ >= 3 40 | #define likely(x) __builtin_expect(!!(x), 1) 41 | #define unlikely(x) __builtin_expect(!!(x), 0) 42 | #else 43 | #define likely(x) (x) 44 | #define unlikely(x) (x) 45 | #endif 46 | 47 | #ifdef _MSC_VER 48 | #define ALIGNED(n) _declspec(align(n)) 49 | #else 50 | #define ALIGNED(n) __attribute__((aligned(n))) 51 | #endif 52 | 53 | #define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) 54 | 55 | #define CHECK_EOF() \ 56 | if (buf == buf_end) { \ 57 | *ret = -2; \ 58 | return NULL; \ 59 | } 60 | 61 | #define EXPECT_CHAR_NO_CHECK(ch) \ 62 | if (*buf++ != ch) { \ 63 | *ret = -1; \ 64 | return NULL; \ 65 | } 66 | 67 | #define EXPECT_CHAR(ch) \ 68 | CHECK_EOF(); \ 69 | EXPECT_CHAR_NO_CHECK(ch); 70 | 71 | #define ADVANCE_TOKEN(tok, toklen) \ 72 | do { \ 73 | const char *tok_start = buf; \ 74 | static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ 75 | int found2; \ 76 | buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ 77 | if (!found2) { \ 78 | CHECK_EOF(); \ 79 | } \ 80 | while (1) { \ 81 | if (*buf == ' ') { \ 82 | break; \ 83 | } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ 84 | if ((unsigned char)*buf < '\040' || *buf == '\177') { \ 85 | *ret = -1; \ 86 | return NULL; \ 87 | } \ 88 | } \ 89 | ++buf; \ 90 | CHECK_EOF(); \ 91 | } \ 92 | tok = tok_start; \ 93 | toklen = buf - tok_start; \ 94 | } while (0) 95 | 96 | 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" 97 | "\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" 98 | "\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" 99 | "\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" 100 | "\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" 101 | "\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" 102 | "\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" 103 | "\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"; 104 | 105 | static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) 106 | { 107 | *found = 0; 108 | #if __SSE4_2__ 109 | if (likely(buf_end - buf >= 16)) { 110 | __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); 111 | 112 | size_t left = (buf_end - buf) & ~15; 113 | do { 114 | __m128i b16 = _mm_loadu_si128((const __m128i *)buf); 115 | int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); 116 | if (unlikely(r != 16)) { 117 | buf += r; 118 | *found = 1; 119 | break; 120 | } 121 | buf += 16; 122 | left -= 16; 123 | } while (likely(left != 0)); 124 | } 125 | #else 126 | /* suppress unused parameter warning */ 127 | (void)buf_end; 128 | (void)ranges; 129 | (void)ranges_size; 130 | #endif 131 | return buf; 132 | } 133 | 134 | static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) 135 | { 136 | const char *token_start = buf; 137 | 138 | #ifdef __SSE4_2__ 139 | static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ 140 | "\012\037" /* allow SP and up to but not including DEL */ 141 | "\177\177"; /* allow chars w. MSB set */ 142 | int found; 143 | buf = findchar_fast(buf, buf_end, ranges1, 6, &found); 144 | if (found) 145 | goto FOUND_CTL; 146 | #else 147 | /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ 148 | while (likely(buf_end - buf >= 8)) { 149 | #define DOIT() \ 150 | do { \ 151 | if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ 152 | goto NonPrintable; \ 153 | ++buf; \ 154 | } while (0) 155 | DOIT(); 156 | DOIT(); 157 | DOIT(); 158 | DOIT(); 159 | DOIT(); 160 | DOIT(); 161 | DOIT(); 162 | DOIT(); 163 | #undef DOIT 164 | continue; 165 | NonPrintable: 166 | if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { 167 | goto FOUND_CTL; 168 | } 169 | ++buf; 170 | } 171 | #endif 172 | for (;; ++buf) { 173 | CHECK_EOF(); 174 | if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { 175 | if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { 176 | goto FOUND_CTL; 177 | } 178 | } 179 | } 180 | FOUND_CTL: 181 | if (likely(*buf == '\015')) { 182 | ++buf; 183 | EXPECT_CHAR('\012'); 184 | *token_len = buf - 2 - token_start; 185 | } else if (*buf == '\012') { 186 | *token_len = buf - token_start; 187 | ++buf; 188 | } else { 189 | *ret = -1; 190 | return NULL; 191 | } 192 | *token = token_start; 193 | 194 | return buf; 195 | } 196 | 197 | static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) 198 | { 199 | int ret_cnt = 0; 200 | buf = last_len < 3 ? buf : buf + last_len - 3; 201 | 202 | while (1) { 203 | CHECK_EOF(); 204 | if (*buf == '\015') { 205 | ++buf; 206 | CHECK_EOF(); 207 | EXPECT_CHAR('\012'); 208 | ++ret_cnt; 209 | } else if (*buf == '\012') { 210 | ++buf; 211 | ++ret_cnt; 212 | } else { 213 | ++buf; 214 | ret_cnt = 0; 215 | } 216 | if (ret_cnt == 2) { 217 | return buf; 218 | } 219 | } 220 | 221 | *ret = -2; 222 | return NULL; 223 | } 224 | 225 | #define PARSE_INT(valp_, mul_) \ 226 | if (*buf < '0' || '9' < *buf) { \ 227 | buf++; \ 228 | *ret = -1; \ 229 | return NULL; \ 230 | } \ 231 | *(valp_) = (mul_) * (*buf++ - '0'); 232 | 233 | #define PARSE_INT_3(valp_) \ 234 | do { \ 235 | int res_ = 0; \ 236 | PARSE_INT(&res_, 100) \ 237 | *valp_ = res_; \ 238 | PARSE_INT(&res_, 10) \ 239 | *valp_ += res_; \ 240 | PARSE_INT(&res_, 1) \ 241 | *valp_ += res_; \ 242 | } while (0) 243 | 244 | /* returned pointer is always within [buf, buf_end), or null */ 245 | static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, 246 | int *ret) 247 | { 248 | /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 249 | * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ 250 | static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ 251 | "\"\"" /* 0x22 */ 252 | "()" /* 0x28,0x29 */ 253 | ",," /* 0x2c */ 254 | "//" /* 0x2f */ 255 | ":@" /* 0x3a-0x40 */ 256 | "[]" /* 0x5b-0x5d */ 257 | "{\xff"; /* 0x7b-0xff */ 258 | const char *buf_start = buf; 259 | int found; 260 | buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); 261 | if (!found) { 262 | CHECK_EOF(); 263 | } 264 | while (1) { 265 | if (*buf == next_char) { 266 | break; 267 | } else if (!token_char_map[(unsigned char)*buf]) { 268 | *ret = -1; 269 | return NULL; 270 | } 271 | ++buf; 272 | CHECK_EOF(); 273 | } 274 | *token = buf_start; 275 | *token_len = buf - buf_start; 276 | return buf; 277 | } 278 | 279 | /* returned pointer is always within [buf, buf_end), or null */ 280 | static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) 281 | { 282 | /* we want at least [HTTP/1.] to try to parse */ 283 | if (buf_end - buf < 9) { 284 | *ret = -2; 285 | return NULL; 286 | } 287 | EXPECT_CHAR_NO_CHECK('H'); 288 | EXPECT_CHAR_NO_CHECK('T'); 289 | EXPECT_CHAR_NO_CHECK('T'); 290 | EXPECT_CHAR_NO_CHECK('P'); 291 | EXPECT_CHAR_NO_CHECK('/'); 292 | EXPECT_CHAR_NO_CHECK('1'); 293 | EXPECT_CHAR_NO_CHECK('.'); 294 | PARSE_INT(minor_version, 1); 295 | return buf; 296 | } 297 | 298 | static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, 299 | size_t max_headers, int *ret) 300 | { 301 | for (;; ++*num_headers) { 302 | CHECK_EOF(); 303 | if (*buf == '\015') { 304 | ++buf; 305 | EXPECT_CHAR('\012'); 306 | break; 307 | } else if (*buf == '\012') { 308 | ++buf; 309 | break; 310 | } 311 | if (*num_headers == max_headers) { 312 | *ret = -1; 313 | return NULL; 314 | } 315 | if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { 316 | /* parsing name, but do not discard SP before colon, see 317 | * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ 318 | if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { 319 | return NULL; 320 | } 321 | if (headers[*num_headers].name_len == 0) { 322 | *ret = -1; 323 | return NULL; 324 | } 325 | ++buf; 326 | for (;; ++buf) { 327 | CHECK_EOF(); 328 | if (!(*buf == ' ' || *buf == '\t')) { 329 | break; 330 | } 331 | } 332 | } else { 333 | headers[*num_headers].name = NULL; 334 | headers[*num_headers].name_len = 0; 335 | } 336 | const char *value; 337 | size_t value_len; 338 | if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { 339 | return NULL; 340 | } 341 | /* remove trailing SPs and HTABs */ 342 | const char *value_end = value + value_len; 343 | for (; value_end != value; --value_end) { 344 | const char c = *(value_end - 1); 345 | if (!(c == ' ' || c == '\t')) { 346 | break; 347 | } 348 | } 349 | headers[*num_headers].value = value; 350 | headers[*num_headers].value_len = value_end - value; 351 | } 352 | return buf; 353 | } 354 | 355 | static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, 356 | size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, 357 | size_t max_headers, int *ret) 358 | { 359 | /* skip first empty line (some clients add CRLF after POST content) */ 360 | CHECK_EOF(); 361 | if (*buf == '\015') { 362 | ++buf; 363 | EXPECT_CHAR('\012'); 364 | } else if (*buf == '\012') { 365 | ++buf; 366 | } 367 | 368 | /* parse request line */ 369 | if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { 370 | return NULL; 371 | } 372 | do { 373 | ++buf; 374 | CHECK_EOF(); 375 | } while (*buf == ' '); 376 | ADVANCE_TOKEN(*path, *path_len); 377 | do { 378 | ++buf; 379 | CHECK_EOF(); 380 | } while (*buf == ' '); 381 | if (*method_len == 0 || *path_len == 0) { 382 | *ret = -1; 383 | return NULL; 384 | } 385 | if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { 386 | return NULL; 387 | } 388 | if (*buf == '\015') { 389 | ++buf; 390 | EXPECT_CHAR('\012'); 391 | } else if (*buf == '\012') { 392 | ++buf; 393 | } else { 394 | *ret = -1; 395 | return NULL; 396 | } 397 | 398 | return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); 399 | } 400 | 401 | int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, 402 | size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) 403 | { 404 | const char *buf = buf_start, *buf_end = buf_start + len; 405 | size_t max_headers = *num_headers; 406 | int r; 407 | 408 | *method = NULL; 409 | *method_len = 0; 410 | *path = NULL; 411 | *path_len = 0; 412 | *minor_version = -1; 413 | *num_headers = 0; 414 | 415 | /* if last_len != 0, check if the request is complete (a fast countermeasure 416 | againt slowloris */ 417 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 418 | return r; 419 | } 420 | 421 | if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, 422 | &r)) == NULL) { 423 | return r; 424 | } 425 | 426 | return (int)(buf - buf_start); 427 | } 428 | 429 | static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, 430 | size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) 431 | { 432 | /* parse "HTTP/1.x" */ 433 | if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { 434 | return NULL; 435 | } 436 | /* skip space */ 437 | if (*buf != ' ') { 438 | *ret = -1; 439 | return NULL; 440 | } 441 | do { 442 | ++buf; 443 | CHECK_EOF(); 444 | } while (*buf == ' '); 445 | /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ 446 | if (buf_end - buf < 4) { 447 | *ret = -2; 448 | return NULL; 449 | } 450 | PARSE_INT_3(status); 451 | 452 | /* get message including preceding space */ 453 | if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { 454 | return NULL; 455 | } 456 | if (*msg_len == 0) { 457 | /* ok */ 458 | } else if (**msg == ' ') { 459 | /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP 460 | * before running past the end of the given buffer. */ 461 | do { 462 | ++*msg; 463 | --*msg_len; 464 | } while (**msg == ' '); 465 | } else { 466 | /* garbage found after status code */ 467 | *ret = -1; 468 | return NULL; 469 | } 470 | 471 | return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); 472 | } 473 | 474 | int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, 475 | struct phr_header *headers, size_t *num_headers, size_t last_len) 476 | { 477 | const char *buf = buf_start, *buf_end = buf + len; 478 | size_t max_headers = *num_headers; 479 | int r; 480 | 481 | *minor_version = -1; 482 | *status = 0; 483 | *msg = NULL; 484 | *msg_len = 0; 485 | *num_headers = 0; 486 | 487 | /* if last_len != 0, check if the response is complete (a fast countermeasure 488 | against slowloris */ 489 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 490 | return r; 491 | } 492 | 493 | if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { 494 | return r; 495 | } 496 | 497 | return (int)(buf - buf_start); 498 | } 499 | 500 | int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) 501 | { 502 | const char *buf = buf_start, *buf_end = buf + len; 503 | size_t max_headers = *num_headers; 504 | int r; 505 | 506 | *num_headers = 0; 507 | 508 | /* if last_len != 0, check if the response is complete (a fast countermeasure 509 | against slowloris */ 510 | if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { 511 | return r; 512 | } 513 | 514 | if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { 515 | return r; 516 | } 517 | 518 | return (int)(buf - buf_start); 519 | } 520 | 521 | enum { 522 | CHUNKED_IN_CHUNK_SIZE, 523 | CHUNKED_IN_CHUNK_EXT, 524 | CHUNKED_IN_CHUNK_DATA, 525 | CHUNKED_IN_CHUNK_CRLF, 526 | CHUNKED_IN_TRAILERS_LINE_HEAD, 527 | CHUNKED_IN_TRAILERS_LINE_MIDDLE 528 | }; 529 | 530 | static int decode_hex(int ch) 531 | { 532 | if ('0' <= ch && ch <= '9') { 533 | return ch - '0'; 534 | } else if ('A' <= ch && ch <= 'F') { 535 | return ch - 'A' + 0xa; 536 | } else if ('a' <= ch && ch <= 'f') { 537 | return ch - 'a' + 0xa; 538 | } else { 539 | return -1; 540 | } 541 | } 542 | 543 | ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) 544 | { 545 | size_t dst = 0, src = 0, bufsz = *_bufsz; 546 | ssize_t ret = -2; /* incomplete */ 547 | 548 | while (1) { 549 | switch (decoder->_state) { 550 | case CHUNKED_IN_CHUNK_SIZE: 551 | for (;; ++src) { 552 | int v; 553 | if (src == bufsz) 554 | goto Exit; 555 | if ((v = decode_hex(buf[src])) == -1) { 556 | if (decoder->_hex_count == 0) { 557 | ret = -1; 558 | goto Exit; 559 | } 560 | break; 561 | } 562 | if (decoder->_hex_count == sizeof(size_t) * 2) { 563 | ret = -1; 564 | goto Exit; 565 | } 566 | decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; 567 | ++decoder->_hex_count; 568 | } 569 | decoder->_hex_count = 0; 570 | decoder->_state = CHUNKED_IN_CHUNK_EXT; 571 | /* fallthru */ 572 | case CHUNKED_IN_CHUNK_EXT: 573 | /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ 574 | for (;; ++src) { 575 | if (src == bufsz) 576 | goto Exit; 577 | if (buf[src] == '\012') 578 | break; 579 | } 580 | ++src; 581 | if (decoder->bytes_left_in_chunk == 0) { 582 | if (decoder->consume_trailer) { 583 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; 584 | break; 585 | } else { 586 | goto Complete; 587 | } 588 | } 589 | decoder->_state = CHUNKED_IN_CHUNK_DATA; 590 | /* fallthru */ 591 | case CHUNKED_IN_CHUNK_DATA: { 592 | size_t avail = bufsz - src; 593 | if (avail < decoder->bytes_left_in_chunk) { 594 | if (dst != src) 595 | memmove(buf + dst, buf + src, avail); 596 | src += avail; 597 | dst += avail; 598 | decoder->bytes_left_in_chunk -= avail; 599 | goto Exit; 600 | } 601 | if (dst != src) 602 | memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); 603 | src += decoder->bytes_left_in_chunk; 604 | dst += decoder->bytes_left_in_chunk; 605 | decoder->bytes_left_in_chunk = 0; 606 | decoder->_state = CHUNKED_IN_CHUNK_CRLF; 607 | } 608 | /* fallthru */ 609 | case CHUNKED_IN_CHUNK_CRLF: 610 | for (;; ++src) { 611 | if (src == bufsz) 612 | goto Exit; 613 | if (buf[src] != '\015') 614 | break; 615 | } 616 | if (buf[src] != '\012') { 617 | ret = -1; 618 | goto Exit; 619 | } 620 | ++src; 621 | decoder->_state = CHUNKED_IN_CHUNK_SIZE; 622 | break; 623 | case CHUNKED_IN_TRAILERS_LINE_HEAD: 624 | for (;; ++src) { 625 | if (src == bufsz) 626 | goto Exit; 627 | if (buf[src] != '\015') 628 | break; 629 | } 630 | if (buf[src++] == '\012') 631 | goto Complete; 632 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; 633 | /* fallthru */ 634 | case CHUNKED_IN_TRAILERS_LINE_MIDDLE: 635 | for (;; ++src) { 636 | if (src == bufsz) 637 | goto Exit; 638 | if (buf[src] == '\012') 639 | break; 640 | } 641 | ++src; 642 | decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; 643 | break; 644 | default: 645 | assert(!"decoder is corrupt"); 646 | } 647 | } 648 | 649 | Complete: 650 | ret = bufsz - src; 651 | Exit: 652 | if (dst != src) 653 | memmove(buf + dst, buf + src, bufsz - src); 654 | *_bufsz = dst; 655 | return ret; 656 | } 657 | 658 | int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) 659 | { 660 | return decoder->_state == CHUNKED_IN_CHUNK_DATA; 661 | } 662 | 663 | #undef CHECK_EOF 664 | #undef EXPECT_CHAR 665 | #undef ADVANCE_TOKEN 666 | -------------------------------------------------------------------------------- /bench/extern/picohttpparser/picohttpparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, 3 | * Shigeo Mitsunari 4 | * 5 | * The software is licensed under either the MIT License (below) or the Perl 6 | * license. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #ifndef picohttpparser_h 28 | #define picohttpparser_h 29 | 30 | #include 31 | 32 | #ifdef _MSC_VER 33 | #define ssize_t intptr_t 34 | #endif 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | /* contains name and value of a header (name == NULL if is a continuing line 41 | * of a multiline header */ 42 | struct phr_header { 43 | const char *name; 44 | size_t name_len; 45 | const char *value; 46 | size_t value_len; 47 | }; 48 | 49 | /* returns number of bytes consumed if successful, -2 if request is partial, 50 | * -1 if failed */ 51 | int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, 52 | int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); 53 | 54 | /* ditto */ 55 | int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, 56 | struct phr_header *headers, size_t *num_headers, size_t last_len); 57 | 58 | /* ditto */ 59 | int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); 60 | 61 | /* should be zero-filled before start */ 62 | struct phr_chunked_decoder { 63 | size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ 64 | char consume_trailer; /* if trailing headers should be consumed */ 65 | char _hex_count; 66 | char _state; 67 | }; 68 | 69 | /* the function rewrites the buffer given as (buf, bufsz) removing the chunked- 70 | * encoding headers. When the function returns without an error, bufsz is 71 | * updated to the length of the decoded data available. Applications should 72 | * repeatedly call the function while it returns -2 (incomplete) every time 73 | * supplying newly arrived data. If the end of the chunked-encoded data is 74 | * found, the function returns a non-negative number indicating the number of 75 | * octets left undecoded, that starts from the offset returned by `*bufsz`. 76 | * Returns -1 on error. 77 | */ 78 | ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); 79 | 80 | /* returns if the chunked decoder is in middle of chunked data */ 81 | int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /bench/httpparser.d: -------------------------------------------------------------------------------- 1 | module httpparser; 2 | 3 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to 7 | * deal in the Software without restriction, including without limitation the 8 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | * sell copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | * IN THE SOFTWARE. 22 | */ 23 | 24 | import core.stdc.config; 25 | 26 | extern (C): 27 | 28 | /* Also update SONAME in the Makefile whenever you change these. */ 29 | enum HTTP_PARSER_VERSION_MAJOR = 2; 30 | enum HTTP_PARSER_VERSION_MINOR = 9; 31 | enum HTTP_PARSER_VERSION_PATCH = 4; 32 | 33 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 34 | * faster 35 | */ 36 | 37 | enum HTTP_PARSER_STRICT = 1; 38 | 39 | /* Maximium header size allowed. If the macro is not defined 40 | * before including this header then the default is used. To 41 | * change the maximum header size, define the macro in the build 42 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 43 | * the effective limit on the size of the header, define the macro 44 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 45 | */ 46 | 47 | enum HTTP_MAX_HEADER_SIZE = 80 * 1024; 48 | 49 | /* Callbacks should return non-zero to indicate an error. The parser will 50 | * then halt execution. 51 | * 52 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 53 | * returning '1' from on_headers_complete will tell the parser that it 54 | * should not expect a body. This is used when receiving a response to a 55 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 56 | * chunked' headers that indicate the presence of a body. 57 | * 58 | * Returning `2` from on_headers_complete will tell parser that it should not 59 | * expect neither a body nor any futher responses on this connection. This is 60 | * useful for handling responses to a CONNECT request which may not contain 61 | * `Upgrade` or `Connection: upgrade` headers. 62 | * 63 | * http_data_cb does not return data chunks. It will be called arbitrarily 64 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 65 | * each providing just a few characters more data. 66 | */ 67 | alias http_data_cb = int function (http_parser*, const(char)* at, size_t length); 68 | alias http_cb = int function (http_parser*); 69 | 70 | /* Status Codes */ 71 | 72 | enum http_status 73 | { 74 | HTTP_STATUS_CONTINUE = 100, 75 | HTTP_STATUS_SWITCHING_PROTOCOLS = 101, 76 | HTTP_STATUS_PROCESSING = 102, 77 | HTTP_STATUS_OK = 200, 78 | HTTP_STATUS_CREATED = 201, 79 | HTTP_STATUS_ACCEPTED = 202, 80 | HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, 81 | HTTP_STATUS_NO_CONTENT = 204, 82 | HTTP_STATUS_RESET_CONTENT = 205, 83 | HTTP_STATUS_PARTIAL_CONTENT = 206, 84 | HTTP_STATUS_MULTI_STATUS = 207, 85 | HTTP_STATUS_ALREADY_REPORTED = 208, 86 | HTTP_STATUS_IM_USED = 226, 87 | HTTP_STATUS_MULTIPLE_CHOICES = 300, 88 | HTTP_STATUS_MOVED_PERMANENTLY = 301, 89 | HTTP_STATUS_FOUND = 302, 90 | HTTP_STATUS_SEE_OTHER = 303, 91 | HTTP_STATUS_NOT_MODIFIED = 304, 92 | HTTP_STATUS_USE_PROXY = 305, 93 | HTTP_STATUS_TEMPORARY_REDIRECT = 307, 94 | HTTP_STATUS_PERMANENT_REDIRECT = 308, 95 | HTTP_STATUS_BAD_REQUEST = 400, 96 | HTTP_STATUS_UNAUTHORIZED = 401, 97 | HTTP_STATUS_PAYMENT_REQUIRED = 402, 98 | HTTP_STATUS_FORBIDDEN = 403, 99 | HTTP_STATUS_NOT_FOUND = 404, 100 | HTTP_STATUS_METHOD_NOT_ALLOWED = 405, 101 | HTTP_STATUS_NOT_ACCEPTABLE = 406, 102 | HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, 103 | HTTP_STATUS_REQUEST_TIMEOUT = 408, 104 | HTTP_STATUS_CONFLICT = 409, 105 | HTTP_STATUS_GONE = 410, 106 | HTTP_STATUS_LENGTH_REQUIRED = 411, 107 | HTTP_STATUS_PRECONDITION_FAILED = 412, 108 | HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, 109 | HTTP_STATUS_URI_TOO_LONG = 414, 110 | HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, 111 | HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, 112 | HTTP_STATUS_EXPECTATION_FAILED = 417, 113 | HTTP_STATUS_MISDIRECTED_REQUEST = 421, 114 | HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, 115 | HTTP_STATUS_LOCKED = 423, 116 | HTTP_STATUS_FAILED_DEPENDENCY = 424, 117 | HTTP_STATUS_UPGRADE_REQUIRED = 426, 118 | HTTP_STATUS_PRECONDITION_REQUIRED = 428, 119 | HTTP_STATUS_TOO_MANY_REQUESTS = 429, 120 | HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 121 | HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, 122 | HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, 123 | HTTP_STATUS_NOT_IMPLEMENTED = 501, 124 | HTTP_STATUS_BAD_GATEWAY = 502, 125 | HTTP_STATUS_SERVICE_UNAVAILABLE = 503, 126 | HTTP_STATUS_GATEWAY_TIMEOUT = 504, 127 | HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, 128 | HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, 129 | HTTP_STATUS_INSUFFICIENT_STORAGE = 507, 130 | HTTP_STATUS_LOOP_DETECTED = 508, 131 | HTTP_STATUS_NOT_EXTENDED = 510, 132 | HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511 133 | } 134 | 135 | /* Request Methods */ 136 | 137 | /* pathological */ 138 | 139 | /* WebDAV */ 140 | 141 | /* subversion */ 142 | 143 | /* upnp */ 144 | 145 | /* RFC-5789 */ 146 | 147 | /* CalDAV */ 148 | 149 | /* RFC-2068, section 19.6.1.2 */ 150 | 151 | /* icecast */ 152 | 153 | enum http_method 154 | { 155 | HTTP_DELETE = 0, 156 | HTTP_GET = 1, 157 | HTTP_HEAD = 2, 158 | HTTP_POST = 3, 159 | HTTP_PUT = 4, 160 | HTTP_CONNECT = 5, 161 | HTTP_OPTIONS = 6, 162 | HTTP_TRACE = 7, 163 | HTTP_COPY = 8, 164 | HTTP_LOCK = 9, 165 | HTTP_MKCOL = 10, 166 | HTTP_MOVE = 11, 167 | HTTP_PROPFIND = 12, 168 | HTTP_PROPPATCH = 13, 169 | HTTP_SEARCH = 14, 170 | HTTP_UNLOCK = 15, 171 | HTTP_BIND = 16, 172 | HTTP_REBIND = 17, 173 | HTTP_UNBIND = 18, 174 | HTTP_ACL = 19, 175 | HTTP_REPORT = 20, 176 | HTTP_MKACTIVITY = 21, 177 | HTTP_CHECKOUT = 22, 178 | HTTP_MERGE = 23, 179 | HTTP_MSEARCH = 24, 180 | HTTP_NOTIFY = 25, 181 | HTTP_SUBSCRIBE = 26, 182 | HTTP_UNSUBSCRIBE = 27, 183 | HTTP_PATCH = 28, 184 | HTTP_PURGE = 29, 185 | HTTP_MKCALENDAR = 30, 186 | HTTP_LINK = 31, 187 | HTTP_UNLINK = 32, 188 | HTTP_SOURCE = 33 189 | } 190 | 191 | enum http_parser_type 192 | { 193 | HTTP_REQUEST = 0, 194 | HTTP_RESPONSE = 1, 195 | HTTP_BOTH = 2 196 | } 197 | 198 | /* Flag values for http_parser.flags field */ 199 | enum flags 200 | { 201 | F_CHUNKED = 1 << 0, 202 | F_CONNECTION_KEEP_ALIVE = 1 << 1, 203 | F_CONNECTION_CLOSE = 1 << 2, 204 | F_CONNECTION_UPGRADE = 1 << 3, 205 | F_TRAILING = 1 << 4, 206 | F_UPGRADE = 1 << 5, 207 | F_SKIPBODY = 1 << 6, 208 | F_CONTENTLENGTH = 1 << 7 209 | } 210 | 211 | /* Map for errno-related constants 212 | * 213 | * The provided argument should be a macro that takes 2 arguments. 214 | */ 215 | 216 | /* No error */ 217 | 218 | /* Callback-related errors */ 219 | 220 | /* Parsing-related errors */ 221 | 222 | /* Define HPE_* values for each errno value above */ 223 | enum http_errno 224 | { 225 | HPE_OK = 0, 226 | HPE_CB_message_begin = 1, 227 | HPE_CB_url = 2, 228 | HPE_CB_header_field = 3, 229 | HPE_CB_header_value = 4, 230 | HPE_CB_headers_complete = 5, 231 | HPE_CB_body = 6, 232 | HPE_CB_message_complete = 7, 233 | HPE_CB_status = 8, 234 | HPE_CB_chunk_header = 9, 235 | HPE_CB_chunk_complete = 10, 236 | HPE_INVALID_EOF_STATE = 11, 237 | HPE_HEADER_OVERFLOW = 12, 238 | HPE_CLOSED_CONNECTION = 13, 239 | HPE_INVALID_VERSION = 14, 240 | HPE_INVALID_STATUS = 15, 241 | HPE_INVALID_METHOD = 16, 242 | HPE_INVALID_URL = 17, 243 | HPE_INVALID_HOST = 18, 244 | HPE_INVALID_PORT = 19, 245 | HPE_INVALID_PATH = 20, 246 | HPE_INVALID_QUERY_STRING = 21, 247 | HPE_INVALID_FRAGMENT = 22, 248 | HPE_LF_EXPECTED = 23, 249 | HPE_INVALID_HEADER_TOKEN = 24, 250 | HPE_INVALID_CONTENT_LENGTH = 25, 251 | HPE_UNEXPECTED_CONTENT_LENGTH = 26, 252 | HPE_INVALID_CHUNK_SIZE = 27, 253 | HPE_INVALID_CONSTANT = 28, 254 | HPE_INVALID_INTERNAL_STATE = 29, 255 | HPE_STRICT = 30, 256 | HPE_PAUSED = 31, 257 | HPE_UNKNOWN = 32, 258 | HPE_INVALID_TRANSFER_ENCODING = 33 259 | } 260 | 261 | /* Get an http_errno value from an http_parser */ 262 | 263 | struct http_parser 264 | { 265 | import std.bitmanip : bitfields; 266 | 267 | mixin(bitfields!( 268 | uint, "type", 2, 269 | uint, "flags", 8, 270 | uint, "state", 7, 271 | uint, "header_state", 7, 272 | uint, "index", 5, 273 | uint, "uses_transfer_encoding", 1, 274 | uint, "allow_chunked_length", 1, 275 | uint, "lenient_http_headers", 1)); 276 | 277 | /** PRIVATE **/ 278 | /* enum http_parser_type */ 279 | /* F_* values from 'flags' enum; semi-public */ 280 | /* enum state from http_parser.c */ 281 | /* enum header_state from http_parser.c */ 282 | /* index into current matcher */ 283 | /* Transfer-Encoding header is present */ 284 | /* Allow headers with both 285 | * `Content-Length` and 286 | * `Transfer-Encoding: chunked` set */ 287 | 288 | uint nread; /* # bytes read in various scenarios */ 289 | ulong content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) 290 | * if no Content-Length header. 291 | */ 292 | 293 | /** READ-ONLY **/ 294 | ushort http_major; 295 | ushort http_minor; 296 | 297 | mixin(bitfields!( 298 | uint, "status_code", 16, 299 | uint, "method", 8, 300 | uint, "http_errno", 7, 301 | uint, "upgrade", 1)); 302 | 303 | /* responses only */ 304 | /* requests only */ 305 | 306 | /* 1 = Upgrade header was present and the parser has exited because of that. 307 | * 0 = No upgrade header present. 308 | * Should be checked when http_parser_execute() returns in addition to 309 | * error checking. 310 | */ 311 | 312 | /** PUBLIC **/ 313 | void* data; /* A pointer to get hook to the "connection" or "socket" object */ 314 | } 315 | 316 | struct http_parser_settings 317 | { 318 | http_cb on_message_begin; 319 | http_data_cb on_url; 320 | http_data_cb on_status; 321 | http_data_cb on_header_field; 322 | http_data_cb on_header_value; 323 | http_cb on_headers_complete; 324 | http_data_cb on_body; 325 | http_cb on_message_complete; 326 | /* When on_chunk_header is called, the current chunk length is stored 327 | * in parser->content_length. 328 | */ 329 | http_cb on_chunk_header; 330 | http_cb on_chunk_complete; 331 | } 332 | 333 | enum http_parser_url_fields 334 | { 335 | UF_SCHEMA = 0, 336 | UF_HOST = 1, 337 | UF_PORT = 2, 338 | UF_PATH = 3, 339 | UF_QUERY = 4, 340 | UF_FRAGMENT = 5, 341 | UF_USERINFO = 6, 342 | UF_MAX = 7 343 | } 344 | 345 | /* Result structure for http_parser_parse_url(). 346 | * 347 | * Callers should index into field_data[] with UF_* values iff field_set 348 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 349 | * because we probably have padding left over), we convert any port to 350 | * a uint16_t. 351 | */ 352 | struct http_parser_url 353 | { 354 | ushort field_set; /* Bitmask of (1 << UF_*) values */ 355 | ushort port; /* Converted UF_PORT string */ 356 | 357 | /* Offset into buffer in which field starts */ 358 | /* Length of run in buffer */ 359 | struct _Anonymous_0 360 | { 361 | ushort off; 362 | ushort len; 363 | } 364 | 365 | _Anonymous_0[http_parser_url_fields.UF_MAX] field_data; 366 | } 367 | 368 | /* Returns the library version. Bits 16-23 contain the major version number, 369 | * bits 8-15 the minor version number and bits 0-7 the patch level. 370 | * Usage example: 371 | * 372 | * unsigned long version = http_parser_version(); 373 | * unsigned major = (version >> 16) & 255; 374 | * unsigned minor = (version >> 8) & 255; 375 | * unsigned patch = version & 255; 376 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 377 | */ 378 | c_ulong http_parser_version (); 379 | 380 | void http_parser_init (http_parser* parser, http_parser_type type); 381 | 382 | /* Initialize http_parser_settings members to 0 383 | */ 384 | void http_parser_settings_init (http_parser_settings* settings); 385 | 386 | /* Executes the parser. Returns number of parsed bytes. Sets 387 | * `parser->http_errno` on error. */ 388 | size_t http_parser_execute ( 389 | http_parser* parser, 390 | const(http_parser_settings)* settings, 391 | const(char)* data, 392 | size_t len); 393 | 394 | /* If http_should_keep_alive() in the on_headers_complete or 395 | * on_message_complete callback returns 0, then this should be 396 | * the last message on the connection. 397 | * If you are the server, respond with the "Connection: close" header. 398 | * If you are the client, close the connection. 399 | */ 400 | int http_should_keep_alive (const(http_parser)* parser); 401 | 402 | /* Returns a string version of the HTTP method. */ 403 | const(char)* http_method_str (http_method m); 404 | 405 | /* Returns a string version of the HTTP status code. */ 406 | const(char)* http_status_str (http_status s); 407 | 408 | /* Return a string name of the given error */ 409 | const(char)* http_errno_name (http_errno err); 410 | 411 | /* Return a string description of the given error */ 412 | const(char)* http_errno_description (http_errno err); 413 | 414 | /* Initialize all http_parser_url members to 0 */ 415 | void http_parser_url_init (http_parser_url* u); 416 | 417 | /* Parse a URL; return nonzero on failure */ 418 | int http_parser_parse_url ( 419 | const(char)* buf, 420 | size_t buflen, 421 | int is_connect, 422 | http_parser_url* u); 423 | 424 | /* Pause or un-pause the parser; a nonzero value pauses */ 425 | void http_parser_pause (http_parser* parser, int paused); 426 | 427 | /* Checks if this is the final chunk of the body. */ 428 | int http_body_is_final (const(http_parser)* parser); 429 | 430 | /* Change the maximum header size provided at compile time. */ 431 | void http_parser_set_max_header_size (uint size); 432 | 433 | -------------------------------------------------------------------------------- /bench/llhttp.d: -------------------------------------------------------------------------------- 1 | extern (C): 2 | 3 | enum LLHTTP_VERSION_MAJOR = 6; 4 | enum LLHTTP_VERSION_MINOR = 0; 5 | enum LLHTTP_VERSION_PATCH = 5; 6 | 7 | enum LLHTTP_STRICT_MODE = 0; 8 | 9 | alias llhttp__internal_t = llhttp__internal_s; 10 | 11 | struct llhttp__internal_s 12 | { 13 | int _index; 14 | void* _span_pos0; 15 | void* _span_cb0; 16 | int error; 17 | const(char)* reason; 18 | const(char)* error_pos; 19 | void* data; 20 | void* _current; 21 | ulong content_length; 22 | ubyte type; 23 | ubyte method; 24 | ubyte http_major; 25 | ubyte http_minor; 26 | ubyte header_state; 27 | ubyte lenient_flags; 28 | ubyte upgrade; 29 | ubyte finish; 30 | ushort flags; 31 | ushort status_code; 32 | void* settings; 33 | } 34 | 35 | int llhttp__internal_init (llhttp__internal_t* s); 36 | int llhttp__internal_execute (llhttp__internal_t* s, const(char)* p, const(char)* endp); 37 | 38 | /* extern "C" */ 39 | 40 | /* INCLUDE_LLHTTP_ITSELF_H_ */ 41 | 42 | enum llhttp_errno 43 | { 44 | HPE_OK = 0, 45 | HPE_INTERNAL = 1, 46 | HPE_STRICT = 2, 47 | HPE_LF_EXPECTED = 3, 48 | HPE_UNEXPECTED_CONTENT_LENGTH = 4, 49 | HPE_CLOSED_CONNECTION = 5, 50 | HPE_INVALID_METHOD = 6, 51 | HPE_INVALID_URL = 7, 52 | HPE_INVALID_CONSTANT = 8, 53 | HPE_INVALID_VERSION = 9, 54 | HPE_INVALID_HEADER_TOKEN = 10, 55 | HPE_INVALID_CONTENT_LENGTH = 11, 56 | HPE_INVALID_CHUNK_SIZE = 12, 57 | HPE_INVALID_STATUS = 13, 58 | HPE_INVALID_EOF_STATE = 14, 59 | HPE_INVALID_TRANSFER_ENCODING = 15, 60 | HPE_CB_MESSAGE_BEGIN = 16, 61 | HPE_CB_HEADERS_COMPLETE = 17, 62 | HPE_CB_MESSAGE_COMPLETE = 18, 63 | HPE_CB_CHUNK_HEADER = 19, 64 | HPE_CB_CHUNK_COMPLETE = 20, 65 | HPE_PAUSED = 21, 66 | HPE_PAUSED_UPGRADE = 22, 67 | HPE_PAUSED_H2_UPGRADE = 23, 68 | HPE_USER = 24 69 | } 70 | 71 | alias llhttp_errno_t = llhttp_errno; 72 | 73 | enum llhttp_flags 74 | { 75 | F_CONNECTION_KEEP_ALIVE = 0x1, 76 | F_CONNECTION_CLOSE = 0x2, 77 | F_CONNECTION_UPGRADE = 0x4, 78 | F_CHUNKED = 0x8, 79 | F_UPGRADE = 0x10, 80 | F_CONTENT_LENGTH = 0x20, 81 | F_SKIPBODY = 0x40, 82 | F_TRAILING = 0x80, 83 | F_TRANSFER_ENCODING = 0x200 84 | } 85 | 86 | alias llhttp_flags_t = llhttp_flags; 87 | 88 | enum llhttp_lenient_flags 89 | { 90 | LENIENT_HEADERS = 0x1, 91 | LENIENT_CHUNKED_LENGTH = 0x2, 92 | LENIENT_KEEP_ALIVE = 0x4 93 | } 94 | 95 | alias llhttp_lenient_flags_t = llhttp_lenient_flags; 96 | 97 | enum llhttp_type 98 | { 99 | HTTP_BOTH = 0, 100 | HTTP_REQUEST = 1, 101 | HTTP_RESPONSE = 2 102 | } 103 | 104 | alias llhttp_type_t = llhttp_type; 105 | 106 | enum llhttp_finish_ 107 | { 108 | HTTP_FINISH_SAFE = 0, 109 | HTTP_FINISH_SAFE_WITH_CB = 1, 110 | HTTP_FINISH_UNSAFE = 2 111 | } 112 | 113 | alias llhttp_finish_t = llhttp_finish_; 114 | 115 | enum llhttp_method 116 | { 117 | HTTP_DELETE = 0, 118 | HTTP_GET = 1, 119 | HTTP_HEAD = 2, 120 | HTTP_POST = 3, 121 | HTTP_PUT = 4, 122 | HTTP_CONNECT = 5, 123 | HTTP_OPTIONS = 6, 124 | HTTP_TRACE = 7, 125 | HTTP_COPY = 8, 126 | HTTP_LOCK = 9, 127 | HTTP_MKCOL = 10, 128 | HTTP_MOVE = 11, 129 | HTTP_PROPFIND = 12, 130 | HTTP_PROPPATCH = 13, 131 | HTTP_SEARCH = 14, 132 | HTTP_UNLOCK = 15, 133 | HTTP_BIND = 16, 134 | HTTP_REBIND = 17, 135 | HTTP_UNBIND = 18, 136 | HTTP_ACL = 19, 137 | HTTP_REPORT = 20, 138 | HTTP_MKACTIVITY = 21, 139 | HTTP_CHECKOUT = 22, 140 | HTTP_MERGE = 23, 141 | HTTP_MSEARCH = 24, 142 | HTTP_NOTIFY = 25, 143 | HTTP_SUBSCRIBE = 26, 144 | HTTP_UNSUBSCRIBE = 27, 145 | HTTP_PATCH = 28, 146 | HTTP_PURGE = 29, 147 | HTTP_MKCALENDAR = 30, 148 | HTTP_LINK = 31, 149 | HTTP_UNLINK = 32, 150 | HTTP_SOURCE = 33, 151 | HTTP_PRI = 34, 152 | HTTP_DESCRIBE = 35, 153 | HTTP_ANNOUNCE = 36, 154 | HTTP_SETUP = 37, 155 | HTTP_PLAY = 38, 156 | HTTP_PAUSE = 39, 157 | HTTP_TEARDOWN = 40, 158 | HTTP_GET_PARAMETER = 41, 159 | HTTP_SET_PARAMETER = 42, 160 | HTTP_REDIRECT = 43, 161 | HTTP_RECORD = 44, 162 | HTTP_FLUSH = 45 163 | } 164 | 165 | alias llhttp_method_t = llhttp_method; 166 | 167 | /* extern "C" */ 168 | 169 | /* LLLLHTTP_C_HEADERS_ */ 170 | 171 | alias llhttp_t = llhttp__internal_s; 172 | alias llhttp_settings_t = llhttp_settings_s; 173 | 174 | alias llhttp_data_cb = int function (llhttp_t*, const(char)* at, size_t length); 175 | alias llhttp_cb = int function (llhttp_t*); 176 | 177 | struct llhttp_settings_s 178 | { 179 | /* Possible return values 0, -1, `HPE_PAUSED` */ 180 | llhttp_cb on_message_begin; 181 | 182 | /* Possible return values 0, -1, HPE_USER */ 183 | llhttp_data_cb on_url; 184 | llhttp_data_cb on_status; 185 | llhttp_data_cb on_header_field; 186 | llhttp_data_cb on_header_value; 187 | 188 | /* Possible return values: 189 | * 0 - Proceed normally 190 | * 1 - Assume that request/response has no body, and proceed to parsing the 191 | * next message 192 | * 2 - Assume absence of body (as above) and make `llhttp_execute()` return 193 | * `HPE_PAUSED_UPGRADE` 194 | * -1 - Error 195 | * `HPE_PAUSED` 196 | */ 197 | llhttp_cb on_headers_complete; 198 | 199 | /* Possible return values 0, -1, HPE_USER */ 200 | llhttp_data_cb on_body; 201 | 202 | /* Possible return values 0, -1, `HPE_PAUSED` */ 203 | llhttp_cb on_message_complete; 204 | 205 | /* When on_chunk_header is called, the current chunk length is stored 206 | * in parser->content_length. 207 | * Possible return values 0, -1, `HPE_PAUSED` 208 | */ 209 | llhttp_cb on_chunk_header; 210 | llhttp_cb on_chunk_complete; 211 | 212 | /* Information-only callbacks, return value is ignored */ 213 | llhttp_cb on_url_complete; 214 | llhttp_cb on_status_complete; 215 | llhttp_cb on_header_field_complete; 216 | llhttp_cb on_header_value_complete; 217 | } 218 | 219 | /* Initialize the parser with specific type and user settings. 220 | * 221 | * NOTE: lifetime of `settings` has to be at least the same as the lifetime of 222 | * the `parser` here. In practice, `settings` has to be either a static 223 | * variable or be allocated with `malloc`, `new`, etc. 224 | */ 225 | void llhttp_init ( 226 | llhttp_t* parser, 227 | llhttp_type_t type, 228 | const(llhttp_settings_t)* settings); 229 | 230 | // defined(__wasm__) 231 | 232 | /* Reset an already initialized parser back to the start state, preserving the 233 | * existing parser type, callback settings, user data, and lenient flags. 234 | */ 235 | void llhttp_reset (llhttp_t* parser); 236 | 237 | /* Initialize the settings object */ 238 | void llhttp_settings_init (llhttp_settings_t* settings); 239 | 240 | /* Parse full or partial request/response, invoking user callbacks along the 241 | * way. 242 | * 243 | * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing 244 | * interrupts, and such errno is returned from `llhttp_execute()`. If 245 | * `HPE_PAUSED` was used as a errno, the execution can be resumed with 246 | * `llhttp_resume()` call. 247 | * 248 | * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` 249 | * is returned after fully parsing the request/response. If the user wishes to 250 | * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. 251 | * 252 | * NOTE: if this function ever returns a non-pause type error, it will continue 253 | * to return the same error upon each successive call up until `llhttp_init()` 254 | * is called. 255 | */ 256 | llhttp_errno_t llhttp_execute (llhttp_t* parser, const(char)* data, size_t len); 257 | 258 | /* This method should be called when the other side has no further bytes to 259 | * send (e.g. shutdown of readable side of the TCP connection.) 260 | * 261 | * Requests without `Content-Length` and other messages might require treating 262 | * all incoming bytes as the part of the body, up to the last byte of the 263 | * connection. This method will invoke `on_message_complete()` callback if the 264 | * request was terminated safely. Otherwise a error code would be returned. 265 | */ 266 | llhttp_errno_t llhttp_finish (llhttp_t* parser); 267 | 268 | /* Returns `1` if the incoming message is parsed until the last byte, and has 269 | * to be completed by calling `llhttp_finish()` on EOF 270 | */ 271 | int llhttp_message_needs_eof (const(llhttp_t)* parser); 272 | 273 | /* Returns `1` if there might be any other messages following the last that was 274 | * successfully parsed. 275 | */ 276 | int llhttp_should_keep_alive (const(llhttp_t)* parser); 277 | 278 | /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set 279 | * appropriate error reason. 280 | * 281 | * Important: do not call this from user callbacks! User callbacks must return 282 | * `HPE_PAUSED` if pausing is required. 283 | */ 284 | void llhttp_pause (llhttp_t* parser); 285 | 286 | /* Might be called to resume the execution after the pause in user's callback. 287 | * See `llhttp_execute()` above for details. 288 | * 289 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. 290 | */ 291 | void llhttp_resume (llhttp_t* parser); 292 | 293 | /* Might be called to resume the execution after the pause in user's callback. 294 | * See `llhttp_execute()` above for details. 295 | * 296 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` 297 | */ 298 | void llhttp_resume_after_upgrade (llhttp_t* parser); 299 | 300 | /* Returns the latest return error */ 301 | llhttp_errno_t llhttp_get_errno (const(llhttp_t)* parser); 302 | 303 | /* Returns the verbal explanation of the latest returned error. 304 | * 305 | * Note: User callback should set error reason when returning the error. See 306 | * `llhttp_set_error_reason()` for details. 307 | */ 308 | const(char)* llhttp_get_error_reason (const(llhttp_t)* parser); 309 | 310 | /* Assign verbal description to the returned error. Must be called in user 311 | * callbacks right before returning the errno. 312 | * 313 | * Note: `HPE_USER` error code might be useful in user callbacks. 314 | */ 315 | void llhttp_set_error_reason (llhttp_t* parser, const(char)* reason); 316 | 317 | /* Returns the pointer to the last parsed byte before the returned error. The 318 | * pointer is relative to the `data` argument of `llhttp_execute()`. 319 | * 320 | * Note: this method might be useful for counting the number of parsed bytes. 321 | */ 322 | const(char)* llhttp_get_error_pos (const(llhttp_t)* parser); 323 | 324 | /* Returns textual name of error code */ 325 | const(char)* llhttp_errno_name (llhttp_errno_t err); 326 | 327 | /* Returns textual name of HTTP method */ 328 | const(char)* llhttp_method_name (llhttp_method_t method); 329 | 330 | /* Enables/disables lenient header value parsing (disabled by default). 331 | * 332 | * Lenient parsing disables header value token checks, extending llhttp's 333 | * protocol support to highly non-compliant clients/server. No 334 | * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when 335 | * lenient parsing is "on". 336 | * 337 | * **(USE AT YOUR OWN RISK)** 338 | */ 339 | void llhttp_set_lenient_headers (llhttp_t* parser, int enabled); 340 | 341 | /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and 342 | * `Content-Length` headers (disabled by default). 343 | * 344 | * Normally `llhttp` would error when `Transfer-Encoding` is present in 345 | * conjunction with `Content-Length`. This error is important to prevent HTTP 346 | * request smuggling, but may be less desirable for small number of cases 347 | * involving legacy servers. 348 | * 349 | * **(USE AT YOUR OWN RISK)** 350 | */ 351 | void llhttp_set_lenient_chunked_length (llhttp_t* parser, int enabled); 352 | 353 | /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 354 | * requests responses. 355 | * 356 | * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) 357 | * the HTTP request/response after the request/response with `Connection: close` 358 | * and `Content-Length`. This is important to prevent cache poisoning attacks, 359 | * but might interact badly with outdated and insecure clients. With this flag 360 | * the extra request/response will be parsed normally. 361 | * 362 | * **(USE AT YOUR OWN RISK)** 363 | */ 364 | void llhttp_set_lenient_keep_alive (llhttp_t* parser, int enabled); 365 | 366 | /* extern "C" */ 367 | 368 | /* INCLUDE_LLHTTP_API_H_ */ 369 | 370 | /* INCLUDE_LLHTTP_H_ */ 371 | -------------------------------------------------------------------------------- /bench/picohttpparser.d: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, 3 | * Shigeo Mitsunari 4 | * 5 | * The software is licensed under either the MIT License (below) or the Perl 6 | * license. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | import core.sys.posix.sys.types; 28 | 29 | extern (C): 30 | 31 | /* contains name and value of a header (name == NULL if is a continuing line 32 | * of a multiline header */ 33 | struct phr_header 34 | { 35 | const(char)* name; 36 | size_t name_len; 37 | const(char)* value; 38 | size_t value_len; 39 | } 40 | 41 | /* returns number of bytes consumed if successful, -2 if request is partial, 42 | * -1 if failed */ 43 | int phr_parse_request ( 44 | const(char)* buf, 45 | size_t len, 46 | const(char*)* method, 47 | size_t* method_len, 48 | const(char*)* path, 49 | size_t* path_len, 50 | int* minor_version, 51 | phr_header* headers, 52 | size_t* num_headers, 53 | size_t last_len); 54 | 55 | /* ditto */ 56 | int phr_parse_response ( 57 | const(char)* _buf, 58 | size_t len, 59 | int* minor_version, 60 | int* status, 61 | const(char*)* msg, 62 | size_t* msg_len, 63 | phr_header* headers, 64 | size_t* num_headers, 65 | size_t last_len); 66 | 67 | /* ditto */ 68 | int phr_parse_headers (const(char)* buf, size_t len, phr_header* headers, size_t* num_headers, size_t last_len); 69 | 70 | /* should be zero-filled before start */ 71 | struct phr_chunked_decoder 72 | { 73 | size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ 74 | char consume_trailer; /* if trailing headers should be consumed */ 75 | char _hex_count; 76 | char _state; 77 | } 78 | 79 | /* the function rewrites the buffer given as (buf, bufsz) removing the chunked- 80 | * encoding headers. When the function returns without an error, bufsz is 81 | * updated to the length of the decoded data available. Applications should 82 | * repeatedly call the function while it returns -2 (incomplete) every time 83 | * supplying newly arrived data. If the end of the chunked-encoded data is 84 | * found, the function returns a non-negative number indicating the number of 85 | * octets left undecoded at the tail of the supplied buffer. Returns -1 on 86 | * error. 87 | */ 88 | ssize_t phr_decode_chunked (phr_chunked_decoder* decoder, char* buf, size_t* bufsz); 89 | 90 | /* returns if the chunked decoder is in middle of chunked data */ 91 | int phr_decode_chunked_is_in_data (phr_chunked_decoder* decoder); 92 | 93 | -------------------------------------------------------------------------------- /bench/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchaloupka/httparsed/1bc45b05a27083d67ab8c4c9e11a43500f1fcba7/bench/results.png -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "httparsed" 2 | description "Fast betterC/nogc HTTP request/response parser" 3 | authors "Tomáš Chaloupka" 4 | targetType "library" 5 | license "public domain" 6 | 7 | configuration "default" { 8 | } 9 | 10 | configuration "betterC" { 11 | buildOptions "betterC" 12 | } 13 | 14 | configuration "unittest" { 15 | dflags "-mcpu=native" platform="ldc" 16 | } 17 | 18 | configuration "httparsed-test-betterC" { 19 | targetType "executable" 20 | targetName "httparsed-test-betterC" 21 | buildOptions "betterC" 22 | } 23 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('httparsed', 'd', 2 | version: '1.3.0' 3 | ) 4 | 5 | httparsed_build_versions = [] 6 | 7 | httparsed_src = ['source/httparsed.d'] 8 | 9 | httparsed_lib = static_library( 10 | 'httparsed', 11 | httparsed_src, 12 | include_directories: include_directories('source'), 13 | d_module_versions: httparsed_build_versions 14 | ) 15 | 16 | httparsed_dep = declare_dependency( 17 | include_directories: include_directories('source'), 18 | link_with: httparsed_lib 19 | ) 20 | 21 | bc_link_args = [] 22 | if meson.get_compiler('d').get_id() == 'llvm' 23 | bc_link_args += '-link-defaultlib-shared=false' 24 | endif 25 | 26 | test_exe = executable( 27 | 'httparsed-test', 28 | httparsed_src, 29 | include_directories: include_directories('source'), 30 | d_args: ['-betterC', '-unittest'], 31 | d_module_versions: ['CI_MAIN'], 32 | link_args: bc_link_args 33 | ) 34 | test('bctest', test_exe) 35 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRC_FILES="-Isource/ source/*.d" 4 | 5 | set -v -e -o pipefail 6 | 7 | if [ -z $DC ]; then DMD="dmd"; fi 8 | 9 | if [ $DC = "ldc2" ]; then DMD="ldmd2"; fi 10 | 11 | rm -f *test* 12 | 13 | if [ "$COVERAGE" = true ]; then 14 | $DMD -version=CI_MAIN -cov -debug -g -unittest -w -vcolumns -of=httparsed-unittest-cov $SRC_FILES 15 | ./httparsed-unittest-cov 16 | wget https://codecov.io/bash -O codecov.sh 17 | bash codecov.sh 18 | else 19 | # unittests no SSE 20 | $DMD -version=CI_MAIN -debug -g -unittest -w -vcolumns -of=httparsed-unittest $SRC_FILES 21 | ./httparsed-unittest 22 | 23 | # unittests with possible SSE 24 | $DMD -version=CI_MAIN -debug -g -unittest -w -vcolumns -mcpu=native -of=httparsed-sse-unittest $SRC_FILES 25 | ./httparsed-sse-unittest 26 | 27 | # betterC unitests no SSE 28 | $DMD -version=CI_MAIN -debug -g -unittest -w -vcolumns -betterC -of=httparsed-bc-unittest $SRC_FILES 29 | ./httparsed-bc-unittest 30 | 31 | # betterC unitests with possible SSE 32 | $DMD -version=CI_MAIN -debug -g -unittest -w -vcolumns -mcpu=native -betterC -of=httparsed-bc-sse-unittest $SRC_FILES 33 | ./httparsed-bc-sse-unittest 34 | 35 | # release build test 36 | $DMD -version=CI_MAIN -release -O -w -mcpu=native -inline -of=httparsed-release-test $SRC_FILES 37 | ./httparsed-release-test 38 | fi 39 | -------------------------------------------------------------------------------- /source/httparsed.d: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | module httparsed; 5 | 6 | nothrow @safe @nogc: 7 | 8 | /// Parser error codes 9 | enum ParserError : int 10 | { 11 | partial = 1, /// not enough data to parse message 12 | newLine, /// invalid character in new line 13 | headerName, /// invalid character in header name 14 | headerValue, /// invalid header value 15 | status, /// invalid character in response status 16 | token, /// invalid character in token 17 | noHeaderName, /// empty header name 18 | noMethod, /// no method in request line 19 | noVersion, /// no version in request line / response status line 20 | noUri, /// no URI in request line 21 | noStatus, /// no status code or text in status line 22 | invalidMethod, /// invalid method in request line 23 | invalidVersion, /// invalid version for the protocol message 24 | } 25 | 26 | /// Helper function to initialize message parser 27 | auto initParser(MSG, Args...)(Args args) { return MsgParser!MSG(args); } 28 | 29 | /** 30 | * HTTP/RTSP message parser. 31 | */ 32 | struct MsgParser(MSG) 33 | { 34 | import std.traits : ForeachType, isArray, Unqual; 35 | 36 | this(Args...)(Args args) 37 | { 38 | this.msg = MSG(args); 39 | } 40 | 41 | /** 42 | * Parses message request (request line + headers). 43 | * 44 | * Params: 45 | * - buffer = buffer to parse message from 46 | * - lastPos = optional argument to store / pass previous position to which message was 47 | * already parsed (speeds up parsing when message comes in parts) 48 | * 49 | * Returns: 50 | * * parsed message header length when parsed sucessfully 51 | * * `-ParserError` on error (ie. -1 when message header is not complete yet) 52 | */ 53 | int parseRequest(T)(T buffer, ref uint lastPos) 54 | if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) 55 | { 56 | static if (is(Unqual!(ForeachType!T) == char)) return parse!parseRequestLine(cast(const(ubyte)[])buffer, lastPos); 57 | else return parse!parseRequestLine(buffer, lastPos); 58 | } 59 | 60 | /// ditto 61 | int parseRequest(T)(T buffer) 62 | if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) 63 | { 64 | uint lastPos; 65 | static if (is(Unqual!(ForeachType!T) == char)) return parse!parseRequestLine(cast(const(ubyte)[])buffer, lastPos); 66 | else return parse!parseRequestLine(buffer, lastPos); 67 | } 68 | 69 | /** 70 | * Parses message response (status line + headers). 71 | * 72 | * Params: 73 | * - buffer = buffer to parse message from 74 | * - lastPos = optional argument to store / pass previous position to which message was 75 | * already parsed (speeds up parsing when message comes in parts) 76 | * 77 | * Returns: 78 | * * parsed message header length when parsed sucessfully 79 | * * `-ParserError.partial` on error (ie. -1 when message header is not comlete yet) 80 | */ 81 | int parseResponse(T)(T buffer, ref uint lastPos) 82 | if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) 83 | { 84 | static if (is(Unqual!(ForeachType!T) == char)) return parse!parseStatusLine(cast(const(ubyte)[])buffer, lastPos); 85 | else return parse!parseStatusLine(buffer, lastPos); 86 | } 87 | 88 | /// ditto 89 | int parseResponse(T)(T buffer) 90 | if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) 91 | { 92 | uint lastPos; 93 | static if (is(Unqual!(ForeachType!T) == char)) return parse!parseStatusLine(cast(const(ubyte)[])buffer, lastPos); 94 | else return parse!parseStatusLine(buffer, lastPos); 95 | } 96 | 97 | /// Gets provided structure used during parsing 98 | ref MSG msg() return { return m_msg; } 99 | 100 | /// ditto 101 | ref const(MSG) msg() const return { return cast(const(MSG))m_msg; } 102 | 103 | alias msg this; 104 | 105 | private: 106 | 107 | // character map of valid characters for token, forbidden: 108 | // 0-SP, DEL, HT 109 | // ()<>@,;:\"/[]?={} 110 | enum tokenRanges = "\0 \"\"(),,//:@[]{}\x7f\xff"; 111 | enum tokenSSERanges = "\0 \"\"(),,//:@[]{\xff"; // merge of last range due to the SSE register size limit 112 | 113 | enum versionRanges = "\0-:@[`{\xff"; // allow only [A-Za-z./] characters 114 | 115 | MSG m_msg; 116 | 117 | int parse(alias pred)(const(ubyte)[] buffer, ref uint lastPos) 118 | { 119 | assert(buffer.length >= lastPos); 120 | immutable l = buffer.length; 121 | 122 | if (_expect(!lastPos, true)) 123 | { 124 | if (_expect(!buffer.length, false)) return err(ParserError.partial); 125 | 126 | // skip first empty line (some clients add CRLF after POST content) 127 | if (_expect(buffer[0] == '\r', false)) 128 | { 129 | if (_expect(buffer.length == 1, false)) return err(ParserError.partial); 130 | if (_expect(buffer[1] != '\n', false)) return err(ParserError.newLine); 131 | lastPos += 2; 132 | buffer = buffer[lastPos..$]; 133 | } 134 | else if (_expect(buffer[0] == '\n', false)) 135 | buffer = buffer[++lastPos..$]; 136 | 137 | immutable res = pred(buffer); 138 | if (_expect(res < 0, false)) return res; 139 | 140 | lastPos = cast(int)(l - buffer.length); // store index of last parsed line 141 | } 142 | else buffer = buffer[lastPos..$]; // skip already parsed lines 143 | 144 | immutable hdrRes = parseHeaders(buffer); 145 | lastPos = cast(int)(l - buffer.length); // store index of last parsed line 146 | 147 | if (_expect(hdrRes < 0, false)) return hdrRes; 148 | return lastPos; // finished 149 | } 150 | 151 | int parseHeaders(ref const(ubyte)[] buffer) 152 | { 153 | bool hasHeader; 154 | size_t start, i; 155 | const(ubyte)[] name, value; 156 | while (true) 157 | { 158 | // check for msg headers end 159 | if (_expect(buffer.length == 0, false)) return err(ParserError.partial); 160 | if (buffer[0] == '\r') 161 | { 162 | if (_expect(buffer.length == 1, false)) return err(ParserError.partial); 163 | if (_expect(buffer[1] != '\n', false)) return err(ParserError.newLine); 164 | 165 | buffer = buffer[2..$]; 166 | return 0; 167 | } 168 | if (_expect(buffer[0] == '\n', false)) 169 | { 170 | buffer = buffer[1..$]; 171 | return 0; 172 | } 173 | 174 | if (!hasHeader || (buffer[i] != ' ' && buffer[i] != '\t')) 175 | { 176 | auto ret = parseToken!(tokenRanges, ':', tokenSSERanges)(buffer, i); 177 | if (_expect(ret < 0, false)) return ret; 178 | if (_expect(start == i, false)) return err(ParserError.noHeaderName); 179 | name = buffer[start..i]; // store header name 180 | i++; // move index after colon 181 | 182 | // skip over SP and HT 183 | for (;; ++i) 184 | { 185 | if (_expect(i == buffer.length, false)) return err(ParserError.partial); 186 | if (buffer[i] != ' ' && buffer[i] != '\t') break; 187 | } 188 | start = i; 189 | } 190 | else name = null; // multiline header 191 | 192 | // parse value 193 | auto ret = parseToken!("\0\010\012\037\177\177", "\r\n")(buffer, i); 194 | if (_expect(ret < 0, false)) return ret; 195 | value = buffer[start..i]; 196 | mixin(advanceNewline); 197 | hasHeader = true; // flag to define that we can now accept multiline header values 198 | static if (__traits(hasMember, m_msg, "onHeader")) 199 | { 200 | // remove trailing SPs and HTABs 201 | if (_expect(value.length && (value[$-1] == ' ' || value[$-1] == '\t'), false)) 202 | { 203 | int j = cast(int)value.length - 2; 204 | for (; j >= 0; --j) 205 | if (!(value[j] == ' ' || value[j] == '\t')) 206 | break; 207 | value = value[0..j+1]; 208 | } 209 | 210 | static if (is(typeof(m_msg.onHeader("", "")) == void)) 211 | m_msg.onHeader(cast(const(char)[])name, cast(const(char)[])value); 212 | else { 213 | auto r = m_msg.onHeader(cast(const(char)[])name, cast(const(char)[])value); 214 | if (_expect(r < 0, false)) return r; 215 | } 216 | } 217 | 218 | // header line completed -> advance buffer 219 | buffer = buffer[i..$]; 220 | start = i = 0; 221 | } 222 | assert(0); 223 | } 224 | 225 | auto parseRequestLine(ref const(ubyte)[] buffer) 226 | { 227 | size_t start, i; 228 | 229 | // METHOD 230 | auto ret = parseToken!(tokenRanges, ' ', tokenSSERanges)(buffer, i); 231 | if (_expect(ret < 0, false)) return ret; 232 | if (_expect(start == i, false)) return err(ParserError.noMethod); 233 | 234 | static if (__traits(hasMember, m_msg, "onMethod")) 235 | { 236 | static if (is(typeof(m_msg.onMethod("")) == void)) 237 | m_msg.onMethod(cast(const(char)[])buffer[start..i]); 238 | else { 239 | auto r = m_msg.onMethod(cast(const(char)[])buffer[start..i]); 240 | if (_expect(r < 0, false)) return r; 241 | } 242 | } 243 | mixin(skipSpaces!(ParserError.noUri)); 244 | start = i; 245 | 246 | // PATH 247 | ret = parseToken!("\000\040\177\177", ' ')(buffer, i); 248 | if (_expect(ret < 0, false)) return ret; 249 | static if (__traits(hasMember, m_msg, "onUri")) 250 | { 251 | static if (is(typeof(m_msg.onUri("")) == void)) 252 | m_msg.onUri(cast(const(char)[])buffer[start..i]); 253 | else { 254 | auto ur = m_msg.onUri(cast(const(char)[])buffer[start..i]); 255 | if (_expect(ur < 0, false)) return ur; 256 | } 257 | } 258 | mixin(skipSpaces!(ParserError.noVersion)); 259 | start = i; 260 | 261 | // VERSION 262 | ret = parseToken!(versionRanges, "\r\n")(buffer, i); 263 | if (_expect(ret < 0, false)) return ret; 264 | static if (__traits(hasMember, m_msg, "onVersion")) 265 | { 266 | static if (is(typeof(m_msg.onVersion("")) == void)) 267 | m_msg.onVersion(cast(const(char)[])buffer[start..i]); 268 | else { 269 | auto vr = m_msg.onVersion(cast(const(char)[])buffer[start..i]); 270 | if (_expect(vr < 0, false)) return vr; 271 | } 272 | } 273 | mixin(advanceNewline); 274 | 275 | // advance buffer after the request line 276 | buffer = buffer[i..$]; 277 | return 0; 278 | } 279 | 280 | auto parseStatusLine(ref const(ubyte)[] buffer) 281 | { 282 | size_t start, i; 283 | 284 | // VERSION 285 | auto ret = parseToken!(versionRanges, ' ')(buffer, i); 286 | if (_expect(ret < 0, false)) return ret; 287 | if (_expect(start == i, false)) return err(ParserError.noVersion); 288 | static if (__traits(hasMember, m_msg, "onVersion")) 289 | { 290 | static if (is(typeof(m_msg.onVersion("")) == void)) 291 | m_msg.onVersion(cast(const(char)[])buffer[start..i]); 292 | else { 293 | auto r = m_msg.onVersion(cast(const(char)[])buffer[start..i]); 294 | if (_expect(r < 0, false)) return r; 295 | } 296 | } 297 | mixin(skipSpaces!(ParserError.noStatus)); 298 | start = i; 299 | 300 | // STATUS CODE 301 | if (_expect(i+3 >= buffer.length, false)) 302 | return err(ParserError.partial); // not enough data - we want at least [:digit:][:digit:][:digit:] to try to parse 303 | 304 | int code; 305 | foreach (j, m; [100, 10, 1]) 306 | { 307 | if (buffer[i+j] < '0' || buffer[i+j] > '9') return err(ParserError.status); 308 | code += (buffer[start+j] - '0') * m; 309 | } 310 | i += 3; 311 | static if (__traits(hasMember, m_msg, "onStatus")) 312 | { 313 | static if (is(typeof(m_msg.onStatus(code)) == void)) 314 | m_msg.onStatus(code); 315 | else { 316 | auto sr = m_msg.onStatus(code); 317 | if (_expect(sr < 0, false)) return sr; 318 | } 319 | } 320 | if (_expect(i == buffer.length, false)) 321 | return err(ParserError.partial); 322 | if (_expect(buffer[i] != ' ' && buffer[i] != '\r' && buffer[i] != '\n', false)) 323 | return err(ParserError.status); // Garbage after status 324 | 325 | start = i; 326 | 327 | // MESSAGE 328 | ret = parseToken!("\0\010\012\037\177\177", "\r\n")(buffer, i); 329 | if (_expect(ret < 0, false)) return ret; 330 | static if (__traits(hasMember, m_msg, "onStatusMsg")) 331 | { 332 | // remove preceding space (we did't advance over spaces because possibly missing status message) 333 | if (i > start) 334 | { 335 | while (buffer[start] == ' ' && start < i) start++; 336 | if (i > start) 337 | { 338 | static if (is(typeof(m_msg.onStatusMsg("")) == void)) 339 | m_msg.onStatusMsg(cast(const(char)[])buffer[start..i]); 340 | else { 341 | auto smr = m_msg.onStatusMsg(cast(const(char)[])buffer[start..i]); 342 | if (_expect(smr < 0, false)) return smr; 343 | } 344 | } 345 | } 346 | } 347 | mixin(advanceNewline); 348 | 349 | // advance buffer after the status line 350 | buffer = buffer[i..$]; 351 | return 0; 352 | } 353 | 354 | /* 355 | * Advances buffer over the token to the next character while checking for valid characters. 356 | * On success, buffer index is left on the next character. 357 | * 358 | * Params: 359 | * - ranges = ranges of characters to stop on 360 | * - sseRanges = if null, same ranges is used, but they are limited to 8 ranges 361 | * - next = next character/s to stop on (must be present in the provided ranges too) 362 | * Returns: 0 on success error code otherwise 363 | */ 364 | int parseToken(string ranges, alias next, string sseRanges = null)(const(ubyte)[] buffer, ref size_t i) pure 365 | { 366 | version (DigitalMars) { 367 | static if (__VERSION__ >= 2094) pragma(inline, true); // older compilers can't inline this 368 | } else pragma(inline, true); 369 | 370 | immutable charMap = parseTokenCharMap!(ranges)(); 371 | 372 | static if (LDC_with_SSE42) 373 | { 374 | // CT function to prepare input for SIMD vector enum 375 | static byte[16] padRanges()(string ranges) 376 | { 377 | byte[16] res; 378 | // res[0..ranges.length] = cast(byte[])ranges[]; - broken on macOS betterC tests 379 | foreach (i, c; ranges) res[i] = cast(byte)c; 380 | return res; 381 | } 382 | 383 | static if (sseRanges) alias usedRng = sseRanges; 384 | else alias usedRng = ranges; 385 | static assert(usedRng.length <= 16, "Ranges must be at most 16 characters long"); 386 | static assert(usedRng.length % 2 == 0, "Ranges must have even number of characters"); 387 | enum rangesSize = usedRng.length; 388 | enum byte16 rngE = padRanges(usedRng); 389 | 390 | if (_expect(buffer.length - i >= 16, true)) 391 | { 392 | size_t left = (buffer.length - i) & ~15; // round down to multiple of 16 393 | byte16 ranges16 = rngE; 394 | 395 | do 396 | { 397 | byte16 b16 = () @trusted { return cast(byte16)_mm_loadu_si128(cast(__m128i*)&buffer[i]); }(); 398 | immutable r = _mm_cmpestri( 399 | ranges16, rangesSize, 400 | b16, 16, 401 | _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS 402 | ); 403 | 404 | if (r != 16) 405 | { 406 | i += r; 407 | goto FOUND; 408 | } 409 | i += 16; 410 | left -= 16; 411 | } 412 | while (_expect(left != 0, true)); 413 | } 414 | } 415 | else 416 | { 417 | // faster unrolled loop to iterate over 8 characters 418 | loop: while (_expect(buffer.length - i >= 8, true)) 419 | { 420 | static foreach (_; 0..8) 421 | { 422 | if (_expect(!charMap[buffer[i]], false)) goto FOUND; 423 | ++i; 424 | } 425 | } 426 | } 427 | 428 | // handle the rest 429 | if (_expect(i >= buffer.length, false)) return err(ParserError.partial); 430 | 431 | FOUND: 432 | while (true) 433 | { 434 | static if (is(typeof(next) == char)) { 435 | static assert(!charMap[next], "Next character is not in ranges"); 436 | if (buffer[i] == next) return 0; 437 | } else { 438 | static assert(next.length > 0, "Next character not provided"); 439 | static foreach (c; next) { 440 | static assert(!charMap[c], "Next character is not in ranges"); 441 | if (buffer[i] == c) return 0; 442 | } 443 | } 444 | if (_expect(!charMap[buffer[i]], false)) return err(ParserError.token); 445 | if (_expect(++i == buffer.length, false)) return err(ParserError.partial); 446 | } 447 | } 448 | 449 | // advances over new line 450 | enum advanceNewline = q{ 451 | assert(i < buffer.length); 452 | if (_expect(buffer[i] == '\r', true)) 453 | { 454 | if (_expect(i+1 == buffer.length, false)) return err(ParserError.partial); 455 | if (_expect(buffer[i+1] != '\n', false)) return err(ParserError.newLine); 456 | i += 2; 457 | } 458 | else if (buffer[i] == '\n') ++i; 459 | else assert(0); 460 | }; 461 | 462 | // skips over spaces in the buffer 463 | template skipSpaces(ParserError err) 464 | { 465 | enum skipSpaces = ` 466 | do { 467 | ++i; 468 | if (_expect(buffer.length == i, false)) return err(ParserError.partial); 469 | if (_expect(buffer[i] == '\r' || buffer[i] == '\n', false)) return err(` ~ err.stringof ~ `); 470 | } while (buffer[i] == ' '); 471 | `; 472 | } 473 | } 474 | 475 | /// 476 | @("example") 477 | unittest 478 | { 479 | // init parser 480 | auto reqParser = initParser!Msg(); // or `MsgParser!MSG reqParser;` 481 | auto resParser = initParser!Msg(); // or `MsgParser!MSG resParser;` 482 | 483 | // parse request 484 | string data = "GET /foo HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n"; 485 | // returns parsed message header length when parsed sucessfully, -ParserError on error 486 | int res = reqParser.parseRequest(data); 487 | assert(res == data.length); 488 | assert(reqParser.method == "GET"); 489 | assert(reqParser.uri == "/foo"); 490 | assert(reqParser.minorVer == 1); // HTTP/1.1 491 | assert(reqParser.headers.length == 1); 492 | assert(reqParser.headers[0].name == "Host"); 493 | assert(reqParser.headers[0].value == "127.0.0.1:8090"); 494 | 495 | // parse response 496 | data = "HTTP/1.0 200 OK\r\n"; 497 | uint lastPos; // store last parsed position for next run 498 | res = resParser.parseResponse(data, lastPos); 499 | assert(res == -ParserError.partial); // no complete message header yet 500 | data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nfoo"; 501 | res = resParser.parseResponse(data, lastPos); // starts parsing from previous position 502 | assert(res == data.length - 3); // whole message header parsed, body left to be handled based on actual header values 503 | assert(resParser.minorVer == 0); // HTTP/1.0 504 | assert(resParser.status == 200); 505 | assert(resParser.statusMsg == "OK"); 506 | assert(resParser.headers.length == 2); 507 | assert(resParser.headers[0].name == "Content-Type"); 508 | assert(resParser.headers[0].value == "text/plain"); 509 | assert(resParser.headers[1].name == "Content-Length"); 510 | assert(resParser.headers[1].value == "3"); 511 | } 512 | 513 | /** 514 | * Parses HTTP version from a slice returned in `onVersion` callback. 515 | * 516 | * Returns: minor version (0 for HTTP/1.0 or 1 for HTTP/1.1) on success or 517 | * `-ParserError.invalidVersion` on error 518 | */ 519 | int parseHttpVersion(const(char)[] ver) pure 520 | { 521 | if (_expect(ver.length != 8, false)) return err(ParserError.invalidVersion); 522 | 523 | static foreach (i, c; "HTTP/1.") 524 | if (_expect(ver[i] != c, false)) return err(ParserError.invalidVersion); 525 | 526 | if (_expect(ver[7] < '0' || ver[7] > '9', false)) return err(ParserError.invalidVersion); 527 | return ver[7] - '0'; 528 | } 529 | 530 | @("parseHttpVersion") 531 | unittest 532 | { 533 | assert(parseHttpVersion("FOO") < 0); 534 | assert(parseHttpVersion("HTTP/1.") < 0); 535 | assert(parseHttpVersion("HTTP/1.12") < 0); 536 | assert(parseHttpVersion("HTTP/1.a") < 0); 537 | assert(parseHttpVersion("HTTP/2.0") < 0); 538 | assert(parseHttpVersion("HTTP/1.00") < 0); 539 | assert(parseHttpVersion("HTTP/1.0") == 0); 540 | assert(parseHttpVersion("HTTP/1.1") == 1); 541 | } 542 | 543 | version (CI_MAIN) 544 | { 545 | // workaround for dub not supporting unittests with betterC 546 | version (D_BetterC) 547 | { 548 | extern(C) void main() @trusted { 549 | import core.stdc.stdio; 550 | static foreach(u; __traits(getUnitTests, httparsed)) 551 | { 552 | static if (__traits(getAttributes, u).length) 553 | printf("unittest %s:%d | '" ~ __traits(getAttributes, u)[0] ~ "'\n", __traits(getLocation, u)[0].ptr, __traits(getLocation, u)[1]); 554 | else 555 | printf("unittest %s:%d\n", __traits(getLocation, u)[0].ptr, __traits(getLocation, u)[1]); 556 | u(); 557 | } 558 | debug printf("All unit tests have been run successfully.\n"); 559 | } 560 | } 561 | else 562 | { 563 | void main() 564 | { 565 | version (unittest) {} // run automagically 566 | else 567 | { 568 | import core.stdc.stdio; 569 | 570 | // just a compilation test 571 | auto reqParser = initParser!Msg(); 572 | auto resParser = initParser!Msg(); 573 | 574 | string data = "GET /foo HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n"; 575 | int res = reqParser.parseRequest(data); 576 | assert(res == data.length); 577 | 578 | data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nfoo"; 579 | res = resParser.parseResponse(data); 580 | assert(res == data.length - 3); 581 | () @trusted { printf("Test app works\n"); }(); 582 | } 583 | } 584 | } 585 | } 586 | 587 | private: 588 | 589 | int err(ParserError e) pure { pragma(inline, true); return -(cast(int)e); } 590 | 591 | /// Builds valid char map from the provided ranges of invalid ones 592 | bool[256] buildValidCharMap()(string invalidRanges) 593 | { 594 | assert(invalidRanges.length % 2 == 0, "Uneven ranges"); 595 | bool[256] res = true; 596 | 597 | for (int i=0; i < invalidRanges.length; i+=2) 598 | for (int j=invalidRanges[i]; j <= invalidRanges[i+1]; ++j) 599 | res[j] = false; 600 | return res; 601 | } 602 | 603 | @("buildValidCharMap") 604 | unittest 605 | { 606 | string ranges = "\0 \"\"(),,//:@[]{{}}\x7f\xff"; 607 | assert(buildValidCharMap(ranges) == 608 | cast(bool[])[ 609 | 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, 610 | 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, 611 | 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, 612 | 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, 613 | 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, 614 | 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, 615 | 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, 616 | 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, 617 | ]); 618 | } 619 | 620 | immutable(bool[256]) parseTokenCharMap(string invalidRanges)() { 621 | static immutable charMap = buildValidCharMap(invalidRanges); 622 | return charMap; 623 | } 624 | 625 | version (unittest) version = WITH_MSG; 626 | else version (CI_MAIN) version = WITH_MSG; 627 | 628 | version (WITH_MSG) 629 | { 630 | // define our message content handler 631 | struct Header 632 | { 633 | const(char)[] name; 634 | const(char)[] value; 635 | } 636 | 637 | // Just store slices of parsed message header 638 | struct Msg 639 | { 640 | @safe pure nothrow @nogc: 641 | void onMethod(const(char)[] method) { this.method = method; } 642 | void onUri(const(char)[] uri) { this.uri = uri; } 643 | int onVersion(const(char)[] ver) 644 | { 645 | minorVer = parseHttpVersion(ver); 646 | return minorVer >= 0 ? 0 : minorVer; 647 | } 648 | void onHeader(const(char)[] name, const(char)[] value) { 649 | this.m_headers[m_headersLength].name = name; 650 | this.m_headers[m_headersLength++].value = value; 651 | } 652 | void onStatus(int status) { this.status = status; } 653 | void onStatusMsg(const(char)[] statusMsg) { this.statusMsg = statusMsg; } 654 | 655 | const(char)[] method; 656 | const(char)[] uri; 657 | int minorVer; 658 | int status; 659 | const(char)[] statusMsg; 660 | 661 | private { 662 | Header[32] m_headers; 663 | size_t m_headersLength; 664 | } 665 | 666 | Header[] headers() return { return m_headers[0..m_headersLength]; } 667 | } 668 | 669 | enum Test { err, complete, partial } 670 | } 671 | 672 | // Tests from https://github.com/h2o/picohttpparser/blob/master/test.c 673 | 674 | @("Request") 675 | unittest 676 | { 677 | auto parse(string data, Test test = Test.complete, int additional = 0) @safe nothrow @nogc 678 | { 679 | auto parser = initParser!Msg(); 680 | auto res = parser.parseRequest(data); 681 | // if (res < 0) writeln("Err: ", cast(ParserError)(-res)); 682 | final switch (test) 683 | { 684 | case Test.err: assert(res < -ParserError.partial); break; 685 | case Test.partial: assert(res == -ParserError.partial); break; 686 | case Test.complete: assert(res == data.length - additional); break; 687 | } 688 | 689 | return parser.msg; 690 | } 691 | 692 | // simple 693 | { 694 | auto req = parse("GET / HTTP/1.0\r\n\r\n"); 695 | assert(req.headers.length == 0); 696 | assert(req.method == "GET"); 697 | assert(req.uri == "/"); 698 | assert(req.minorVer == 0); 699 | } 700 | 701 | // parse headers 702 | { 703 | auto req = parse("GET /hoge HTTP/1.1\r\nHost: example.com\r\nCookie: \r\n\r\n"); 704 | assert(req.method == "GET"); 705 | assert(req.uri == "/hoge"); 706 | assert(req.minorVer == 1); 707 | assert(req.headers.length == 2); 708 | assert(req.headers[0] == Header("Host", "example.com")); 709 | assert(req.headers[1] == Header("Cookie", "")); 710 | } 711 | 712 | // multibyte included 713 | { 714 | auto req = parse("GET /hoge HTTP/1.1\r\nHost: example.com\r\nUser-Agent: \343\201\262\343/1.0\r\n\r\n"); 715 | assert(req.method == "GET"); 716 | assert(req.uri == "/hoge"); 717 | assert(req.minorVer == 1); 718 | assert(req.headers.length == 2); 719 | assert(req.headers[0] == Header("Host", "example.com")); 720 | assert(req.headers[1] == Header("User-Agent", "\343\201\262\343/1.0")); 721 | } 722 | 723 | //multiline 724 | { 725 | auto req = parse("GET / HTTP/1.0\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n"); 726 | assert(req.method == "GET"); 727 | assert(req.uri == "/"); 728 | assert(req.minorVer == 0); 729 | assert(req.headers.length == 3); 730 | assert(req.headers[0] == Header("foo", "")); 731 | assert(req.headers[1] == Header("foo", "b")); 732 | assert(req.headers[2] == Header(null, " \tc")); 733 | } 734 | 735 | // header name with trailing space 736 | parse("GET / HTTP/1.0\r\nfoo : ab\r\n\r\n", Test.err); 737 | 738 | // incomplete 739 | assert(parse("\r", Test.partial).method == null); 740 | assert(parse("\r\n", Test.partial).method == null); 741 | assert(parse("\r\nGET", Test.partial).method == null); 742 | assert(parse("GET", Test.partial).method == null); 743 | assert(parse("GET ", Test.partial).method == "GET"); 744 | assert(parse("GET /", Test.partial).uri == null); 745 | assert(parse("GET / ", Test.partial).uri == "/"); 746 | assert(parse("GET / HTTP/1.1", Test.partial).minorVer == 0); 747 | assert(parse("GET / HTTP/1.1\r", Test.partial).minorVer == 1); 748 | assert(parse("GET / HTTP/1.1\r\n", Test.partial).minorVer == 1); 749 | parse("GET / HTTP/1.0\r\n\r", Test.partial); 750 | parse("GET / HTTP/1.0\r\n\r\n", Test.complete); 751 | parse(" / HTTP/1.0\r\n\r\n", Test.err); // empty method 752 | parse("GET HTTP/1.0\r\n\r\n", Test.err); // empty request target 753 | parse("GET / \r\n\r\n", Test.err); // empty version 754 | parse("GET / HTTP/1.0\r\n:a\r\n\r\n", Test.err); // empty header name 755 | parse("GET / HTTP/1.0\r\n :a\r\n\r\n", Test.err); // empty header name (space only) 756 | parse("G\0T / HTTP/1.0\r\n\r\n", Test.err); // NUL in method 757 | parse("G\tT / HTTP/1.0\r\n\r\n", Test.err); // tab in method 758 | parse("GET /\x7f HTTP/1.0\r\n\r\n", Test.err); // DEL in uri 759 | parse("GET / HTTP/1.0\r\na\0b: c\r\n\r\n", Test.err); // NUL in header name 760 | parse("GET / HTTP/1.0\r\nab: c\0d\r\n\r\n", Test.err); // NUL in header value 761 | parse("GET / HTTP/1.0\r\na\033b: c\r\n\r\n", Test.err); // CTL in header name 762 | parse("GET / HTTP/1.0\r\nab: c\033\r\n\r\n", Test.err); // CTL in header value 763 | parse("GET / HTTP/1.0\r\n/: 1\r\n\r\n", Test.err); // invalid char in header value 764 | parse("GET / HTTP/1.0\r\n\r\n", Test.complete); // multiple spaces between tokens 765 | 766 | // accept MSB chars 767 | { 768 | auto res = parse("GET /\xa0 HTTP/1.0\r\nh: c\xa2y\r\n\r\n"); 769 | assert(res.method == "GET"); 770 | assert(res.uri == "/\xa0"); 771 | assert(res.minorVer == 0); 772 | assert(res.headers.length == 1); 773 | assert(res.headers[0] == Header("h", "c\xa2y")); 774 | } 775 | 776 | parse("GET / HTTP/1.0\r\n\x7b: 1\r\n\r\n", Test.err); // disallow '{' 777 | 778 | // exclude leading and trailing spaces in header value 779 | { 780 | auto req = parse("GET / HTTP/1.0\r\nfoo: a \t \r\n\r\n"); 781 | assert(req.headers[0].value == "a"); 782 | } 783 | 784 | // leave the body intact 785 | parse("GET / HTTP/1.0\r\n\r\nfoo bar baz", Test.complete, "foo bar baz".length); 786 | 787 | // realworld 788 | { 789 | auto req = parse("GET /cookies HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17\r\nAccept-Encoding: gzip,deflate,sdch\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\nCookie: name=wookie\r\n\r\n"); 790 | assert(req.method == "GET"); 791 | assert(req.uri == "/cookies"); 792 | assert(req.minorVer == 1); 793 | assert(req.headers[0] == Header("Host", "127.0.0.1:8090")); 794 | assert(req.headers[1] == Header("Connection", "keep-alive")); 795 | assert(req.headers[2] == Header("Cache-Control", "max-age=0")); 796 | assert(req.headers[3] == Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); 797 | assert(req.headers[4] == Header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17")); 798 | assert(req.headers[5] == Header("Accept-Encoding", "gzip,deflate,sdch")); 799 | assert(req.headers[6] == Header("Accept-Language", "en-US,en;q=0.8")); 800 | assert(req.headers[7] == Header("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3")); 801 | assert(req.headers[8] == Header("Cookie", "name=wookie")); 802 | } 803 | 804 | // newline 805 | { 806 | auto req = parse("GET / HTTP/1.0\nfoo: a\n\n"); 807 | } 808 | } 809 | 810 | @("Response") 811 | // Tests from https://github.com/h2o/picohttpparser/blob/master/test.c 812 | unittest 813 | { 814 | auto parse(string data, Test test = Test.complete, int additional = 0) @safe nothrow 815 | { 816 | auto parser = initParser!Msg(); 817 | 818 | auto res = parser.parseResponse(data); 819 | // if (res < 0) writeln("Err: ", cast(ParserError)(-res)); 820 | final switch (test) 821 | { 822 | case Test.err: assert(res < -ParserError.partial); break; 823 | case Test.partial: assert(res == -ParserError.partial); break; 824 | case Test.complete: assert(res == data.length - additional); break; 825 | } 826 | 827 | return parser.msg; 828 | } 829 | 830 | // simple 831 | { 832 | auto res = parse("HTTP/1.0 200 OK\r\n\r\n"); 833 | assert(res.headers.length == 0); 834 | assert(res.status == 200); 835 | assert(res.minorVer == 0); 836 | assert(res.statusMsg == "OK"); 837 | } 838 | 839 | parse("HTTP/1.0 200 OK\r\n\r", Test.partial); // partial 840 | 841 | // parse headers 842 | { 843 | auto res = parse("HTTP/1.1 200 OK\r\nHost: example.com\r\nCookie: \r\n\r\n"); 844 | assert(res.headers.length == 2); 845 | assert(res.minorVer == 1); 846 | assert(res.status == 200); 847 | assert(res.statusMsg == "OK"); 848 | assert(res.headers[0] == Header("Host", "example.com")); 849 | assert(res.headers[1] == Header("Cookie", "")); 850 | } 851 | 852 | // parse multiline 853 | { 854 | auto res = parse("HTTP/1.0 200 OK\r\nfoo: \r\nfoo: b\r\n \tc\r\n\r\n"); 855 | assert(res.headers.length == 3); 856 | assert(res.minorVer == 0); 857 | assert(res.status == 200); 858 | assert(res.statusMsg == "OK"); 859 | assert(res.headers[0] == Header("foo", "")); 860 | assert(res.headers[1] == Header("foo", "b")); 861 | assert(res.headers[2] == Header(null, " \tc")); 862 | } 863 | 864 | // internal server error 865 | { 866 | auto res = parse("HTTP/1.0 500 Internal Server Error\r\n\r\n"); 867 | assert(res.headers.length == 0); 868 | assert(res.minorVer == 0); 869 | assert(res.status == 500); 870 | assert(res.statusMsg == "Internal Server Error"); 871 | } 872 | 873 | parse("H", Test.partial); // incomplete 1 874 | parse("HTTP/1.", Test.partial); // incomplete 2 875 | assert(parse("HTTP/1.1", Test.partial).minorVer == 0); // incomplete 3 - differs from picohttpparser as we don't parse exact version 876 | assert(parse("HTTP/1.1 ", Test.partial).minorVer == 1); // incomplete 4 877 | parse("HTTP/1.1 2", Test.partial); // incomplete 5 878 | assert(parse("HTTP/1.1 200", Test.partial).status == 0); // incomplete 6 879 | assert(parse("HTTP/1.1 200 ", Test.partial).status == 200); // incomplete 7 880 | assert(parse("HTTP/1.1 200\r", Test.partial).status == 200); // incomplete 7.1 881 | parse("HTTP/1.1 200 O", Test.partial); // incomplete 8 882 | assert(parse("HTTP/1.1 200 OK\r", Test.partial).statusMsg == "OK"); // incomplete 9 - differs from picohttpparser 883 | assert(parse("HTTP/1.1 200 OK\r\n", Test.partial).statusMsg == "OK"); // incomplete 10 884 | assert(parse("HTTP/1.1 200 OK\n", Test.partial).statusMsg == "OK"); // incomplete 11 885 | assert(parse("HTTP/1.1 200 OK\r\nA: 1\r", Test.partial).headers.length == 0); // incomplete 11 886 | parse("HTTP/1.1 200 OK\r\n\r\n", Test.complete); // multiple spaces between tokens 887 | 888 | // incomplete 12 889 | { 890 | auto res = parse("HTTP/1.1 200 OK\r\nA: 1\r\n", Test.partial); 891 | assert(res.headers.length == 1); 892 | assert(res.headers[0] == Header("A", "1")); 893 | } 894 | 895 | // slowloris (incomplete) 896 | { 897 | auto parser = initParser!Msg(); 898 | assert(parser.parseResponse("HTTP/1.0 200 OK\r\n") == -ParserError.partial); 899 | assert(parser.parseResponse("HTTP/1.0 200 OK\r\n\r") == -ParserError.partial); 900 | assert(parser.parseResponse("HTTP/1.0 200 OK\r\n\r\nblabla") == "HTTP/1.0 200 OK\r\n\r\n".length); 901 | } 902 | 903 | parse("HTTP/1. 200 OK\r\n\r\n", Test.err); // invalid http version 904 | parse("HTTP/1.2z 200 OK\r\n\r\n", Test.err); // invalid http version 2 905 | parse("HTTP/1.1 OK\r\n\r\n", Test.err); // no status code 906 | 907 | assert(parse("HTTP/1.1 200\r\n\r\n").statusMsg == ""); // accept missing trailing whitespace in status-line 908 | parse("HTTP/1.1 200X\r\n\r\n", Test.err); // garbage after status 1 909 | parse("HTTP/1.1 200X \r\n\r\n", Test.err); // garbage after status 2 910 | parse("HTTP/1.1 200X OK\r\n\r\n", Test.err); // garbage after status 3 911 | 912 | assert(parse("HTTP/1.1 200 OK\r\nbar: \t b\t \t\r\n\r\n").headers[0].value == "b"); // exclude leading and trailing spaces in header value 913 | } 914 | 915 | @("Incremental") 916 | unittest 917 | { 918 | string req = "GET /cookies HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17\r\nAccept-Encoding: gzip,deflate,sdch\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\nCookie: name=wookie\r\n\r\n"; 919 | auto parser = initParser!Msg(); 920 | uint parsed; 921 | auto res = parser.parseRequest(req[0.."GET /cookies HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nConn".length], parsed); 922 | assert(res == -ParserError.partial); 923 | assert(parser.msg.method == "GET"); 924 | assert(parser.msg.uri == "/cookies"); 925 | assert(parser.msg.minorVer == 1); 926 | assert(parser.msg.headers.length == 1); 927 | assert(parser.msg.headers[0] == Header("Host", "127.0.0.1:8090")); 928 | 929 | res = parser.parseRequest(req, parsed); 930 | assert(res == req.length); 931 | assert(parser.msg.method == "GET"); 932 | assert(parser.msg.uri == "/cookies"); 933 | assert(parser.msg.minorVer == 1); 934 | assert(parser.msg.headers[0] == Header("Host", "127.0.0.1:8090")); 935 | assert(parser.msg.headers[1] == Header("Connection", "keep-alive")); 936 | assert(parser.msg.headers[2] == Header("Cache-Control", "max-age=0")); 937 | assert(parser.msg.headers[3] == Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); 938 | assert(parser.msg.headers[4] == Header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17")); 939 | assert(parser.msg.headers[5] == Header("Accept-Encoding", "gzip,deflate,sdch")); 940 | assert(parser.msg.headers[6] == Header("Accept-Language", "en-US,en;q=0.8")); 941 | assert(parser.msg.headers[7] == Header("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3")); 942 | assert(parser.msg.headers[8] == Header("Cookie", "name=wookie")); 943 | } 944 | 945 | //** used intrinsics **// 946 | 947 | version(LDC) 948 | { 949 | public import ldc.intrinsics; 950 | 951 | version (X86) version = ANY_X86; 952 | else version (X86_64) version = ANY_X86; 953 | 954 | alias _expect = llvm_expect; 955 | 956 | version (ANY_X86) 957 | { 958 | public import core.simd; 959 | import ldc.gccbuiltins_x86; 960 | enum LDC_with_SSE42 = __traits(targetHasFeature, "sse4.2"); 961 | 962 | // These specify the type of data that we're comparing. 963 | enum _SIDD_UBYTE_OPS = 0x00; 964 | enum _SIDD_UWORD_OPS = 0x01; 965 | enum _SIDD_SBYTE_OPS = 0x02; 966 | enum _SIDD_SWORD_OPS = 0x03; 967 | 968 | // These specify the type of comparison operation. 969 | enum _SIDD_CMP_EQUAL_ANY = 0x00; 970 | enum _SIDD_CMP_RANGES = 0x04; 971 | enum _SIDD_CMP_EQUAL_EACH = 0x08; 972 | enum _SIDD_CMP_EQUAL_ORDERED = 0x0c; 973 | 974 | // These are used in _mm_cmpXstri() to specify the return. 975 | enum _SIDD_LEAST_SIGNIFICANT = 0x00; 976 | enum _SIDD_MOST_SIGNIFICANT = 0x40; 977 | 978 | // These macros are used in _mm_cmpXstri() to specify the return. 979 | enum _SIDD_BIT_MASK = 0x00; 980 | enum _SIDD_UNIT_MASK = 0x40; 981 | 982 | // some definition aliases to commonly used names 983 | alias __m128i = int4; 984 | 985 | // some used methods aliases 986 | alias _mm_loadu_si128 = loadUnaligned!__m128i; 987 | alias _mm_cmpestri = __builtin_ia32_pcmpestri128; 988 | } 989 | else 990 | enum LDC_with_SSE42 = false; 991 | } 992 | else 993 | { 994 | enum LDC_with_SSE42 = false; 995 | 996 | T _expect(T)(T val, T expected_val) if (__traits(isIntegral, T)) 997 | { 998 | pragma(inline, true); 999 | return val; 1000 | } 1001 | } 1002 | 1003 | pragma(msg, "SSE: ", LDC_with_SSE42); 1004 | --------------------------------------------------------------------------------