< Summary

Information
Class: Ice.SSL.RFC2253
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/SSL/RFC2253.cs
Tag: 71_18251537082
Line coverage
47%
Covered lines: 85
Uncovered lines: 94
Coverable lines: 179
Total lines: 454
Line coverage: 47.4%
Branch coverage
43%
Covered branches: 70
Total branches: 162
Branch coverage: 43.2%
Method coverage
69%
Covered methods: 9
Total methods: 13
Method coverage: 69.2%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
parse(...)90%20.262091.3%
parseStrict(...)60%10.11090%
unescape(...)27.27%134.82238.46%
hexToInt(...)0%156120%
unescapeHex(...)0%620%
parseNameComponent(...)83.33%11.84645.45%
parseAttributeTypeAndValue(...)50%4.13480%
parseAttributeType(...)50%155.473856.67%
parseAttributeValue(...)45.45%158.762234.38%
parsePair(...)0%7280%
parseHexPair(...)0%210140%
eatWhite(...)100%44100%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/SSL/RFC2253.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Diagnostics;
 6using System.Text;
 7
 8//
 9// See RFC 2253 and RFC 1779.
 10//
 11namespace Ice.SSL;
 12
 13internal class RFC2253
 14{
 15    internal struct RDNPair
 16    {
 17        internal string key;
 18        internal string value;
 19    }
 20
 21    internal class RDNEntry
 22    {
 123        internal List<RDNPair> rdn = [];
 24        internal bool negate;
 25    }
 26
 27    internal static List<RDNEntry> parse(string data)
 28    {
 129        List<RDNEntry> results = [];
 130        var current = new RDNEntry();
 131        int pos = 0;
 132        while (pos < data.Length)
 33        {
 134            eatWhite(data, ref pos);
 135            if (pos < data.Length && data[pos] == '!')
 36            {
 137                if (current.rdn.Count > 0)
 38                {
 039                    throw new ParseException("negation symbol '!' must appear at start of list");
 40                }
 141                ++pos;
 142                current.negate = true;
 43            }
 144            current.rdn.Add(parseNameComponent(data, ref pos));
 145            eatWhite(data, ref pos);
 146            if (pos < data.Length && data[pos] == ',')
 47            {
 148                ++pos;
 49            }
 150            else if (pos < data.Length && data[pos] == ';')
 51            {
 152                ++pos;
 153                results.Add(current);
 154                current = new RDNEntry();
 55            }
 156            else if (pos < data.Length)
 57            {
 058                throw new ParseException($"expected ',' or ';' at `{data[pos..]}'");
 59            }
 60        }
 161        if (current.rdn.Count > 0)
 62        {
 163            results.Add(current);
 64        }
 65
 166        return results;
 67    }
 68
 69    internal static List<RDNPair> parseStrict(string data)
 70    {
 171        List<RDNPair> results = [];
 172        int pos = 0;
 173        while (pos < data.Length)
 74        {
 175            results.Add(parseNameComponent(data, ref pos));
 176            eatWhite(data, ref pos);
 177            if (pos < data.Length && (data[pos] == ',' || data[pos] == ';'))
 78            {
 179                ++pos;
 80            }
 181            else if (pos < data.Length)
 82            {
 083                throw new ParseException($"expected ',' or ';' at `{data[pos..]}'");
 84            }
 85        }
 186        return results;
 87    }
 88
 89    public static string unescape(string data)
 90    {
 191        if (data.Length == 0)
 92        {
 093            return data;
 94        }
 95
 196        if (data[0] == '"')
 97        {
 098            if (data[^1] != '"')
 99            {
 0100                throw new ParseException("unescape: missing \"");
 101            }
 102            // Return the string without quotes.
 0103            return data[1..^1];
 104        }
 105
 106        // Unescape the entire string.
 1107        var result = new StringBuilder();
 1108        if (data[0] == '#')
 109        {
 0110            int pos = 1;
 0111            while (pos < data.Length)
 112            {
 0113                result.Append(unescapeHex(data, pos));
 0114                pos += 2;
 115            }
 116        }
 117        else
 118        {
 1119            int pos = 0;
 1120            while (pos < data.Length)
 121            {
 1122                if (data[pos] != '\\')
 123                {
 1124                    result.Append(data[pos]);
 1125                    ++pos;
 126                }
 127                else
 128                {
 0129                    ++pos;
 0130                    if (pos >= data.Length)
 131                    {
 0132                        throw new ParseException("unescape: invalid escape sequence");
 133                    }
 0134                    if (special.Contains(data[pos], StringComparison.Ordinal) || data[pos] != '\\' || data[pos] != '"')
 135                    {
 0136                        result.Append(data[pos]);
 0137                        ++pos;
 138                    }
 139                    else
 140                    {
 0141                        result.Append(unescapeHex(data, pos));
 0142                        pos += 2;
 143                    }
 144                }
 145            }
 146        }
 1147        return result.ToString();
 148    }
 149
 150    private static int hexToInt(char v)
 151    {
 0152        if (v >= '0' && v <= '9')
 153        {
 0154            return v - '0';
 155        }
 0156        if (v >= 'a' && v <= 'f')
 157        {
 0158            return 10 + (v - 'a');
 159        }
 0160        if (v >= 'A' && v <= 'F')
 161        {
 0162            return 10 + (v - 'A');
 163        }
 0164        throw new ParseException("unescape: invalid hex pair");
 165    }
 166
 167    private static char unescapeHex(string data, int pos)
 168    {
 169        Debug.Assert(pos < data.Length);
 0170        if (pos + 2 >= data.Length)
 171        {
 0172            throw new ParseException("unescape: invalid hex pair");
 173        }
 0174        return (char)((hexToInt(data[pos]) * 16) + hexToInt(data[pos + 1]));
 175    }
 176
 177    private static RDNPair parseNameComponent(string data, ref int pos)
 178    {
 1179        RDNPair result = parseAttributeTypeAndValue(data, ref pos);
 1180        while (pos < data.Length)
 181        {
 1182            eatWhite(data, ref pos);
 1183            if (pos < data.Length && data[pos] == '+')
 184            {
 0185                ++pos;
 186            }
 187            else
 188            {
 189                break;
 190            }
 0191            RDNPair p = parseAttributeTypeAndValue(data, ref pos);
 0192            result.value += "+";
 0193            result.value += p.key;
 0194            result.value += '=';
 0195            result.value += p.value;
 196        }
 1197        return result;
 198    }
 199
 200    private static RDNPair parseAttributeTypeAndValue(string data, ref int pos)
 201    {
 1202        var p = new RDNPair();
 1203        p.key = parseAttributeType(data, ref pos);
 1204        eatWhite(data, ref pos);
 1205        if (pos >= data.Length)
 206        {
 0207            throw new ParseException("invalid attribute type/value pair (unexpected end of data)");
 208        }
 1209        if (data[pos] != '=')
 210        {
 0211            throw new ParseException($"invalid attribute type/value pair (missing =). remainder: {data[pos..]}");
 212        }
 1213        ++pos;
 1214        p.value = parseAttributeValue(data, ref pos);
 1215        return p;
 216    }
 217
 218    private static string parseAttributeType(string data, ref int pos)
 219    {
 1220        eatWhite(data, ref pos);
 1221        if (pos >= data.Length)
 222        {
 0223            throw new ParseException("invalid attribute type (expected end of data)");
 224        }
 225
 1226        string result = "";
 227
 228        // RFC 1779.
 229        // <key> ::= 1*( <keychar> ) | "OID." <oid> | "oid." <oid>
 230        // <oid> ::= <digitstring> | <digitstring> "." <oid>
 231        // RFC 2253:
 232        // attributeType = (ALPHA 1*keychar) | oid
 233        // keychar    = ALPHA | DIGIT | "-"
 234        // oid        = 1*DIGIT *("." 1*DIGIT)
 235        //
 236        // In section 4 of RFC 2253 the document says:
 237        // Implementations MUST allow an oid in the attribute type to be prefixed by one of the character strings
 238        // "oid." or "OID.".
 239        //
 240        // Here we must also check for "oid." and "OID." before parsing according to the ALPHA KEYCHAR* rule.
 241        //
 242        // First the OID case.
 1243        if (char.IsDigit(data[pos]) ||
 1244           (data.Length - pos >= 4 && (data.Substring(pos, 4) == "oid." ||
 1245                                                   data.Substring(pos, 4) == "OID.")))
 246        {
 0247            if (!char.IsDigit(data[pos]))
 248            {
 0249                result += data.Substring(pos, 4);
 0250                pos += 4;
 251            }
 252
 253            while (true)
 254            {
 255                // 1*DIGIT
 0256                while (pos < data.Length && char.IsDigit(data[pos]))
 257                {
 0258                    result += data[pos];
 0259                    ++pos;
 260                }
 261                // "." 1*DIGIT
 0262                if (pos < data.Length && data[pos] == '.')
 263                {
 0264                    result += data[pos];
 0265                    ++pos;
 266                    // 1*DIGIT must follow "."
 0267                    if (pos < data.Length && !char.IsDigit(data[pos]))
 268                    {
 0269                        throw new ParseException("invalid attribute type (expected end of data)");
 270                    }
 271                }
 272                else
 273                {
 274                    break;
 275                }
 276            }
 277        }
 1278        else if (char.IsUpper(data[pos]) || char.IsLower(data[pos]))
 279        {
 280            // The grammar is wrong in this case. It should be ALPHA KEYCHAR* otherwise it will not accept "O" as a
 281            // valid attribute type.
 1282            result += data[pos];
 1283            ++pos;
 284            // 1* KEYCHAR
 1285            while (pos < data.Length &&
 1286                  (char.IsDigit(data[pos]) ||
 1287                   char.IsUpper(data[pos]) ||
 1288                   char.IsLower(data[pos]) ||
 1289                   data[pos] == '-'))
 290            {
 1291                result += data[pos];
 1292                ++pos;
 293            }
 294        }
 295        else
 296        {
 0297            throw new ParseException("invalid attribute type");
 298        }
 1299        return result;
 300    }
 301
 302    private static string parseAttributeValue(string data, ref int pos)
 303    {
 1304        eatWhite(data, ref pos);
 1305        if (pos >= data.Length)
 306        {
 0307            return "";
 308        }
 309
 310        // RFC 2253
 311        // # hexstring
 1312        var result = new StringBuilder();
 1313        if (data[pos] == '#')
 314        {
 0315            result.Append(data[pos]);
 0316            ++pos;
 0317            while (true)
 318            {
 0319                string h = parseHexPair(data, ref pos, true);
 0320                if (h.Length == 0)
 321                {
 322                    break;
 323                }
 0324                result.Append(h);
 325            }
 326        }
 327
 328        // RFC 2253
 329        // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2
 330        // quotechar     = <any character except "\" or QUOTATION >
 1331        else if (data[pos] == '"')
 332        {
 0333            result.Append(data[pos]);
 0334            ++pos;
 0335            while (true)
 336            {
 0337                if (pos >= data.Length)
 338                {
 0339                    throw new ParseException("invalid attribute value (unexpected end of data)");
 340                }
 341                // final terminating "
 0342                if (data[pos] == '"')
 343                {
 0344                    result.Append(data[pos]);
 0345                    ++pos;
 0346                    break;
 347                }
 348                // any character except '\'
 0349                else if (data[pos] != '\\')
 350                {
 0351                    result.Append(data[pos]);
 0352                    ++pos;
 353                }
 354                // pair '\'
 355                else
 356                {
 0357                    result.Append(parsePair(data, ref pos));
 358                }
 359            }
 360        }
 361        //
 362        // RFC 2253
 363        // * (stringchar | pair)
 364        // stringchar = <any character except one of special, "\" or QUOTATION >
 365        //
 366        else
 367        {
 1368            while (pos < data.Length)
 369            {
 1370                if (data[pos] == '\\')
 371                {
 0372                    result.Append(parsePair(data, ref pos));
 373                }
 1374                else if (!special.Contains(data[pos], StringComparison.Ordinal) && data[pos] != '"')
 375                {
 1376                    result.Append(data[pos]);
 1377                    ++pos;
 378                }
 379                else
 380                {
 381                    break;
 382                }
 383            }
 384        }
 1385        return result.ToString();
 386    }
 387
 388    // RFC2253:
 389    // pair       = "\" ( special | "\" | QUOTATION | hexpair )
 390    private static string parsePair(string data, ref int pos)
 391    {
 0392        string result = "";
 393
 394        Debug.Assert(data[pos] == '\\');
 0395        result += data[pos];
 0396        ++pos;
 397
 0398        if (pos >= data.Length)
 399        {
 0400            throw new ParseException("invalid escape format (unexpected end of data)");
 401        }
 402
 0403        if (special.Contains(data[pos], StringComparison.Ordinal) || data[pos] != '\\' ||
 0404           data[pos] != '"')
 405        {
 0406            result += data[pos];
 0407            ++pos;
 0408            return result;
 409        }
 0410        return parseHexPair(data, ref pos, false);
 411    }
 412
 413    // RFC 2253
 414    // hexpair    = hexchar hexchar
 415    private static string parseHexPair(string data, ref int pos, bool allowEmpty)
 416    {
 0417        string result = "";
 0418        if (pos < data.Length && hexvalid.Contains(data[pos], StringComparison.Ordinal))
 419        {
 0420            result += data[pos];
 0421            ++pos;
 422        }
 0423        if (pos < data.Length && hexvalid.Contains(data[pos], StringComparison.Ordinal))
 424        {
 0425            result += data[pos];
 0426            ++pos;
 427        }
 0428        if (result.Length != 2)
 429        {
 0430            if (allowEmpty && result.Length == 0)
 431            {
 0432                return result;
 433            }
 0434            throw new ParseException("invalid hex format");
 435        }
 0436        return result;
 437    }
 438
 439    // RFC 2253:
 440    //
 441    // Implementations MUST allow for space (' ' ASCII 32) characters to be present between name-component and ',',
 442    // between attributeTypeAndValue and '+', between attributeType and '=', and between '=' and attributeValue.
 443    // These space characters are ignored when parsing.
 444    private static void eatWhite(string data, ref int pos)
 445    {
 1446        while (pos < data.Length && data[pos] == ' ')
 447        {
 1448            ++pos;
 449        }
 1450    }
 451
 452    private const string special = ",=+<>#;";
 453    private const string hexvalid = "0123456789abcdefABCDEF";
 454}