< Summary

Information
Class: Ice.SSL.RFC2253.RDNEntry
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/SSL/RFC2253.cs
Tag: 71_18251537082
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 454
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 1
Total methods: 1
Method coverage: 100%

Metrics

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

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    {
 29        List<RDNEntry> results = [];
 30        var current = new RDNEntry();
 31        int pos = 0;
 32        while (pos < data.Length)
 33        {
 34            eatWhite(data, ref pos);
 35            if (pos < data.Length && data[pos] == '!')
 36            {
 37                if (current.rdn.Count > 0)
 38                {
 39                    throw new ParseException("negation symbol '!' must appear at start of list");
 40                }
 41                ++pos;
 42                current.negate = true;
 43            }
 44            current.rdn.Add(parseNameComponent(data, ref pos));
 45            eatWhite(data, ref pos);
 46            if (pos < data.Length && data[pos] == ',')
 47            {
 48                ++pos;
 49            }
 50            else if (pos < data.Length && data[pos] == ';')
 51            {
 52                ++pos;
 53                results.Add(current);
 54                current = new RDNEntry();
 55            }
 56            else if (pos < data.Length)
 57            {
 58                throw new ParseException($"expected ',' or ';' at `{data[pos..]}'");
 59            }
 60        }
 61        if (current.rdn.Count > 0)
 62        {
 63            results.Add(current);
 64        }
 65
 66        return results;
 67    }
 68
 69    internal static List<RDNPair> parseStrict(string data)
 70    {
 71        List<RDNPair> results = [];
 72        int pos = 0;
 73        while (pos < data.Length)
 74        {
 75            results.Add(parseNameComponent(data, ref pos));
 76            eatWhite(data, ref pos);
 77            if (pos < data.Length && (data[pos] == ',' || data[pos] == ';'))
 78            {
 79                ++pos;
 80            }
 81            else if (pos < data.Length)
 82            {
 83                throw new ParseException($"expected ',' or ';' at `{data[pos..]}'");
 84            }
 85        }
 86        return results;
 87    }
 88
 89    public static string unescape(string data)
 90    {
 91        if (data.Length == 0)
 92        {
 93            return data;
 94        }
 95
 96        if (data[0] == '"')
 97        {
 98            if (data[^1] != '"')
 99            {
 100                throw new ParseException("unescape: missing \"");
 101            }
 102            // Return the string without quotes.
 103            return data[1..^1];
 104        }
 105
 106        // Unescape the entire string.
 107        var result = new StringBuilder();
 108        if (data[0] == '#')
 109        {
 110            int pos = 1;
 111            while (pos < data.Length)
 112            {
 113                result.Append(unescapeHex(data, pos));
 114                pos += 2;
 115            }
 116        }
 117        else
 118        {
 119            int pos = 0;
 120            while (pos < data.Length)
 121            {
 122                if (data[pos] != '\\')
 123                {
 124                    result.Append(data[pos]);
 125                    ++pos;
 126                }
 127                else
 128                {
 129                    ++pos;
 130                    if (pos >= data.Length)
 131                    {
 132                        throw new ParseException("unescape: invalid escape sequence");
 133                    }
 134                    if (special.Contains(data[pos], StringComparison.Ordinal) || data[pos] != '\\' || data[pos] != '"')
 135                    {
 136                        result.Append(data[pos]);
 137                        ++pos;
 138                    }
 139                    else
 140                    {
 141                        result.Append(unescapeHex(data, pos));
 142                        pos += 2;
 143                    }
 144                }
 145            }
 146        }
 147        return result.ToString();
 148    }
 149
 150    private static int hexToInt(char v)
 151    {
 152        if (v >= '0' && v <= '9')
 153        {
 154            return v - '0';
 155        }
 156        if (v >= 'a' && v <= 'f')
 157        {
 158            return 10 + (v - 'a');
 159        }
 160        if (v >= 'A' && v <= 'F')
 161        {
 162            return 10 + (v - 'A');
 163        }
 164        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);
 170        if (pos + 2 >= data.Length)
 171        {
 172            throw new ParseException("unescape: invalid hex pair");
 173        }
 174        return (char)((hexToInt(data[pos]) * 16) + hexToInt(data[pos + 1]));
 175    }
 176
 177    private static RDNPair parseNameComponent(string data, ref int pos)
 178    {
 179        RDNPair result = parseAttributeTypeAndValue(data, ref pos);
 180        while (pos < data.Length)
 181        {
 182            eatWhite(data, ref pos);
 183            if (pos < data.Length && data[pos] == '+')
 184            {
 185                ++pos;
 186            }
 187            else
 188            {
 189                break;
 190            }
 191            RDNPair p = parseAttributeTypeAndValue(data, ref pos);
 192            result.value += "+";
 193            result.value += p.key;
 194            result.value += '=';
 195            result.value += p.value;
 196        }
 197        return result;
 198    }
 199
 200    private static RDNPair parseAttributeTypeAndValue(string data, ref int pos)
 201    {
 202        var p = new RDNPair();
 203        p.key = parseAttributeType(data, ref pos);
 204        eatWhite(data, ref pos);
 205        if (pos >= data.Length)
 206        {
 207            throw new ParseException("invalid attribute type/value pair (unexpected end of data)");
 208        }
 209        if (data[pos] != '=')
 210        {
 211            throw new ParseException($"invalid attribute type/value pair (missing =). remainder: {data[pos..]}");
 212        }
 213        ++pos;
 214        p.value = parseAttributeValue(data, ref pos);
 215        return p;
 216    }
 217
 218    private static string parseAttributeType(string data, ref int pos)
 219    {
 220        eatWhite(data, ref pos);
 221        if (pos >= data.Length)
 222        {
 223            throw new ParseException("invalid attribute type (expected end of data)");
 224        }
 225
 226        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.
 243        if (char.IsDigit(data[pos]) ||
 244           (data.Length - pos >= 4 && (data.Substring(pos, 4) == "oid." ||
 245                                                   data.Substring(pos, 4) == "OID.")))
 246        {
 247            if (!char.IsDigit(data[pos]))
 248            {
 249                result += data.Substring(pos, 4);
 250                pos += 4;
 251            }
 252
 253            while (true)
 254            {
 255                // 1*DIGIT
 256                while (pos < data.Length && char.IsDigit(data[pos]))
 257                {
 258                    result += data[pos];
 259                    ++pos;
 260                }
 261                // "." 1*DIGIT
 262                if (pos < data.Length && data[pos] == '.')
 263                {
 264                    result += data[pos];
 265                    ++pos;
 266                    // 1*DIGIT must follow "."
 267                    if (pos < data.Length && !char.IsDigit(data[pos]))
 268                    {
 269                        throw new ParseException("invalid attribute type (expected end of data)");
 270                    }
 271                }
 272                else
 273                {
 274                    break;
 275                }
 276            }
 277        }
 278        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.
 282            result += data[pos];
 283            ++pos;
 284            // 1* KEYCHAR
 285            while (pos < data.Length &&
 286                  (char.IsDigit(data[pos]) ||
 287                   char.IsUpper(data[pos]) ||
 288                   char.IsLower(data[pos]) ||
 289                   data[pos] == '-'))
 290            {
 291                result += data[pos];
 292                ++pos;
 293            }
 294        }
 295        else
 296        {
 297            throw new ParseException("invalid attribute type");
 298        }
 299        return result;
 300    }
 301
 302    private static string parseAttributeValue(string data, ref int pos)
 303    {
 304        eatWhite(data, ref pos);
 305        if (pos >= data.Length)
 306        {
 307            return "";
 308        }
 309
 310        // RFC 2253
 311        // # hexstring
 312        var result = new StringBuilder();
 313        if (data[pos] == '#')
 314        {
 315            result.Append(data[pos]);
 316            ++pos;
 317            while (true)
 318            {
 319                string h = parseHexPair(data, ref pos, true);
 320                if (h.Length == 0)
 321                {
 322                    break;
 323                }
 324                result.Append(h);
 325            }
 326        }
 327
 328        // RFC 2253
 329        // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2
 330        // quotechar     = <any character except "\" or QUOTATION >
 331        else if (data[pos] == '"')
 332        {
 333            result.Append(data[pos]);
 334            ++pos;
 335            while (true)
 336            {
 337                if (pos >= data.Length)
 338                {
 339                    throw new ParseException("invalid attribute value (unexpected end of data)");
 340                }
 341                // final terminating "
 342                if (data[pos] == '"')
 343                {
 344                    result.Append(data[pos]);
 345                    ++pos;
 346                    break;
 347                }
 348                // any character except '\'
 349                else if (data[pos] != '\\')
 350                {
 351                    result.Append(data[pos]);
 352                    ++pos;
 353                }
 354                // pair '\'
 355                else
 356                {
 357                    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        {
 368            while (pos < data.Length)
 369            {
 370                if (data[pos] == '\\')
 371                {
 372                    result.Append(parsePair(data, ref pos));
 373                }
 374                else if (!special.Contains(data[pos], StringComparison.Ordinal) && data[pos] != '"')
 375                {
 376                    result.Append(data[pos]);
 377                    ++pos;
 378                }
 379                else
 380                {
 381                    break;
 382                }
 383            }
 384        }
 385        return result.ToString();
 386    }
 387
 388    // RFC2253:
 389    // pair       = "\" ( special | "\" | QUOTATION | hexpair )
 390    private static string parsePair(string data, ref int pos)
 391    {
 392        string result = "";
 393
 394        Debug.Assert(data[pos] == '\\');
 395        result += data[pos];
 396        ++pos;
 397
 398        if (pos >= data.Length)
 399        {
 400            throw new ParseException("invalid escape format (unexpected end of data)");
 401        }
 402
 403        if (special.Contains(data[pos], StringComparison.Ordinal) || data[pos] != '\\' ||
 404           data[pos] != '"')
 405        {
 406            result += data[pos];
 407            ++pos;
 408            return result;
 409        }
 410        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    {
 417        string result = "";
 418        if (pos < data.Length && hexvalid.Contains(data[pos], StringComparison.Ordinal))
 419        {
 420            result += data[pos];
 421            ++pos;
 422        }
 423        if (pos < data.Length && hexvalid.Contains(data[pos], StringComparison.Ordinal))
 424        {
 425            result += data[pos];
 426            ++pos;
 427        }
 428        if (result.Length != 2)
 429        {
 430            if (allowEmpty && result.Length == 0)
 431            {
 432                return result;
 433            }
 434            throw new ParseException("invalid hex format");
 435        }
 436        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    {
 446        while (pos < data.Length && data[pos] == ' ')
 447        {
 448            ++pos;
 449        }
 450    }
 451
 452    private const string special = ",=+<>#;";
 453    private const string hexvalid = "0123456789abcdefABCDEF";
 454}

Methods/Properties

.ctor()