< Summary

Information
Class: Ice.Internal.HttpParser
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/HttpParser.cs
Tag: 71_18251537082
Line coverage
75%
Covered lines: 212
Uncovered lines: 68
Coverable lines: 280
Total lines: 733
Line coverage: 75.7%
Branch coverage
70%
Covered branches: 170
Total branches: 241
Branch coverage: 70.5%
Method coverage
75%
Covered methods: 9
Total methods: 12
Method coverage: 75%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
isCompleteMessage(...)85.71%14.051493.75%
parse(...)69.23%1126.8322173.53%
type()100%210%
method()100%210%
uri()100%11100%
versionMajor()100%11100%
versionMinor()100%11100%
status()100%11100%
reason()100%210%
getHeader(...)75%4.59466.67%
getHeaders()100%22100%

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)
 11        : base(message, null)
 12    {
 13    }
 14}
 15
 16internal sealed class HttpParser
 17{
 118    internal HttpParser()
 19    {
 120        _type = Type.Unknown;
 121        _versionMajor = 0;
 122        _versionMinor = 0;
 123        _status = 0;
 124        _state = State.Init;
 125    }
 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    {
 136        byte[] raw = buf.rawBytes();
 137        int p = begin;
 38
 39        //
 40        // Skip any leading CR-LF characters.
 41        //
 142        while (p < end)
 43        {
 144            byte ch = raw[p];
 145            if (ch != (byte)'\r' && ch != (byte)'\n')
 46            {
 47                break;
 48            }
 049            ++p;
 50        }
 51
 52        //
 53        // Look for adjacent CR-LF/CR-LF or LF/LF.
 54        //
 155        bool seenFirst = false;
 156        while (p < end)
 57        {
 158            byte ch = raw[p++];
 159            if (ch == (byte)'\n')
 60            {
 161                if (seenFirst)
 62                {
 163                    return p;
 64                }
 65                else
 66                {
 167                    seenFirst = true;
 68                }
 69            }
 170            else if (ch != (byte)'\r')
 71            {
 172                seenFirst = false;
 73            }
 74        }
 75
 176        return -1;
 77    }
 78
 79    internal bool parse(ByteBuffer buf, int begin, int end)
 80    {
 181        byte[] raw = buf.rawBytes();
 182        int p = begin;
 183        int start = 0;
 84        const char CR = '\r';
 85        const char LF = '\n';
 86
 187        if (_state == State.Complete)
 88        {
 089            _state = State.Init;
 90        }
 91
 192        while (p != end && _state != State.Complete)
 93        {
 194            char c = (char)raw[p];
 95
 196            switch (_state)
 97            {
 98                case State.Init:
 99                {
 1100                    _method = new StringBuilder();
 1101                    _uri = new StringBuilder();
 1102                    _versionMajor = -1;
 1103                    _versionMinor = -1;
 1104                    _status = -1;
 1105                    _reason = "";
 1106                    _headers.Clear();
 1107                    _state = State.Type;
 1108                    continue;
 109                }
 110                case State.Type:
 111                {
 1112                    if (c == CR || c == LF)
 113                    {
 114                        break;
 115                    }
 1116                    else if (c == 'H')
 117                    {
 118                        //
 119                        // Could be the start of "HTTP/1.1" or "HEAD".
 120                        //
 1121                        _state = State.TypeCheck;
 1122                        break;
 123                    }
 124                    else
 125                    {
 1126                        _state = State.Request;
 1127                        continue;
 128                    }
 129                }
 130                case State.TypeCheck:
 131                {
 1132                    if (c == 'T') // Continuing "H_T_TP/1.1"
 133                    {
 1134                        _state = State.Response;
 135                    }
 0136                    else if (c == 'E') // Expecting "HEAD"
 137                    {
 0138                        _state = State.Request;
 0139                        _method.Append('H');
 0140                        _method.Append('E');
 141                    }
 142                    else
 143                    {
 0144                        throw new WebSocketException("malformed request or response");
 145                    }
 146                    break;
 147                }
 148                case State.Request:
 149                {
 1150                    _type = Type.Request;
 1151                    _state = State.RequestMethod;
 1152                    continue;
 153                }
 154                case State.RequestMethod:
 155                {
 1156                    if (c == ' ' || c == CR || c == LF)
 157                    {
 1158                        _state = State.RequestMethodSP;
 1159                        continue;
 160                    }
 1161                    _method.Append(c);
 1162                    break;
 163                }
 164                case State.RequestMethodSP:
 165                {
 1166                    if (c == ' ')
 167                    {
 168                        break;
 169                    }
 1170                    else if (c == CR || c == LF)
 171                    {
 0172                        throw new WebSocketException("malformed request");
 173                    }
 1174                    _state = State.RequestURI;
 1175                    continue;
 176                }
 177                case State.RequestURI:
 178                {
 1179                    if (c == ' ' || c == CR || c == LF)
 180                    {
 1181                        _state = State.RequestURISP;
 1182                        continue;
 183                    }
 1184                    _uri.Append(c);
 1185                    break;
 186                }
 187                case State.RequestURISP:
 188                {
 1189                    if (c == ' ')
 190                    {
 191                        break;
 192                    }
 1193                    else if (c == CR || c == LF)
 194                    {
 0195                        throw new WebSocketException("malformed request");
 196                    }
 1197                    _state = State.Version;
 1198                    continue;
 199                }
 200                case State.RequestLF:
 201                {
 1202                    if (c != LF)
 203                    {
 0204                        throw new WebSocketException("malformed request");
 205                    }
 1206                    _state = State.HeaderFieldStart;
 1207                    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                    //
 1216                    if (c == CR)
 217                    {
 1218                        _state = State.HeaderFieldEndLF;
 1219                        break;
 220                    }
 1221                    else if (c == LF)
 222                    {
 0223                        _state = State.Complete;
 0224                        break;
 225                    }
 1226                    else if (c == ' ')
 227                    {
 228                        //
 229                        // Could be a continuation line.
 230                        //
 0231                        _state = State.HeaderFieldContStart;
 0232                        break;
 233                    }
 234
 1235                    _state = State.HeaderFieldNameStart;
 1236                    continue;
 237                }
 238                case State.HeaderFieldContStart:
 239                {
 0240                    if (c == ' ')
 241                    {
 242                        break;
 243                    }
 244
 0245                    _state = State.HeaderFieldCont;
 0246                    start = p;
 0247                    continue;
 248                }
 249                case State.HeaderFieldCont:
 250                {
 0251                    if (c == CR || c == LF)
 252                    {
 0253                        if (p > start)
 254                        {
 0255                            if (_headerName.Length == 0)
 256                            {
 0257                                throw new WebSocketException("malformed header");
 258                            }
 259                            Debug.Assert(_headers.ContainsKey(_headerName));
 0260                            string s = _headers[_headerName];
 0261                            var newValue = new StringBuilder(s);
 0262                            newValue.Append(' ');
 0263                            for (int i = start; i < p; ++i)
 264                            {
 0265                                newValue.Append((char)raw[i]);
 266                            }
 0267                            _headers[_headerName] = newValue.ToString();
 0268                            _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart;
 269                        }
 270                        else
 271                        {
 272                            //
 273                            // Could mark the end of the header fields.
 274                            //
 0275                            _state = c == CR ? State.HeaderFieldEndLF : State.Complete;
 276                        }
 277                    }
 278
 0279                    break;
 280                }
 281                case State.HeaderFieldNameStart:
 282                {
 283                    Debug.Assert(c != ' ');
 1284                    start = p;
 1285                    _headerName = "";
 1286                    _state = State.HeaderFieldName;
 1287                    continue;
 288                }
 289                case State.HeaderFieldName:
 290                {
 1291                    if (c == ' ' || c == ':')
 292                    {
 1293                        _state = State.HeaderFieldNameEnd;
 1294                        continue;
 295                    }
 1296                    else if (c == CR || c == LF)
 297                    {
 0298                        throw new WebSocketException("malformed header");
 299                    }
 300                    break;
 301                }
 302                case State.HeaderFieldNameEnd:
 303                {
 1304                    if (_headerName.Length == 0)
 305                    {
 1306                        var str = new StringBuilder();
 1307                        for (int i = start; i < p; ++i)
 308                        {
 1309                            str.Append((char)raw[i]);
 310                        }
 1311                        _headerName = str.ToString().ToLowerInvariant();
 312                        //
 313                        // Add a placeholder entry if necessary.
 314                        //
 1315                        if (!_headers.ContainsKey(_headerName))
 316                        {
 1317                            _headers[_headerName] = "";
 1318                            _headerNames[_headerName] = str.ToString();
 319                        }
 320                    }
 321
 1322                    if (c == ' ')
 323                    {
 324                        break;
 325                    }
 1326                    else if (c != ':' || p == start)
 327                    {
 0328                        throw new WebSocketException("malformed header");
 329                    }
 330
 1331                    _state = State.HeaderFieldValueStart;
 1332                    break;
 333                }
 334                case State.HeaderFieldValueStart:
 335                {
 1336                    if (c == ' ')
 337                    {
 338                        break;
 339                    }
 340
 341                    //
 342                    // Check for "Name:\r\n"
 343                    //
 1344                    if (c == CR)
 345                    {
 0346                        _state = State.HeaderFieldLF;
 0347                        break;
 348                    }
 1349                    else if (c == LF)
 350                    {
 0351                        _state = State.HeaderFieldStart;
 0352                        break;
 353                    }
 354
 1355                    start = p;
 1356                    _state = State.HeaderFieldValue;
 1357                    continue;
 358                }
 359                case State.HeaderFieldValue:
 360                {
 1361                    if (c == CR || c == LF)
 362                    {
 1363                        _state = State.HeaderFieldValueEnd;
 1364                        continue;
 365                    }
 366                    break;
 367                }
 368                case State.HeaderFieldValueEnd:
 369                {
 370                    Debug.Assert(c == CR || c == LF);
 1371                    if (p > start)
 372                    {
 1373                        var str = new StringBuilder();
 1374                        for (int i = start; i < p; ++i)
 375                        {
 1376                            str.Append((char)raw[i]);
 377                        }
 1378                        if (!_headers.TryGetValue(_headerName, out string s) || s.Length == 0)
 379                        {
 1380                            _headers[_headerName] = str.ToString();
 381                        }
 382                        else
 383                        {
 0384                            _headers[_headerName] = s + ", " + str.ToString();
 385                        }
 386                    }
 387
 1388                    if (c == CR)
 389                    {
 1390                        _state = State.HeaderFieldLF;
 391                    }
 392                    else
 393                    {
 0394                        _state = State.HeaderFieldStart;
 395                    }
 0396                    break;
 397                }
 398                case State.HeaderFieldLF:
 399                {
 1400                    if (c != LF)
 401                    {
 0402                        throw new WebSocketException("malformed header");
 403                    }
 1404                    _state = State.HeaderFieldStart;
 1405                    break;
 406                }
 407                case State.HeaderFieldEndLF:
 408                {
 1409                    if (c != LF)
 410                    {
 0411                        throw new WebSocketException("malformed header");
 412                    }
 1413                    _state = State.Complete;
 1414                    break;
 415                }
 416                case State.Version:
 417                {
 1418                    if (c != 'H')
 419                    {
 0420                        throw new WebSocketException("malformed version");
 421                    }
 1422                    _state = State.VersionH;
 1423                    break;
 424                }
 425                case State.VersionH:
 426                {
 1427                    if (c != 'T')
 428                    {
 0429                        throw new WebSocketException("malformed version");
 430                    }
 1431                    _state = State.VersionHT;
 1432                    break;
 433                }
 434                case State.VersionHT:
 435                {
 1436                    if (c != 'T')
 437                    {
 0438                        throw new WebSocketException("malformed version");
 439                    }
 1440                    _state = State.VersionHTT;
 1441                    break;
 442                }
 443                case State.VersionHTT:
 444                {
 1445                    if (c != 'P')
 446                    {
 0447                        throw new WebSocketException("malformed version");
 448                    }
 1449                    _state = State.VersionHTTP;
 1450                    break;
 451                }
 452                case State.VersionHTTP:
 453                {
 1454                    if (c != '/')
 455                    {
 0456                        throw new WebSocketException("malformed version");
 457                    }
 1458                    _state = State.VersionMajor;
 1459                    break;
 460                }
 461                case State.VersionMajor:
 462                {
 1463                    if (c == '.')
 464                    {
 1465                        if (_versionMajor == -1)
 466                        {
 0467                            throw new WebSocketException("malformed version");
 468                        }
 1469                        _state = State.VersionMinor;
 1470                        break;
 471                    }
 1472                    else if (c < '0' || c > '9')
 473                    {
 0474                        throw new WebSocketException("malformed version");
 475                    }
 1476                    if (_versionMajor == -1)
 477                    {
 1478                        _versionMajor = 0;
 479                    }
 1480                    _versionMajor *= 10;
 1481                    _versionMajor += (int)(c - '0');
 1482                    break;
 483                }
 484                case State.VersionMinor:
 485                {
 1486                    if (c == CR)
 487                    {
 1488                        if (_versionMinor == -1 || _type != Type.Request)
 489                        {
 0490                            throw new WebSocketException("malformed version");
 491                        }
 1492                        _state = State.RequestLF;
 1493                        break;
 494                    }
 1495                    else if (c == LF)
 496                    {
 0497                        if (_versionMinor == -1 || _type != Type.Request)
 498                        {
 0499                            throw new WebSocketException("malformed version");
 500                        }
 0501                        _state = State.HeaderFieldStart;
 0502                        break;
 503                    }
 1504                    else if (c == ' ')
 505                    {
 1506                        if (_versionMinor == -1 || _type != Type.Response)
 507                        {
 0508                            throw new WebSocketException("malformed version");
 509                        }
 1510                        _state = State.ResponseVersionSP;
 1511                        break;
 512                    }
 1513                    else if (c < '0' || c > '9')
 514                    {
 0515                        throw new WebSocketException("malformed version");
 516                    }
 1517                    if (_versionMinor == -1)
 518                    {
 1519                        _versionMinor = 0;
 520                    }
 1521                    _versionMinor *= 10;
 1522                    _versionMinor += c - '0';
 1523                    break;
 524                }
 525                case State.Response:
 526                {
 1527                    _type = Type.Response;
 1528                    _state = State.VersionHT;
 1529                    continue;
 530                }
 531                case State.ResponseVersionSP:
 532                {
 1533                    if (c == ' ')
 534                    {
 535                        break;
 536                    }
 537
 1538                    _state = State.ResponseStatus;
 1539                    continue;
 540                }
 541                case State.ResponseStatus:
 542                {
 543                    // TODO: Is reason string optional?
 1544                    if (c == CR)
 545                    {
 1546                        if (_status == -1)
 547                        {
 0548                            throw new WebSocketException("malformed response status");
 549                        }
 1550                        _state = State.ResponseLF;
 1551                        break;
 552                    }
 1553                    else if (c == LF)
 554                    {
 0555                        if (_status == -1)
 556                        {
 0557                            throw new WebSocketException("malformed response status");
 558                        }
 0559                        _state = State.HeaderFieldStart;
 0560                        break;
 561                    }
 1562                    else if (c == ' ')
 563                    {
 1564                        if (_status == -1)
 565                        {
 0566                            throw new WebSocketException("malformed response status");
 567                        }
 1568                        _state = State.ResponseReasonStart;
 1569                        break;
 570                    }
 1571                    else if (c < '0' || c > '9')
 572                    {
 0573                        throw new WebSocketException("malformed response status");
 574                    }
 1575                    if (_status == -1)
 576                    {
 1577                        _status = 0;
 578                    }
 1579                    _status *= 10;
 1580                    _status += c - '0';
 1581                    break;
 582                }
 583                case State.ResponseReasonStart:
 584                {
 585                    //
 586                    // Skip leading spaces.
 587                    //
 1588                    if (c == ' ')
 589                    {
 590                        break;
 591                    }
 592
 1593                    _state = State.ResponseReason;
 1594                    start = p;
 1595                    continue;
 596                }
 597                case State.ResponseReason:
 598                {
 1599                    if (c == CR || c == LF)
 600                    {
 1601                        if (p > start)
 602                        {
 1603                            var str = new StringBuilder();
 1604                            for (int i = start; i < p; ++i)
 605                            {
 1606                                str.Append((char)raw[i]);
 607                            }
 1608                            _reason = str.ToString();
 609                        }
 1610                        _state = c == CR ? State.ResponseLF : State.HeaderFieldStart;
 611                    }
 612
 1613                    break;
 614                }
 615                case State.ResponseLF:
 616                {
 1617                    if (c != LF)
 618                    {
 0619                        throw new WebSocketException("malformed status line");
 620                    }
 1621                    _state = State.HeaderFieldStart;
 622                    break;
 623                }
 624                case State.Complete:
 625                {
 626                    Debug.Assert(false); // Shouldn't reach
 627                    break;
 628                }
 629            }
 630
 1631            ++p;
 632        }
 633
 1634        return _state == State.Complete;
 635    }
 636
 0637    internal Type type() => _type;
 638
 639    internal string method()
 640    {
 641        Debug.Assert(_type == Type.Request);
 0642        return _method.ToString();
 643    }
 644
 645    internal string uri()
 646    {
 647        Debug.Assert(_type == Type.Request);
 1648        return _uri.ToString();
 649    }
 650
 1651    internal int versionMajor() => _versionMajor;
 652
 1653    internal int versionMinor() => _versionMinor;
 654
 1655    internal int status() => _status;
 656
 0657    internal string reason() => _reason;
 658
 659    internal string getHeader(string name, bool toLower)
 660    {
 1661        if (_headers.TryGetValue(name.ToLowerInvariant(), out string s))
 662        {
 1663            return toLower ? s.Trim().ToLowerInvariant() : s.Trim();
 664        }
 665
 0666        return null;
 667    }
 668
 669    internal Dictionary<string, string> getHeaders()
 670    {
 1671        var dict = new Dictionary<string, string>();
 1672        foreach (KeyValuePair<string, string> e in _headers)
 673        {
 1674            dict[_headerNames[e.Key]] = e.Value.Trim();
 675        }
 1676        return dict;
 677    }
 678
 679    private Type _type;
 680
 1681    private StringBuilder _method = new StringBuilder();
 1682    private StringBuilder _uri = new StringBuilder();
 683
 1684    private readonly Dictionary<string, string> _headers = new Dictionary<string, string>();
 1685    private readonly Dictionary<string, string> _headerNames = new Dictionary<string, string>();
 1686    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}