< Summary

Information
Class: Ice.Internal.WebSocketException
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/HttpParser.cs
Tag: 71_18251537082
Line coverage
0%
Covered lines: 0
Uncovered lines: 2
Coverable lines: 2
Total lines: 733
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
0%
Covered methods: 0
Total methods: 1
Method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/Internal/HttpParser.cs

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

Methods/Properties

.ctor(string)