1 // Copyright (c) 2013 Heapsource.com and Contributors - http://www.heapsource.com 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 deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // 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 NON-INFRINGEMENT. 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 FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 // 21 22 23 module http.parser.core; 24 import http.parser.c; 25 import std.stdio; 26 import std.conv; 27 import std.string : toStringz, CaseSensitive, indexOf; 28 import std.stdint : uint16_t; 29 30 enum HttpParserType { 31 REQUEST, 32 RESPONSE, 33 BOTH 34 }; 35 36 enum HttpBodyTransmissionMode { 37 None, 38 ContentLength, 39 Chunked 40 }; 41 42 @property bool shouldRead(HttpBodyTransmissionMode mode) pure nothrow { 43 return mode > HttpBodyTransmissionMode.None; 44 } 45 46 public struct HttpVersion { 47 private: 48 ushort _minor; 49 ushort _major; 50 string _str; 51 public: 52 this(ushort major, ushort minor) { 53 _major = major; 54 _minor = minor; 55 _str = std..string.format("%d.%d", _major, _minor); 56 } 57 58 @property { 59 ushort major() pure nothrow { 60 return _major; 61 } 62 ushort minor() pure nothrow { 63 return _minor; 64 } 65 } 66 67 string toString() { 68 return _str; 69 } 70 } 71 public struct HttpHeader { 72 package string _name, _value; 73 74 public: 75 @property string name() { 76 return _name; 77 } 78 @property void name(string name) { 79 _name = name; 80 } 81 @property string value() { 82 return _value; 83 } 84 @property void value(string value) { 85 _value = value; 86 } 87 88 @property bool hasValue() { 89 return _value !is null; 90 } 91 92 @property bool hasName() { 93 return _name !is null; 94 } 95 96 @property bool isEmpty() { 97 return !hasName() && !hasValue(); 98 } 99 } 100 101 public class HttpParserException : Exception { 102 private string _name; 103 public this(string message, string name, string filename = __FILE__, size_t line = __LINE__, Throwable next = null) { 104 super(message, filename, line, next); 105 } 106 public @property string name() { 107 return _name; 108 } 109 public @property void name(string name) { 110 _name = name; 111 } 112 } 113 114 public struct HttpBodyChunk { 115 private: 116 ubyte[] _buffer; 117 bool _isFinal; 118 119 public: 120 this(ubyte[] buffer, bool isFinal) 121 { 122 _buffer = buffer; 123 _isFinal = isFinal; 124 } 125 126 @property { 127 128 bool isFinal() pure nothrow { 129 return _isFinal; 130 } 131 132 ubyte[] buffer() pure nothrow { 133 return _buffer; 134 } 135 136 } 137 } 138 139 public alias void delegate(HttpParser) HttpParserDelegate; 140 public alias void delegate(HttpParser, HttpBodyChunk) HttpParserBodyChunkDelegate; 141 public alias void delegate(HttpParser, string data) HttpParserStringDelegate; 142 public alias void delegate(HttpParser, HttpHeader header) HttpParserHeaderDelegate; 143 144 public class HttpParser { 145 private { 146 147 extern(C) { 148 mixin(http_parser_cb!("on_message_begin")); 149 mixin(http_parser_data_cb!("on_url")); 150 mixin(http_parser_data_cb!("on_status_complete")); 151 mixin(http_parser_data_cb!("on_header_value")); 152 mixin(http_parser_data_cb!("on_header_field")); 153 mixin(http_parser_cb!("on_headers_complete")); 154 mixin(http_parser_data_cb!("on_body")); 155 mixin(http_parser_cb!("on_message_complete")); 156 } 157 158 http_parser* _parser; 159 http_parser_settings _settings; 160 HttpParserType _type; 161 162 // delegates 163 HttpParserDelegate _messageBegin, _messageComplete, _headersComplete; 164 HttpParserBodyChunkDelegate _onBody; 165 HttpParserStringDelegate _onUrl, _statusComplete; 166 HttpParserHeaderDelegate _onHeader; 167 Throwable _lastException; 168 HttpBodyTransmissionMode _transmissionMode; 169 bool _transferEncodingPresent = false; 170 171 __gshared const int CB_OK = 0; 172 __gshared const int CB_ERR = 1; 173 174 /** Begin Counters 175 Countes are reset every time a new message is received. Check _resetCounters. 176 */ 177 int _headerFields; 178 int _headerValues; 179 uint _statusCode; 180 HttpHeader _currentHeader; 181 182 void _resetCounters() { 183 _transferEncodingPresent = false; 184 _headerFields = 0; 185 _headerValues = 0; 186 _resetCurrentHeader(); 187 } 188 189 void _resetCurrentHeader() { 190 destroy(_currentHeader); 191 } 192 /** End Counters **/ 193 } 194 195 196 public { 197 198 this() { 199 this(HttpParserType.REQUEST); 200 } 201 202 this(HttpParserType type) { 203 _type = type; 204 _parser = duv_alloc_http_parser(); 205 duv_set_http_parser_data(_parser, cast(void*)this); 206 http_parser_init(_parser, cast(http_parser_type)type); 207 _settings.on_message_begin = &duv_http_parser_on_message_begin; 208 _settings.on_message_complete = &duv_http_parser_on_message_complete; 209 _settings.on_status_complete = &duv_http_parser_on_status_complete; 210 _settings.on_header_field = &duv_http_parser_on_header_field; 211 _settings.on_header_value = &duv_http_parser_on_header_value; 212 _settings.on_headers_complete = &duv_http_parser_on_headers_complete; 213 _settings.on_body = &duv_http_parser_on_body; 214 _settings.on_url = &duv_http_parser_on_url; 215 } 216 217 size_t execute(string text) { 218 return execute(cast(ubyte[])text); 219 } 220 221 size_t execute(ubyte[] data) { 222 _lastException = null; 223 size_t inputLength = data.length; 224 size_t ret = http_parser_execute(_parser, &_settings, cast(ubyte*)data, inputLength); 225 auto error = duv_http_parser_get_errno(_parser); 226 //writefln("Parsed %d of %d", ret, inputLength); 227 if(_lastException || error || ret != inputLength) { 228 if(_lastException) { 229 throw _lastException; 230 } 231 const(char)* errName = duv_http_errno_name(_parser); 232 const(char)* errDescription = duv_http_errno_description(_parser); 233 string errNameStr = to!string(errName); 234 string errDescStr = to!string(errDescription); 235 throw new HttpParserException(errDescStr, errNameStr); 236 } 237 return ret; 238 } 239 240 @property HttpVersion protocolVersion() { 241 return HttpVersion(duv_http_major(_parser), duv_http_minor(_parser)); 242 } 243 244 @property HttpParserType type() { 245 return _type; 246 } 247 248 @property HttpParserDelegate onMessageBegin() { 249 return _messageBegin; 250 } 251 @property void onMessageBegin(HttpParserDelegate callback) { 252 _messageBegin = callback; 253 } 254 255 @property HttpParserDelegate onMessageComplete() { 256 return _messageComplete; 257 } 258 @property void onMessageComplete(HttpParserDelegate callback) { 259 _messageComplete = callback; 260 } 261 262 @property HttpParserStringDelegate onStatusComplete() { 263 return _statusComplete; 264 } 265 @property void onStatusComplete(HttpParserStringDelegate callback) { 266 _statusComplete = callback; 267 } 268 269 @property HttpParserDelegate onHeadersComplete() { 270 return _headersComplete; 271 } 272 @property void onHeadersComplete(HttpParserDelegate callback) { 273 _headersComplete = callback; 274 } 275 276 @property HttpParserBodyChunkDelegate onBody() { 277 return _onBody; 278 } 279 @property void onBody(HttpParserBodyChunkDelegate callback) { 280 _onBody = callback; 281 } 282 283 @property HttpParserStringDelegate onUrl() { 284 return _onUrl; 285 } 286 @property void onUrl(HttpParserStringDelegate callback) { 287 _onUrl = callback; 288 } 289 290 @property HttpParserHeaderDelegate onHeader() { 291 return _onHeader; 292 } 293 @property void onHeader(HttpParserHeaderDelegate callback) { 294 _onHeader = callback; 295 } 296 297 @property string method() { 298 return std.conv.to!string(duv_http_method_str(_parser)); 299 } 300 301 @property ulong contentLength() { 302 return http_parser_get_content_length(_parser); 303 } 304 @property HttpBodyTransmissionMode transmissionMode() { 305 return _transmissionMode; 306 } 307 308 /* 309 Available on statusComplete 310 */ 311 @property uint statusCode() { 312 return _statusCode; 313 } 314 } 315 316 package { 317 int _on_message_begin() { 318 _resetCounters(); 319 if(this._messageBegin) { 320 try { 321 _messageBegin(this); 322 } catch(Throwable ex) { 323 _lastException = ex; 324 return CB_ERR; 325 } 326 } 327 return CB_OK; 328 } 329 330 int _on_message_complete() { 331 if(this._messageComplete) { 332 try { 333 _messageComplete(this); 334 } catch(Throwable ex) { 335 _lastException = ex; 336 return CB_ERR; 337 } 338 } 339 return CB_OK; 340 } 341 342 343 int _on_status_complete(ubyte[] data) { 344 if(_type == HttpParserType.RESPONSE) { 345 _statusCode = duv_http_status_code(_parser); 346 } 347 if(this._statusComplete) { 348 try { 349 _statusComplete(this, cast(string)data); 350 } catch(Throwable ex) { 351 _lastException = ex; 352 return CB_ERR; 353 } 354 } 355 return CB_OK; 356 } 357 358 int _on_url(ubyte[] data) { 359 if(this._onUrl) { 360 try { 361 _onUrl(this, cast(string)data); 362 } catch(Throwable ex) { 363 _lastException = ex; 364 return CB_ERR; 365 } 366 } 367 return CB_OK; 368 } 369 370 int _on_header_field(ubyte[] data) { 371 if(_currentHeader.hasValue) { 372 int res = _safePublishHeader(); 373 _resetCurrentHeader(); 374 if(res != CB_OK) { 375 return res; 376 } 377 } 378 string text = cast(string)data; 379 _currentHeader._name ~= text; 380 return CB_OK; 381 } 382 383 int _on_header_value(ubyte[] data) { 384 string text = cast(string)data; 385 _currentHeader._value ~= text; 386 return CB_OK; 387 } 388 389 int _safePublishHeader() { 390 try { 391 _publishHeader(); 392 } catch(Throwable ex) { 393 _lastException = ex; 394 return CB_ERR; 395 } 396 return CB_OK; 397 } 398 void _publishHeader() { 399 if(_currentHeader.isEmpty) return; 400 if(_currentHeader.name.indexOf("Transfer-Encoding", CaseSensitive.no) != -1) { 401 _transferEncodingPresent = true; 402 } 403 if(this._onHeader) { 404 this._onHeader(this, _currentHeader); 405 } 406 } 407 408 void _determinateTransmissionMode() { 409 // Follow the order in the RFC http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 410 411 // use transfer encoding if present 412 if(_transferEncodingPresent) { 413 _transmissionMode = HttpBodyTransmissionMode.Chunked; 414 } 415 // otherwise check if there is contentLength (ContentLength: 0 must be interpreted as non-existent) 416 else if(this.contentLength > 0) { 417 _transmissionMode = HttpBodyTransmissionMode.ContentLength; 418 } else { 419 // No HTTP Entity Body 420 _transmissionMode = HttpBodyTransmissionMode.None; 421 } 422 } 423 424 int _on_headers_complete() { 425 try { 426 _publishHeader(); 427 _determinateTransmissionMode(); 428 if(this._headersComplete) { 429 _headersComplete(this); 430 } 431 } catch(Throwable ex) { 432 _lastException = ex; 433 return CB_ERR; 434 } 435 return CB_OK; 436 } 437 438 int _on_body(ubyte[] data) { 439 if(this._onBody) { 440 try { 441 bool isFinal = http_body_is_final(_parser) != 0; 442 auto chunk = HttpBodyChunk(data, isFinal); 443 _onBody(this, chunk); 444 } catch(Throwable ex) { 445 _lastException = ex; 446 return CB_ERR; 447 } 448 } 449 return CB_OK; 450 } 451 } 452 453 ~this() { 454 if(_parser) { 455 duv_free_http_parser(_parser); 456 _parser = null; 457 } 458 } 459 } 460 461 struct Uri { 462 private: 463 string _schema, _host, _path, _query, _fragment, _userInfo; 464 ushort _port; 465 string _absoluteUri; 466 467 void buildAbsoluteUri() { 468 string absolutePort = _port == 0 ? "" : ":" ~ _port.to!string; 469 string absoluteQuery = _query.length == 0 ? "" : "?" ~ _query; 470 string absoluteUserInfo = _userInfo.length == 0 ? "" : _userInfo ~ "@"; 471 _absoluteUri = this.schema ~ "://" ~ absoluteUserInfo ~ _host ~ absolutePort ~ _path ~ absoluteQuery; 472 } 473 474 public: 475 this(in string rawUri, bool isConnect = false) { 476 http_parser_url * url = alloc_http_parser_url(); 477 scope (exit) free_http_parser_url(url); 478 immutable(char) * buff = rawUri.toStringz; 479 int res = http_parser_parse_url(buff, rawUri.length, isConnect ? 1 : 0, url); 480 if(res != 0) { 481 throw new Exception("Failed to parse rawUri " ~ rawUri); 482 } 483 484 auto port = http_parser_get_port(url); 485 auto schema = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_SCHEMA); 486 auto host = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_HOST); 487 auto path = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_PATH); 488 auto query = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_QUERY); 489 auto fragment = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_FRAGMENT); 490 auto userInfo = http_parser_get_field_string(url, rawUri, http_parser_url_fields.UF_USERINFO); 491 492 this(schema, host, port, path, query, fragment, userInfo); 493 } 494 495 this(in string schema, in string host, in ushort port, in string path, in string query, in string fragment, in string userInfo) { 496 _schema = schema; 497 _host = host; 498 _port = port; 499 _path = path; 500 _query = query; 501 _fragment = fragment; 502 _userInfo = userInfo; 503 this.buildAbsoluteUri(); 504 } 505 506 @property { 507 508 string schema() pure nothrow { 509 return _schema; 510 } 511 512 string host() pure nothrow { 513 return _host; 514 } 515 516 ushort port() pure nothrow { 517 return _port; 518 } 519 520 string path() pure nothrow { 521 return _path; 522 } 523 524 string query() pure nothrow { 525 return _query; 526 } 527 528 string fragment() pure nothrow { 529 return _fragment; 530 } 531 532 string userInfo() pure nothrow { 533 return _userInfo; 534 } 535 string absoluteUri() pure nothrow { 536 return _absoluteUri; 537 } 538 } 539 string toString() { 540 return this.absoluteUri; 541 } 542 }