1 // 2 // Copyright (c) ZeroC, Inc. All rights reserved. 3 // 4 5 namespace IceInternal 6 { 7 using System.Diagnostics; 8 using System.Collections.Generic; 9 using System.Text; 10 11 internal sealed class WebSocketException : System.Exception 12 { WebSocketException()13 internal WebSocketException() : 14 base("", null) 15 { 16 } 17 WebSocketException(string message)18 internal WebSocketException(string message) : 19 base(message, null) 20 { 21 } 22 WebSocketException(string message, System.Exception cause)23 internal WebSocketException(string message, System.Exception cause) : 24 base(message, cause) 25 { 26 } 27 WebSocketException(System.Exception cause)28 internal WebSocketException(System.Exception cause) : 29 base("", cause) 30 { 31 } 32 } 33 34 internal sealed class HttpParser 35 { HttpParser()36 internal HttpParser() 37 { 38 _type = Type.Unknown; 39 _versionMajor = 0; 40 _versionMinor = 0; 41 _status = 0; 42 _state = State.Init; 43 } 44 45 internal enum Type 46 { 47 Unknown, 48 Request, 49 Response 50 }; 51 isCompleteMessage(ByteBuffer buf, int begin, int end)52 internal int isCompleteMessage(ByteBuffer buf, int begin, int end) 53 { 54 byte[] raw = buf.rawBytes(); 55 int p = begin; 56 57 // 58 // Skip any leading CR-LF characters. 59 // 60 while(p < end) 61 { 62 byte ch = raw[p]; 63 if(ch != (byte)'\r' && ch != (byte)'\n') 64 { 65 break; 66 } 67 ++p; 68 } 69 70 // 71 // Look for adjacent CR-LF/CR-LF or LF/LF. 72 // 73 bool seenFirst = false; 74 while(p < end) 75 { 76 byte ch = raw[p++]; 77 if(ch == (byte)'\n') 78 { 79 if(seenFirst) 80 { 81 return p; 82 } 83 else 84 { 85 seenFirst = true; 86 } 87 } 88 else if(ch != (byte)'\r') 89 { 90 seenFirst = false; 91 } 92 } 93 94 return -1; 95 } 96 parse(ByteBuffer buf, int begin, int end)97 internal bool parse(ByteBuffer buf, int begin, int end) 98 { 99 byte[] raw = buf.rawBytes(); 100 int p = begin; 101 int start = 0; 102 const char CR = '\r'; 103 const char LF = '\n'; 104 105 if(_state == State.Complete) 106 { 107 _state = State.Init; 108 } 109 110 while(p != end && _state != State.Complete) 111 { 112 char c = (char)raw[p]; 113 114 switch(_state) 115 { 116 case State.Init: 117 { 118 _method = new StringBuilder(); 119 _uri = new StringBuilder(); 120 _versionMajor = -1; 121 _versionMinor = -1; 122 _status = -1; 123 _reason = ""; 124 _headers.Clear(); 125 _state = State.Type; 126 continue; 127 } 128 case State.Type: 129 { 130 if(c == CR || c == LF) 131 { 132 break; 133 } 134 else if(c == 'H') 135 { 136 // 137 // Could be the start of "HTTP/1.1" or "HEAD". 138 // 139 _state = State.TypeCheck; 140 break; 141 } 142 else 143 { 144 _state = State.Request; 145 continue; 146 } 147 } 148 case State.TypeCheck: 149 { 150 if(c == 'T') // Continuing "H_T_TP/1.1" 151 { 152 _state = State.Response; 153 } 154 else if(c == 'E') // Expecting "HEAD" 155 { 156 _state = State.Request; 157 _method.Append('H'); 158 _method.Append('E'); 159 } 160 else 161 { 162 throw new WebSocketException("malformed request or response"); 163 } 164 break; 165 } 166 case State.Request: 167 { 168 _type = Type.Request; 169 _state = State.RequestMethod; 170 continue; 171 } 172 case State.RequestMethod: 173 { 174 if(c == ' ' || c == CR || c == LF) 175 { 176 _state = State.RequestMethodSP; 177 continue; 178 } 179 _method.Append(c); 180 break; 181 } 182 case State.RequestMethodSP: 183 { 184 if(c == ' ') 185 { 186 break; 187 } 188 else if(c == CR || c == LF) 189 { 190 throw new WebSocketException("malformed request"); 191 } 192 _state = State.RequestURI; 193 continue; 194 } 195 case State.RequestURI: 196 { 197 if(c == ' ' || c == CR || c == LF) 198 { 199 _state = State.RequestURISP; 200 continue; 201 } 202 _uri.Append(c); 203 break; 204 } 205 case State.RequestURISP: 206 { 207 if(c == ' ') 208 { 209 break; 210 } 211 else if(c == CR || c == LF) 212 { 213 throw new WebSocketException("malformed request"); 214 } 215 _state = State.Version; 216 continue; 217 } 218 case State.RequestLF: 219 { 220 if(c != LF) 221 { 222 throw new WebSocketException("malformed request"); 223 } 224 _state = State.HeaderFieldStart; 225 break; 226 } 227 case State.HeaderFieldStart: 228 { 229 // 230 // We've already seen a LF to reach this state. 231 // 232 // Another CR or LF indicates the end of the header fields. 233 // 234 if(c == CR) 235 { 236 _state = State.HeaderFieldEndLF; 237 break; 238 } 239 else if(c == LF) 240 { 241 _state = State.Complete; 242 break; 243 } 244 else if(c == ' ') 245 { 246 // 247 // Could be a continuation line. 248 // 249 _state = State.HeaderFieldContStart; 250 break; 251 } 252 253 _state = State.HeaderFieldNameStart; 254 continue; 255 } 256 case State.HeaderFieldContStart: 257 { 258 if(c == ' ') 259 { 260 break; 261 } 262 263 _state = State.HeaderFieldCont; 264 start = p; 265 continue; 266 } 267 case State.HeaderFieldCont: 268 { 269 if(c == CR || c == LF) 270 { 271 if(p > start) 272 { 273 if(_headerName.Length == 0) 274 { 275 throw new WebSocketException("malformed header"); 276 } 277 Debug.Assert(_headers.ContainsKey(_headerName)); 278 string s = _headers[_headerName]; 279 StringBuilder newValue = new StringBuilder(s); 280 newValue.Append(' '); 281 for(int i = start; i < p; ++i) 282 { 283 newValue.Append((char)raw[i]); 284 } 285 _headers[_headerName] = newValue.ToString(); 286 _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart; 287 } 288 else 289 { 290 // 291 // Could mark the end of the header fields. 292 // 293 _state = c == CR ? State.HeaderFieldEndLF : State.Complete; 294 } 295 } 296 297 break; 298 } 299 case State.HeaderFieldNameStart: 300 { 301 Debug.Assert(c != ' '); 302 start = p; 303 _headerName = ""; 304 _state = State.HeaderFieldName; 305 continue; 306 } 307 case State.HeaderFieldName: 308 { 309 if(c == ' ' || c == ':') 310 { 311 _state = State.HeaderFieldNameEnd; 312 continue; 313 } 314 else if(c == CR || c == LF) 315 { 316 throw new WebSocketException("malformed header"); 317 } 318 break; 319 } 320 case State.HeaderFieldNameEnd: 321 { 322 if(_headerName.Length == 0) 323 { 324 StringBuilder str = new StringBuilder(); 325 for(int i = start; i < p; ++i) 326 { 327 str.Append((char)raw[i]); 328 } 329 _headerName = str.ToString().ToLower(); 330 // 331 // Add a placeholder entry if necessary. 332 // 333 if(!_headers.ContainsKey(_headerName)) 334 { 335 _headers[_headerName] = ""; 336 _headerNames[_headerName] = str.ToString(); 337 } 338 } 339 340 if(c == ' ') 341 { 342 break; 343 } 344 else if(c != ':' || p == start) 345 { 346 throw new WebSocketException("malformed header"); 347 } 348 349 _state = State.HeaderFieldValueStart; 350 break; 351 } 352 case State.HeaderFieldValueStart: 353 { 354 if(c == ' ') 355 { 356 break; 357 } 358 359 // 360 // Check for "Name:\r\n" 361 // 362 if(c == CR) 363 { 364 _state = State.HeaderFieldLF; 365 break; 366 } 367 else if(c == LF) 368 { 369 _state = State.HeaderFieldStart; 370 break; 371 } 372 373 start = p; 374 _state = State.HeaderFieldValue; 375 continue; 376 } 377 case State.HeaderFieldValue: 378 { 379 if(c == CR || c == LF) 380 { 381 _state = State.HeaderFieldValueEnd; 382 continue; 383 } 384 break; 385 } 386 case State.HeaderFieldValueEnd: 387 { 388 Debug.Assert(c == CR || c == LF); 389 if(p > start) 390 { 391 StringBuilder str = new StringBuilder(); 392 for(int i = start; i < p; ++i) 393 { 394 str.Append((char)raw[i]); 395 } 396 string s = null; 397 if(!_headers.TryGetValue(_headerName, out s) || s.Length == 0) 398 { 399 _headers[_headerName] = str.ToString(); 400 } 401 else 402 { 403 _headers[_headerName] = s + ", " + str.ToString(); 404 } 405 } 406 407 if(c == CR) 408 { 409 _state = State.HeaderFieldLF; 410 } 411 else 412 { 413 _state = State.HeaderFieldStart; 414 } 415 break; 416 } 417 case State.HeaderFieldLF: 418 { 419 if(c != LF) 420 { 421 throw new WebSocketException("malformed header"); 422 } 423 _state = State.HeaderFieldStart; 424 break; 425 } 426 case State.HeaderFieldEndLF: 427 { 428 if(c != LF) 429 { 430 throw new WebSocketException("malformed header"); 431 } 432 _state = State.Complete; 433 break; 434 } 435 case State.Version: 436 { 437 if(c != 'H') 438 { 439 throw new WebSocketException("malformed version"); 440 } 441 _state = State.VersionH; 442 break; 443 } 444 case State.VersionH: 445 { 446 if(c != 'T') 447 { 448 throw new WebSocketException("malformed version"); 449 } 450 _state = State.VersionHT; 451 break; 452 } 453 case State.VersionHT: 454 { 455 if(c != 'T') 456 { 457 throw new WebSocketException("malformed version"); 458 } 459 _state = State.VersionHTT; 460 break; 461 } 462 case State.VersionHTT: 463 { 464 if(c != 'P') 465 { 466 throw new WebSocketException("malformed version"); 467 } 468 _state = State.VersionHTTP; 469 break; 470 } 471 case State.VersionHTTP: 472 { 473 if(c != '/') 474 { 475 throw new WebSocketException("malformed version"); 476 } 477 _state = State.VersionMajor; 478 break; 479 } 480 case State.VersionMajor: 481 { 482 if(c == '.') 483 { 484 if(_versionMajor == -1) 485 { 486 throw new WebSocketException("malformed version"); 487 } 488 _state = State.VersionMinor; 489 break; 490 } 491 else if(c < '0' || c > '9') 492 { 493 throw new WebSocketException("malformed version"); 494 } 495 if(_versionMajor == -1) 496 { 497 _versionMajor = 0; 498 } 499 _versionMajor *= 10; 500 _versionMajor += (int)(c - '0'); 501 break; 502 } 503 case State.VersionMinor: 504 { 505 if(c == CR) 506 { 507 if(_versionMinor == -1 || _type != Type.Request) 508 { 509 throw new WebSocketException("malformed version"); 510 } 511 _state = State.RequestLF; 512 break; 513 } 514 else if(c == LF) 515 { 516 if(_versionMinor == -1 || _type != Type.Request) 517 { 518 throw new WebSocketException("malformed version"); 519 } 520 _state = State.HeaderFieldStart; 521 break; 522 } 523 else if(c == ' ') 524 { 525 if(_versionMinor == -1 || _type != Type.Response) 526 { 527 throw new WebSocketException("malformed version"); 528 } 529 _state = State.ResponseVersionSP; 530 break; 531 } 532 else if(c < '0' || c > '9') 533 { 534 throw new WebSocketException("malformed version"); 535 } 536 if(_versionMinor == -1) 537 { 538 _versionMinor = 0; 539 } 540 _versionMinor *= 10; 541 _versionMinor += (c - '0'); 542 break; 543 } 544 case State.Response: 545 { 546 _type = Type.Response; 547 _state = State.VersionHT; 548 continue; 549 } 550 case State.ResponseVersionSP: 551 { 552 if(c == ' ') 553 { 554 break; 555 } 556 557 _state = State.ResponseStatus; 558 continue; 559 } 560 case State.ResponseStatus: 561 { 562 // TODO: Is reason string optional? 563 if(c == CR) 564 { 565 if(_status == -1) 566 { 567 throw new WebSocketException("malformed response status"); 568 } 569 _state = State.ResponseLF; 570 break; 571 } 572 else if(c == LF) 573 { 574 if(_status == -1) 575 { 576 throw new WebSocketException("malformed response status"); 577 } 578 _state = State.HeaderFieldStart; 579 break; 580 } 581 else if(c == ' ') 582 { 583 if(_status == -1) 584 { 585 throw new WebSocketException("malformed response status"); 586 } 587 _state = State.ResponseReasonStart; 588 break; 589 } 590 else if(c < '0' || c > '9') 591 { 592 throw new WebSocketException("malformed response status"); 593 } 594 if(_status == -1) 595 { 596 _status = 0; 597 } 598 _status *= 10; 599 _status += (c - '0'); 600 break; 601 } 602 case State.ResponseReasonStart: 603 { 604 // 605 // Skip leading spaces. 606 // 607 if(c == ' ') 608 { 609 break; 610 } 611 612 _state = State.ResponseReason; 613 start = p; 614 continue; 615 } 616 case State.ResponseReason: 617 { 618 if(c == CR || c == LF) 619 { 620 if(p > start) 621 { 622 StringBuilder str = new StringBuilder(); 623 for(int i = start; i < p; ++i) 624 { 625 str.Append((char)raw[i]); 626 } 627 _reason = str.ToString(); 628 } 629 _state = c == CR ? State.ResponseLF : State.HeaderFieldStart; 630 } 631 632 break; 633 } 634 case State.ResponseLF: 635 { 636 if(c != LF) 637 { 638 throw new WebSocketException("malformed status line"); 639 } 640 _state = State.HeaderFieldStart; 641 break; 642 } 643 case State.Complete: 644 { 645 Debug.Assert(false); // Shouldn't reach 646 break; 647 } 648 } 649 650 ++p; 651 } 652 653 return _state == State.Complete; 654 } 655 type()656 internal Type type() 657 { 658 return _type; 659 } 660 method()661 internal string method() 662 { 663 Debug.Assert(_type == Type.Request); 664 return _method.ToString(); 665 } 666 uri()667 internal string uri() 668 { 669 Debug.Assert(_type == Type.Request); 670 return _uri.ToString(); 671 } 672 versionMajor()673 internal int versionMajor() 674 { 675 return _versionMajor; 676 } 677 versionMinor()678 internal int versionMinor() 679 { 680 return _versionMinor; 681 } 682 status()683 internal int status() 684 { 685 return _status; 686 } 687 reason()688 internal string reason() 689 { 690 return _reason; 691 } 692 getHeader(string name, bool toLower)693 internal string getHeader(string name, bool toLower) 694 { 695 string s = null; 696 if(_headers.TryGetValue(name.ToLower(), out s)) 697 { 698 return toLower ? s.Trim().ToLower() : s.Trim(); 699 } 700 701 return null; 702 } 703 getHeaders()704 internal Dictionary<string, string> getHeaders() 705 { 706 Dictionary<string, string> dict = new Dictionary<string, string>(); 707 foreach(KeyValuePair<string, string> e in _headers) 708 { 709 dict[_headerNames[e.Key]] = e.Value.Trim(); 710 } 711 return dict; 712 } 713 714 private Type _type; 715 716 private StringBuilder _method = new StringBuilder(); 717 private StringBuilder _uri = new StringBuilder(); 718 719 private Dictionary<string, string> _headers = new Dictionary<string, string>(); 720 private Dictionary<string, string> _headerNames = new Dictionary<string, string>(); 721 private string _headerName = ""; 722 723 private int _versionMajor; 724 private int _versionMinor; 725 726 private int _status; 727 private string _reason; 728 729 private enum State 730 { 731 Init, 732 Type, 733 TypeCheck, 734 Request, 735 RequestMethod, 736 RequestMethodSP, 737 RequestURI, 738 RequestURISP, 739 RequestLF, 740 HeaderFieldStart, 741 HeaderFieldContStart, 742 HeaderFieldCont, 743 HeaderFieldNameStart, 744 HeaderFieldName, 745 HeaderFieldNameEnd, 746 HeaderFieldValueStart, 747 HeaderFieldValue, 748 HeaderFieldValueEnd, 749 HeaderFieldLF, 750 HeaderFieldEndLF, 751 Version, 752 VersionH, 753 VersionHT, 754 VersionHTT, 755 VersionHTTP, 756 VersionMajor, 757 VersionMinor, 758 Response, 759 ResponseVersionSP, 760 ResponseStatus, 761 ResponseReasonStart, 762 ResponseReason, 763 ResponseLF, 764 Complete 765 }; 766 private State _state; 767 } 768 } 769