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);