1 // Modified from https://github.com/tchaloupka/httparsed
2 module dast.http.parser;
3 
4 nothrow @safe @nogc:
5 
6 /// Parser error codes
7 enum ParseError {
8 	ok, /// no error
9 	partial = 1, /// not enough data to parse message
10 	newLine, /// invalid character in new line
11 	headerName, /// invalid character in header name
12 	status, /// invalid character in response status
13 	token, /// invalid character in token
14 	noHeaderName, /// empty header name
15 	noMethod, /// no method in request line
16 	noVersion, /// no version in request line / response status line
17 	noUri, /// no URI in request line
18 	noStatus, /// no status code or text in status line
19 	invalidMethod, /// invalid method in request line
20 	invalidVersion, /// invalid version for the protocol message
21 }
22 
23 /// Helper function to initialize message parser
24 auto initParser(MSG, Args...)(Args args)
25 	=> Parser!MSG(args);
26 
27 /**
28  *  HTTP/RTSP message parser.
29  */
30 struct Parser(MSG) {
31 	import std.traits : ForeachType, isArray, Unqual;
32 
33 	this(Args...)(Args args) {
34 		msg = MSG(args);
35 	}
36 
37 	/**
38 		Parses message request (request line + headers).
39 
40 		Params:
41 			buffer = buffer to parse message from
42 			lastPos = optional argument to store / pass previous position to which message was
43 						already parsed (speeds up parsing when message comes in parts)
44 
45 		Returns:
46 		  * parsed message header length when parsed sucessfully
47 		  * `-ParseError.partial` on error (ie. -1 when message header is not comlete yet)
48 	 */
49 	int parseRequest(T)(T buffer, ref uint lastPos)
50 	if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) {
51 		static if (is(Unqual!(ForeachType!T) == char))
52 			return parse!parseRequestLine(cast(const(ubyte)[])buffer, lastPos);
53 		else
54 			return parse!parseRequestLine(buffer, lastPos);
55 	}
56 
57 	/// ditto
58 	int parseRequest(T)(T buffer)
59 	if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) {
60 		uint lastPos;
61 		static if (is(Unqual!(ForeachType!T) == char))
62 			return parse!parseRequestLine(cast(const(ubyte)[])buffer, lastPos);
63 		else
64 			return parse!parseRequestLine(buffer, lastPos);
65 	}
66 
67 	/**
68 		Parses message response (status line + headers).
69 
70 		Params:
71 			buffer = buffer to parse message from
72 			lastPos = optional argument to store / pass previous position to which message was
73 						already parsed (speeds up parsing when message comes in parts)
74 
75 		Returns:
76 		  * parsed message header length when parsed sucessfully
77 		  * `-ParseError.partial` on error (ie. -1 when message header is not comlete yet)
78 	 */
79 	int parseResponse(T)(T buffer, ref uint lastPos)
80 	if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) {
81 		static if (is(Unqual!(ForeachType!T) == char))
82 			return parse!parseStatusLine(cast(const(ubyte)[])buffer, lastPos);
83 		else
84 			return parse!parseStatusLine(buffer, lastPos);
85 	}
86 
87 	/// ditto
88 	int parseResponse(T)(T buffer)
89 	if (isArray!T && (is(Unqual!(ForeachType!T) == char) || is(Unqual!(ForeachType!T) == ubyte))) {
90 		uint lastPos;
91 		static if (is(Unqual!(ForeachType!T) == char))
92 			return parse!parseStatusLine(cast(const(ubyte)[])buffer, lastPos);
93 		else
94 			return parse!parseStatusLine(buffer, lastPos);
95 	}
96 
97 	/// Gets provided structure used during parsing
98 	ref MSG msg() return  => m_msg;
99 
100 	alias msg this;
101 
102 private:
103 
104 	// character map of valid characters for token, forbidden:
105 	//   0-SP, DEL, HT
106 	//   ()<>@,;:\"/[]?={}
107 	enum tokenRanges = "\0 \"\"(),,//:@[]{}\x7f\xff";
108 	enum tokenSSERanges = "\0 \"\"(),,//:@[]{\xff"; // merge of last range due to the SSE register size limit
109 
110 	enum versionRanges = "\0-:@[`{\xff"; // allow only [A-Za-z./] characters
111 
112 	MSG m_msg;
113 
114 	int parse(alias pred)(const(ubyte)[] buf, ref uint lastPos)
115 	in (buf.length >= lastPos) {
116 		immutable l = buf.length;
117 
118 		if (expect(!lastPos, true)) {
119 			if (expect(!buf.length, false))
120 				return err(ParseError.partial);
121 
122 			// skip first empty line (some clients add CRLF after POST content)
123 			if (expect(buf[0] == '\r', false)) {
124 				if (expect(buf.length == 1, false))
125 					return err(ParseError.partial);
126 				if (expect(buf[1] != '\n', false))
127 					return err(ParseError.newLine);
128 				lastPos += 2;
129 				buf = buf[lastPos .. $];
130 			} else if (expect(buf[0] == '\n', false))
131 				buf = buf[++lastPos .. $];
132 
133 			immutable res = pred(buf);
134 			if (expect(res < 0, false))
135 				return res;
136 
137 			lastPos = cast(int)(l - buf.length); // store index of last parsed line
138 		} else
139 			buf = buf[lastPos .. $]; // skip already parsed lines
140 
141 		immutable hdrRes = parseHeaders(buf);
142 		lastPos = cast(int)(l - buf.length); // store index of last parsed line
143 
144 		if (expect(hdrRes < 0, false))
145 			return hdrRes;
146 		return lastPos; // finished
147 	}
148 
149 	int parseHeaders(ref const(ubyte)[] buf) {
150 		bool hasHeader;
151 		size_t start, i;
152 		const(ubyte)[] name, value;
153 		while (true) {
154 			// check for msg headers end
155 			if (expect(buf.length == 0, false))
156 				return err(ParseError.partial);
157 			if (buf[0] == '\r') {
158 				if (expect(buf.length == 1, false))
159 					return err(ParseError.partial);
160 				if (expect(buf[1] != '\n', false))
161 					return err(ParseError.newLine);
162 
163 				buf = buf[2 .. $];
164 				return 0;
165 			}
166 			if (expect(buf[0] == '\n', false)) {
167 				buf = buf[1 .. $];
168 				return 0;
169 			}
170 
171 			if (!hasHeader || (buf[i] != ' ' && buf[i] != '\t')) {
172 				auto ret = parseToken!(tokenRanges, ':', tokenSSERanges)(buf, i);
173 				if (expect(ret < 0, false))
174 					return ret;
175 				if (expect(start == i, false))
176 					return err(ParseError.noHeaderName);
177 				name = buf[start .. i]; // store header name
178 				i++; // move index after colon
179 
180 				// skip over SP and HT
181 				for (;; ++i) {
182 					if (expect(i == buf.length, false))
183 						return err(ParseError.partial);
184 					if (buf[i] != ' ' && buf[i] != '\t')
185 						break;
186 				}
187 				start = i;
188 			} else
189 				name = null; // multiline header
190 
191 			// parse value
192 			auto ret = parseToken!("\0\010\012\037\177\177", "\r\n")(buf, i);
193 			if (expect(ret < 0, false))
194 				return ret;
195 			value = buf[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 				// remove trailing SPs and HTABs
200 				if (expect(value.length && (value[$ - 1] == ' ' || value[$ - 1] == '\t'), false)) {
201 					int j = cast(int)value.length - 2;
202 					for (; j >= 0; --j)
203 						if (!(value[j] == ' ' || value[j] == '\t'))
204 							break;
205 					value = value[0 .. j + 1];
206 				}
207 
208 				static if (is(typeof(m_msg.onHeader("", "")) == void))
209 					m_msg.onHeader(cast(const(char)[])name, cast(const(char)[])value);
210 				else {
211 					auto r = m_msg.onHeader(cast(const(char)[])name, cast(const(char)[])value);
212 					if (expect(r < 0, false))
213 						return r;
214 				}
215 			}
216 
217 			// header line completed -> advance buffer
218 			buf = buf[i .. $];
219 			start = i = 0;
220 		}
221 		assert(0);
222 	}
223 
224 	auto parseRequestLine(ref const(ubyte)[] buf) {
225 		size_t start, i;
226 
227 		// METHOD
228 		auto ret = parseToken!(tokenRanges, ' ', tokenSSERanges)(buf, i);
229 		if (expect(ret < 0, false))
230 			return ret;
231 		if (expect(start == i, false))
232 			return err(ParseError.noMethod);
233 
234 		static if (__traits(hasMember, m_msg, "onMethod")) {
235 			static if (is(typeof(m_msg.onMethod("")) == void))
236 				m_msg.onMethod(cast(const(char)[])buf[start .. i]);
237 			else {
238 				auto r = m_msg.onMethod(cast(const(char)[])buf[start .. i]);
239 				if (expect(r < 0, false))
240 					return r;
241 			}
242 		}
243 		mixin(skipSpaces!(ParseError.noUri));
244 		start = i;
245 
246 		// PATH
247 		ret = parseToken!("\000\040\177\177", ' ')(buf, i);
248 		if (expect(ret < 0, false))
249 			return ret;
250 		static if (__traits(hasMember, m_msg, "onUri")) {
251 			static if (is(typeof(m_msg.onUri("")) == void))
252 				m_msg.onUri(cast(const(char)[])buf[start .. i]);
253 			else {
254 				auto ur = m_msg.onUri(cast(const(char)[])buf[start .. i]);
255 				if (expect(ur < 0, false))
256 					return ur;
257 			}
258 		}
259 		mixin(skipSpaces!(ParseError.noVersion));
260 		start = i;
261 
262 		// VERSION
263 		ret = parseToken!(versionRanges, "\r\n")(buf, i);
264 		if (expect(ret < 0, false))
265 			return ret;
266 		static if (__traits(hasMember, m_msg, "onVersion")) {
267 			static if (is(typeof(m_msg.onVersion("")) == void))
268 				m_msg.onVersion(cast(const(char)[])buf[start .. i]);
269 			else {
270 				auto vr = m_msg.onVersion(cast(const(char)[])buf[start .. i]);
271 				if (expect(vr < 0, false))
272 					return vr;
273 			}
274 		}
275 		mixin(advanceNewline);
276 
277 		// advance buffer after the request line
278 		buf = buf[i .. $];
279 		return 0;
280 	}
281 
282 	auto parseStatusLine(ref const(ubyte)[] buf) {
283 		size_t start, i;
284 
285 		// VERSION
286 		auto ret = parseToken!(versionRanges, ' ')(buf, i);
287 		if (expect(ret < 0, false))
288 			return ret;
289 		if (expect(start == i, false))
290 			return err(ParseError.noVersion);
291 		static if (__traits(hasMember, m_msg, "onVersion")) {
292 			static if (is(typeof(m_msg.onVersion("")) == void))
293 				m_msg.onVersion(cast(const(char)[])buf[start .. i]);
294 			else {
295 				auto r = m_msg.onVersion(cast(const(char)[])buf[start .. i]);
296 				if (expect(r < 0, false))
297 					return r;
298 			}
299 		}
300 		mixin(skipSpaces!(ParseError.noStatus));
301 		start = i;
302 
303 		// STATUS CODE
304 		if (expect(i + 3 >= buf.length, false))
305 			return err(ParseError.partial); // not enough data - we want at least [:digit:][:digit:][:digit:]<other char> to try to parse
306 
307 		int code;
308 		foreach (j, m; [100, 10, 1]) {
309 			if (buf[i + j] < '0' || buf[i + j] > '9')
310 				return err(ParseError.status);
311 			code += (buf[start + j] - '0') * m;
312 		}
313 		i += 3;
314 		static if (__traits(hasMember, m_msg, "onStatus")) {
315 			static if (is(typeof(m_msg.onStatus(code)) == void))
316 				m_msg.onStatus(code);
317 			else {
318 				auto sr = m_msg.onStatus(code);
319 				if (expect(sr < 0, false))
320 					return sr;
321 			}
322 		}
323 		if (expect(i == buf.length, false))
324 			return err(ParseError.partial);
325 		if (expect(buf[i] != ' ' && buf[i] != '\r' && buf[i] != '\n', false))
326 			return err(ParseError.status); // Garbage after status
327 
328 		start = i;
329 
330 		// MESSAGE
331 		ret = parseToken!("\0\010\012\037\177\177", "\r\n")(buf, i);
332 		if (expect(ret < 0, false))
333 			return ret;
334 		static if (__traits(hasMember, m_msg, "onStatusMsg")) {
335 			// remove preceding space (we did't advance over spaces because possibly missing status message)
336 			if (i > start) {
337 				while (buf[start] == ' ' && start < i)
338 					start++;
339 				if (i > start) {
340 					static if (is(typeof(m_msg.onStatusMsg("")) == void))
341 						m_msg.onStatusMsg(cast(const(char)[])buf[start .. i]);
342 					else {
343 						auto smr = m_msg.onStatusMsg(cast(const(char)[])buf[start .. i]);
344 						if (expect(smr < 0, false))
345 							return smr;
346 					}
347 				}
348 			}
349 		}
350 		mixin(advanceNewline);
351 
352 		// advance buffer after the status line
353 		buf = buf[i .. $];
354 		return 0;
355 	}
356 
357 	/**
358 	 * Advances buffer over the token to the next character while checking for valid characters.
359 	 * On success, buffer index is left on the next character.
360 	 *
361 	 * Params:
362 	 *      ranges = ranges of characters to stop on
363 	 *      sseRanges = if null, same ranges is used, but they are limited to 8 ranges
364 	 *      next  = next character/s to stop on (must be present in the provided ranges too)
365 	 * Returns: 0 on success error code otherwise
366 	 */
367 	int parseToken(string ranges, alias next, string sseRanges = null)(
368 		const(ubyte)[] buf, ref size_t i) pure {
369 		version (DigitalMars) {
370 			static if (__VERSION__ >= 2094)
371 				pragma(inline, true); // older compilers can't inline this
372 		} else
373 			pragma(inline, true);
374 
375 		immutable charMap = parseTokenCharMap!(ranges)();
376 
377 		static if (LDC_with_SSE42) {
378 			// CT function to prepare input for SIMD vector enum
379 			static byte[16] padRanges()(string ranges) {
380 				byte[16] res;
381 				// res[0..ranges.length] = cast(byte[])ranges[]; - broken on macOS betterC tests
382 				foreach (i, c; ranges)
383 					res[i] = cast(byte)c;
384 				return res;
385 			}
386 
387 			static if (sseRanges)
388 				alias usedRng = sseRanges;
389 			else
390 				alias usedRng = ranges;
391 			static assert(usedRng.length <= 16, "Ranges must be at most 16 characters long");
392 			static assert(usedRng.length % 2 == 0, "Ranges must have even number of characters");
393 			enum rangesSize = usedRng.length;
394 			enum byte16 rngE = padRanges(usedRng);
395 
396 			if (expect(buf.length - i >= 16, true)) {
397 				size_t left = (buf.length - i) & ~15; // round down to multiple of 16
398 				byte16 ranges16 = rngE;
399 
400 				do {
401 					byte16 b16 = () @trusted {
402 						return cast(byte16)_mm_loadu_si128(cast(int4*)&buf[i]);
403 					}();
404 					immutable r = _mm_cmpestri(
405 						ranges16, rangesSize,
406 						b16, 16,
407 						_SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS
408 					);
409 
410 					if (r != 16) {
411 						i += r;
412 						goto FOUND;
413 					}
414 					i += 16;
415 					left -= 16;
416 				}
417 				while (expect(left != 0, true));
418 			}
419 		} else {
420 			// faster unrolled loop to iterate over 8 characters
421 			while (expect(buf.length - i >= 8, true)) {
422 				static foreach (_; 0 .. 8) {
423 					if (expect(!charMap[buf[i]], false))
424 						goto FOUND;
425 					++i;
426 				}
427 			}
428 		}
429 
430 		// handle the rest
431 		if (expect(i >= buf.length, false))
432 			return err(ParseError.partial);
433 
434 		FOUND: while (true) {
435 			static if (is(typeof(next) == char)) {
436 				static assert(!charMap[next], "Next character is not in ranges");
437 				if (buf[i] == next)
438 					return 0;
439 			} else {
440 				static assert(next.length > 0, "Next character not provided");
441 				static foreach (c; next) {
442 					static assert(!charMap[c], "Next character is not in ranges");
443 					if (buf[i] == c)
444 						return 0;
445 				}
446 			}
447 			if (expect(!charMap[buf[i]], false))
448 				return err(ParseError.token);
449 			if (expect(++i == buf.length, false))
450 				return err(ParseError.partial);
451 		}
452 	}
453 
454 	// advances over new line
455 	enum advanceNewline = q{
456 			assert(i < buf.length);
457 			if (expect(buf[i] == '\r', true))
458 			{
459 				if (expect(i+1 == buf.length, false)) return err(ParseError.partial);
460 				if (expect(buf[i+1] != '\n', false)) return err(ParseError.newLine);
461 				i += 2;
462 			}
463 			else if (buf[i] == '\n') ++i;
464 			else assert(0);
465 		};
466 
467 	// skips over spaces in the buffer
468 	template skipSpaces(ParseError err) {
469 		enum skipSpaces = `
470 			do {
471 				++i;
472 				if (expect(buf.length == i, false)) return err(ParseError.partial);
473 				if (expect(buf[i] == '\r' || buf[i] == '\n', false)) return err(`
474 			~ err.stringof ~ `);
475 			} while (buf[i] == ' ');
476 		`;
477 	}
478 }
479 
480 ///
481 @"example"unittest {
482 	// init parser
483 	auto reqParser = initParser!Msg(); // or `Parser!MSG reqParser;`
484 	auto resParser = initParser!Msg(); // or `Parser!MSG resParser;`
485 
486 	// parse request
487 	string data = "GET /foo HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n";
488 	// returns parsed message header length when parsed sucessfully, -ParseError on error
489 	int res = reqParser.parseRequest(data);
490 	assert(res == data.length);
491 	assert(reqParser.method == "GET");
492 	assert(reqParser.uri == "/foo");
493 	assert(reqParser.minorVer == 1); // HTTP/1.1
494 	assert(reqParser.headers.length == 1);
495 	assert(reqParser.headers[0].name == "Host");
496 	assert(reqParser.headers[0].value == "127.0.0.1:8090");
497 
498 	// parse response
499 	data = "HTTP/1.0 200 OK\r\n";
500 	uint lastPos; // store last parsed position for next run
501 	res = resParser.parseResponse(data, lastPos);
502 	assert(res == -ParseError.partial); // no complete message header yet
503 	data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nfoo";
504 	res = resParser.parseResponse(data, lastPos); // starts parsing from previous position
505 	assert(res == data.length - 3); // whole message header parsed, body left to be handled based on actual header values
506 	assert(resParser.minorVer == 0); // HTTP/1.0
507 	assert(resParser.status == 200);
508 	assert(resParser.statusMsg == "OK");
509 	assert(resParser.headers.length == 2);
510 	assert(resParser.headers[0].name == "Content-Type");
511 	assert(resParser.headers[0].value == "text/plain");
512 	assert(resParser.headers[1].name == "Content-Length");
513 	assert(resParser.headers[1].value == "3");
514 }
515 
516 /**
517  * Parses HTTP version from a slice returned in `onVersion` callback.
518  *
519  * Returns: minor version (0 for HTTP/1.0 or 1 for HTTP/1.1) on success or
520  *          `-ParseError.invalidVersion` on error
521  */
522 int parseHttpVersion(const(char)[] ver) pure {
523 	if (expect(ver.length != 8, false))
524 		return err(ParseError.invalidVersion);
525 
526 	static foreach (i, c; "HTTP/1.")
527 		if (expect(ver[i] != c, false))
528 			return err(ParseError.invalidVersion);
529 
530 	if (expect(ver[7] < '0' || ver[7] > '9', false))
531 		return err(ParseError.invalidVersion);
532 	return ver[7] - '0';
533 }
534 
535 @"parseHttpVersion"unittest {
536 	assert(parseHttpVersion("FOO") < 0);
537 	assert(parseHttpVersion("HTTP/1.") < 0);
538 	assert(parseHttpVersion("HTTP/1.12") < 0);
539 	assert(parseHttpVersion("HTTP/1.a") < 0);
540 	assert(parseHttpVersion("HTTP/2.0") < 0);
541 	assert(parseHttpVersion("HTTP/1.00") < 0);
542 	assert(parseHttpVersion("HTTP/1.0") == 0);
543 	assert(parseHttpVersion("HTTP/1.1") == 1);
544 }
545 
546 version (CI_MAIN) {
547 	// workaround for dub not supporting unittests with betterC
548 	version (D_BetterC) {
549 		extern (C) void main() {
550 			import core.stdc.stdio;
551 
552 			static foreach (u; __traits(getUnitTests, httparsed)) {
553 				debug printf("testing '" ~ __traits(getAttributes, u)[0] ~ "'\n");
554 				u();
555 			}
556 			debug printf("All unit tests have been run successfully.\n");
557 		}
558 	} else {
559 		void main() {
560 			version (unittest) {
561 			}  // run automagically
562 			else {
563 				import core.stdc.stdio;
564 
565 				// just a compilation test
566 				auto reqParser = initParser!Msg();
567 				auto resParser = initParser!Msg();
568 
569 				string data = "GET /foo HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n";
570 				int res = reqParser.parseRequest(data);
571 				assert(res == data.length);
572 
573 				data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 3\r\n\r\nfoo";
574 				res = resParser.parseResponse(data);
575 				assert(res == data.length - 3);
576 				() @trusted { printf("Test app works\n"); }();
577 			}
578 		}
579 	}
580 }
581 
582 private:
583 
584 int err(ParseError e) pure {
585 	pragma(inline, true);
586 	return -(cast(int)e);
587 }
588 
589 /// Builds valid char map from the provided ranges of invalid ones
590 bool[256] buildValidCharMap()(string invalidRanges) {
591 	assert(invalidRanges.length % 2 == 0, "Uneven ranges");
592 	bool[256] res = true;
593 
594 	for (int i = 0; i < invalidRanges.length; i += 2)
595 		for (int j = invalidRanges[i]; j <= invalidRanges[i + 1]; ++j)
596 			res[j] = false;
597 	return res;
598 }
599 
600 @"buildValidCharMap"unittest {
601 	string ranges = "\0 \"\"(),,//:@[]{{}}\x7f\xff";
602 	assert(buildValidCharMap(ranges) ==
603 		cast(bool[])[
604 			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,
605 			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,
606 			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,
607 			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,
608 			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,
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,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,
611 			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,
612 		]);
613 }
614 
615 immutable(bool[256]) parseTokenCharMap(string invalidRanges)() {
616 	static immutable charMap = buildValidCharMap(invalidRanges);
617 	return charMap;
618 }
619 
620 version (unittest)
621 	version = WITH_MSG;
622 else version (CI_MAIN)
623 	version = WITH_MSG;
624 
625 version (WITH_MSG) {
626 	// define our message content handler
627 	struct Header {
628 		const(char)[] name;
629 		const(char)[] value;
630 	}
631 
632 	// Just store slices of parsed message header
633 	struct Msg {
634 	@safe pure nothrow @nogc:
635 		void onMethod(const(char)[] method) {
636 			this.method = method;
637 		}
638 
639 		void onUri(const(char)[] uri) {
640 			this.uri = uri;
641 		}
642 
643 		int onVersion(const(char)[] ver) {
644 			minorVer = parseHttpVersion(ver);
645 			return minorVer >= 0 ? 0 : minorVer;
646 		}
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 
653 		void onStatus(int status) {
654 			this.status = status;
655 		}
656 
657 		void onStatusMsg(const(char)[] statusMsg) {
658 			this.statusMsg = statusMsg;
659 		}
660 
661 		const(char)[] method;
662 		const(char)[] uri;
663 		int minorVer;
664 		int status;
665 		const(char)[] statusMsg;
666 
667 		private {
668 			Header[32] m_headers;
669 			size_t m_headersLength;
670 		}
671 
672 		Header[] headers() return {
673 			return m_headers[0 .. m_headersLength];
674 		}
675 	}
676 
677 	enum Test {
678 		err,
679 		complete,
680 		partial
681 	}
682 }
683 
684 // Tests from https://github.com/h2o/picohttpparser/blob/master/test.c
685 
686 @"Request"unittest {
687 	auto parse(string data, Test test = Test.complete, int additional = 0) @safe nothrow @nogc {
688 		auto parser = initParser!Msg();
689 		auto res = parser.parseRequest(data);
690 		// if (res < 0) writeln("Err: ", cast(ParseError)(-res));
691 		final switch (test) {
692 		case Test.err:
693 			assert(res < -ParseError.partial);
694 			break;
695 		case Test.partial:
696 			assert(res == -ParseError.partial);
697 			break;
698 		case Test.complete:
699 			assert(res == data.length - additional);
700 			break;
701 		}
702 
703 		return parser.msg;
704 	}
705 
706 	// simple
707 	{
708 		auto req = parse("GET / HTTP/1.0\r\n\r\n");
709 		assert(req.headers.length == 0);
710 		assert(req.method == "GET");
711 		assert(req.uri == "/");
712 		assert(req.minorVer == 0);
713 	}
714 
715 	// parse headers
716 	{
717 		auto req = parse("GET /hoge HTTP/1.1\r\nHost: example.com\r\nCookie: \r\n\r\n");
718 		assert(req.method == "GET");
719 		assert(req.uri == "/hoge");
720 		assert(req.minorVer == 1);
721 		assert(req.headers.length == 2);
722 		assert(req.headers[0] == Header("Host", "example.com"));
723 		assert(req.headers[1] == Header("Cookie", ""));
724 	}
725 
726 	// multibyte included
727 	{
728 		auto req = parse(
729 			"GET /hoge HTTP/1.1\r\nHost: example.com\r\nUser-Agent: \343\201\262\343/1.0\r\n\r\n");
730 		assert(req.method == "GET");
731 		assert(req.uri == "/hoge");
732 		assert(req.minorVer == 1);
733 		assert(req.headers.length == 2);
734 		assert(req.headers[0] == Header("Host", "example.com"));
735 		assert(req.headers[1] == Header("User-Agent", "\343\201\262\343/1.0"));
736 	}
737 
738 	//multiline
739 	{
740 		auto req = parse("GET / HTTP/1.0\r\nfoo: \r\nfoo: b\r\n  \tc\r\n\r\n");
741 		assert(req.method == "GET");
742 		assert(req.uri == "/");
743 		assert(req.minorVer == 0);
744 		assert(req.headers.length == 3);
745 		assert(req.headers[0] == Header("foo", ""));
746 		assert(req.headers[1] == Header("foo", "b"));
747 		assert(req.headers[2] == Header(null, "  \tc"));
748 	}
749 
750 	// header name with trailing space
751 	parse("GET / HTTP/1.0\r\nfoo : ab\r\n\r\n", Test.err);
752 
753 	// incomplete
754 	assert(parse("\r", Test.partial).method == null);
755 	assert(parse("\r\n", Test.partial).method == null);
756 	assert(parse("\r\nGET", Test.partial).method == null);
757 	assert(parse("GET", Test.partial).method == null);
758 	assert(parse("GET ", Test.partial).method == "GET");
759 	assert(parse("GET /", Test.partial).uri == null);
760 	assert(parse("GET / ", Test.partial).uri == "/");
761 	assert(parse("GET / HTTP/1.1", Test.partial).minorVer == 0);
762 	assert(parse("GET / HTTP/1.1\r", Test.partial).minorVer == 1);
763 	assert(parse("GET / HTTP/1.1\r\n", Test.partial).minorVer == 1);
764 	parse("GET / HTTP/1.0\r\n\r", Test.partial);
765 	parse("GET / HTTP/1.0\r\n\r\n", Test.complete);
766 	parse(" / HTTP/1.0\r\n\r\n", Test.err); // empty method
767 	parse("GET  HTTP/1.0\r\n\r\n", Test.err); // empty request target
768 	parse("GET / \r\n\r\n", Test.err); // empty version
769 	parse("GET / HTTP/1.0\r\n:a\r\n\r\n", Test.err); // empty header name
770 	parse("GET / HTTP/1.0\r\n :a\r\n\r\n", Test.err); // empty header name (space only)
771 	parse("G\0T / HTTP/1.0\r\n\r\n", Test.err); // NUL in method
772 	parse("G\tT / HTTP/1.0\r\n\r\n", Test.err); // tab in method
773 	parse("GET /\x7f HTTP/1.0\r\n\r\n", Test.err); // DEL in uri
774 	parse("GET / HTTP/1.0\r\na\0b: c\r\n\r\n", Test.err); // NUL in header name
775 	parse("GET / HTTP/1.0\r\nab: c\0d\r\n\r\n", Test.err); // NUL in header value
776 	parse("GET / HTTP/1.0\r\na\033b: c\r\n\r\n", Test.err); // CTL in header name
777 	parse("GET / HTTP/1.0\r\nab: c\033\r\n\r\n", Test.err); // CTL in header value
778 	parse("GET / HTTP/1.0\r\n/: 1\r\n\r\n", Test.err); // invalid char in header value
779 	parse("GET   /   HTTP/1.0\r\n\r\n", Test.complete); // multiple spaces between tokens
780 
781 	// accept MSB chars
782 	{
783 		auto res = parse("GET /\xa0 HTTP/1.0\r\nh: c\xa2y\r\n\r\n");
784 		assert(res.method == "GET");
785 		assert(res.uri == "/\xa0");
786 		assert(res.minorVer == 0);
787 		assert(res.headers.length == 1);
788 		assert(res.headers[0] == Header("h", "c\xa2y"));
789 	}
790 
791 	parse("GET / HTTP/1.0\r\n\x7b: 1\r\n\r\n", Test.err); // disallow '{'
792 
793 	// exclude leading and trailing spaces in header value
794 	{
795 		auto req = parse("GET / HTTP/1.0\r\nfoo:  a \t \r\n\r\n");
796 		assert(req.headers[0].value == "a");
797 	}
798 
799 	// leave the body intact
800 	parse("GET / HTTP/1.0\r\n\r\nfoo bar baz", Test.complete, "foo bar baz".length);
801 
802 	// realworld
803 	{
804 		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");
805 		assert(req.method == "GET");
806 		assert(req.uri == "/cookies");
807 		assert(req.minorVer == 1);
808 		assert(req.headers[0] == Header("Host", "127.0.0.1:8090"));
809 		assert(req.headers[1] == Header("Connection", "keep-alive"));
810 		assert(req.headers[2] == Header("Cache-Control", "max-age=0"));
811 		assert(req.headers[3] == Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
812 		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"));
813 		assert(req.headers[5] == Header("Accept-Encoding", "gzip,deflate,sdch"));
814 		assert(req.headers[6] == Header("Accept-Language", "en-US,en;q=0.8"));
815 		assert(req.headers[7] == Header("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
816 		assert(req.headers[8] == Header("Cookie", "name=wookie"));
817 	}
818 
819 	// newline
820 	{
821 		auto req = parse("GET / HTTP/1.0\nfoo: a\n\n");
822 	}
823 }
824 
825 @"Response" // Tests from https://github.com/h2o/picohttpparser/blob/master/test.c
826 unittest {
827 	auto parse(string data, Test test = Test.complete, int additional = 0) @safe nothrow {
828 		auto parser = initParser!Msg();
829 
830 		auto res = parser.parseResponse(data);
831 		// if (res < 0) writeln("Err: ", cast(ParseError)(-res));
832 		final switch (test) {
833 		case Test.err:
834 			assert(res < -ParseError.partial);
835 			break;
836 		case Test.partial:
837 			assert(res == -ParseError.partial);
838 			break;
839 		case Test.complete:
840 			assert(res == data.length - additional);
841 		}
842 
843 		return parser.msg;
844 	}
845 
846 	// simple
847 	{
848 		auto res = parse("HTTP/1.0 200 OK\r\n\r\n");
849 		assert(res.headers.length == 0);
850 		assert(res.status == 200);
851 		assert(res.minorVer == 0);
852 		assert(res.statusMsg == "OK");
853 	}
854 
855 	parse("HTTP/1.0 200 OK\r\n\r", Test.partial); // partial
856 
857 	// parse headers
858 	{
859 		auto res = parse("HTTP/1.1 200 OK\r\nHost: example.com\r\nCookie: \r\n\r\n");
860 		assert(res.headers.length == 2);
861 		assert(res.minorVer == 1);
862 		assert(res.status == 200);
863 		assert(res.statusMsg == "OK");
864 		assert(res.headers[0] == Header("Host", "example.com"));
865 		assert(res.headers[1] == Header("Cookie", ""));
866 	}
867 
868 	// parse multiline
869 	{
870 		auto res = parse("HTTP/1.0 200 OK\r\nfoo: \r\nfoo: b\r\n  \tc\r\n\r\n");
871 		assert(res.headers.length == 3);
872 		assert(res.minorVer == 0);
873 		assert(res.status == 200);
874 		assert(res.statusMsg == "OK");
875 		assert(res.headers[0] == Header("foo", ""));
876 		assert(res.headers[1] == Header("foo", "b"));
877 		assert(res.headers[2] == Header(null, "  \tc"));
878 	}
879 
880 	// internal server error
881 	{
882 		auto res = parse("HTTP/1.0 500 Internal Server Error\r\n\r\n");
883 		assert(res.headers.length == 0);
884 		assert(res.minorVer == 0);
885 		assert(res.status == 500);
886 		assert(res.statusMsg == "Internal Server Error");
887 	}
888 
889 	parse("H", Test.partial); // incomplete 1
890 	parse("HTTP/1.", Test.partial); // incomplete 2
891 	assert(parse("HTTP/1.1", Test.partial).minorVer == 0); // incomplete 3 - differs from picohttpparser as we don't parse exact version
892 	assert(parse("HTTP/1.1 ", Test.partial).minorVer == 1); // incomplete 4
893 	parse("HTTP/1.1 2", Test.partial); // incomplete 5
894 	assert(parse("HTTP/1.1 200", Test.partial).status == 0); // incomplete 6
895 	assert(parse("HTTP/1.1 200 ", Test.partial).status == 200); // incomplete 7
896 	assert(parse("HTTP/1.1 200\r", Test.partial).status == 200); // incomplete 7.1
897 	parse("HTTP/1.1 200 O", Test.partial); // incomplete 8
898 	assert(parse("HTTP/1.1 200 OK\r", Test.partial).statusMsg == "OK"); // incomplete 9 - differs from picohttpparser
899 	assert(parse("HTTP/1.1 200 OK\r\n", Test.partial).statusMsg == "OK"); // incomplete 10
900 	assert(parse("HTTP/1.1 200 OK\n", Test.partial).statusMsg == "OK"); // incomplete 11
901 	assert(parse("HTTP/1.1 200 OK\r\nA: 1\r", Test.partial).headers.length == 0); // incomplete 11
902 	parse("HTTP/1.1   200   OK\r\n\r\n", Test.complete); // multiple spaces between tokens
903 
904 	// incomplete 12
905 	{
906 		auto res = parse("HTTP/1.1 200 OK\r\nA: 1\r\n", Test.partial);
907 		assert(res.headers.length == 1);
908 		assert(res.headers[0] == Header("A", "1"));
909 	}
910 
911 	// slowloris (incomplete)
912 	{
913 		auto parser = initParser!Msg();
914 		assert(parser.parseResponse("HTTP/1.0 200 OK\r\n") == -ParseError.partial);
915 		assert(parser.parseResponse("HTTP/1.0 200 OK\r\n\r") == -ParseError.partial);
916 		assert(parser.parseResponse(
917 				"HTTP/1.0 200 OK\r\n\r\nblabla") == "HTTP/1.0 200 OK\r\n\r\n".length);
918 	}
919 
920 	parse("HTTP/1. 200 OK\r\n\r\n", Test.err); // invalid http version
921 	parse("HTTP/1.2z 200 OK\r\n\r\n", Test.err); // invalid http version 2
922 	parse("HTTP/1.1  OK\r\n\r\n", Test.err); // no status code
923 
924 	assert(parse("HTTP/1.1 200\r\n\r\n").statusMsg == ""); // accept missing trailing whitespace in status-line
925 	parse("HTTP/1.1 200X\r\n\r\n", Test.err); // garbage after status 1
926 	parse("HTTP/1.1 200X \r\n\r\n", Test.err); // garbage after status 2
927 	parse("HTTP/1.1 200X OK\r\n\r\n", Test.err); // garbage after status 3
928 
929 	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
930 }
931 
932 @"Incremental"unittest {
933 	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";
934 	auto parser = initParser!Msg();
935 	uint parsed;
936 	auto res = parser.parseRequest(
937 		req[0 .. "GET /cookies HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nConn".length], parsed);
938 	assert(res == -ParseError.partial);
939 	assert(parser.msg.method == "GET");
940 	assert(parser.msg.uri == "/cookies");
941 	assert(parser.msg.minorVer == 1);
942 	assert(parser.msg.headers.length == 1);
943 	assert(parser.msg.headers[0] == Header("Host", "127.0.0.1:8090"));
944 
945 	res = parser.parseRequest(req, parsed);
946 	assert(res == req.length);
947 	assert(parser.msg.method == "GET");
948 	assert(parser.msg.uri == "/cookies");
949 	assert(parser.msg.minorVer == 1);
950 	assert(parser.msg.headers[0] == Header("Host", "127.0.0.1:8090"));
951 	assert(parser.msg.headers[1] == Header("Connection", "keep-alive"));
952 	assert(parser.msg.headers[2] == Header("Cache-Control", "max-age=0"));
953 	assert(parser.msg.headers[3] == Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
954 	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"));
955 	assert(parser.msg.headers[5] == Header("Accept-Encoding", "gzip,deflate,sdch"));
956 	assert(parser.msg.headers[6] == Header("Accept-Language", "en-US,en;q=0.8"));
957 	assert(parser.msg.headers[7] == Header("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
958 	assert(parser.msg.headers[8] == Header("Cookie", "name=wookie"));
959 }
960 
961 // used intrinsics
962 
963 version (LDC) {
964 	public import core.simd;
965 	public import ldc.intrinsics;
966 	import ldc.gccbuiltins_x86;
967 
968 	enum LDC_with_SSE42 = __traits(targetHasFeature, "sse4.2");
969 
970 	// These specify the type of data that we're comparing.
971 	enum {
972 		_SIDD_UBYTE_OPS = 0x00,
973 		_SIDD_UWORD_OPS = 0x01,
974 		_SIDD_SBYTE_OPS = 0x02,
975 		_SIDD_SWORD_OPS = 0x03,
976 
977 		// These specify the type of comparison operation.
978 		_SIDD_CMP_EQUAL_ANY = 0x00,
979 		_SIDD_CMP_RANGES = 0x04,
980 		_SIDD_CMP_EQUAL_EACH = 0x08,
981 		_SIDD_CMP_EQUAL_ORDERED = 0x0c,
982 
983 		// These are used in _mm_cmpXstri() to specify the return.
984 		_SIDD_LEAST_SIGNIFICANT = 0x00,
985 		_SIDD_MOST_SIGNIFICANT = 0x40,
986 
987 		// These macros are used in _mm_cmpXstri() to specify the return.
988 		_SIDD_BIT_MASK = 0x00,
989 		_SIDD_UNIT_MASK = 0x40,
990 	}
991 
992 	// some used methods aliases
993 	alias expect = llvm_expect;
994 	alias _mm_loadu_si128 = loadUnaligned!int4;
995 	alias _mm_cmpestri = __builtin_ia32_pcmpestri128;
996 } else {
997 	enum LDC_with_SSE42 = false;
998 
999 	T expect(T)(T val, T expected_val) if (__traits(isIntegral, T)) {
1000 		pragma(inline, true);
1001 		return val;
1002 	}
1003 }
1004 
1005 pragma(msg, "SSE: ", LDC_with_SSE42);