// // Copyright (c) ZeroC, Inc. All rights reserved. // // // See RFC 2253 and RFC 1779. // namespace IceSSL { using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; class RFC2253 { internal class ParseException : Exception { internal ParseException() { } internal ParseException(string reason) { this.reason = reason; } internal string ice_id() { return "::RFC2253::ParseException"; } internal string reason; } internal struct RDNPair { internal string key; internal string value; } internal class RDNEntry { internal List rdn = new List(); internal bool negate = false; } internal static List parse(string data) { List results = new List(); RDNEntry current = new RDNEntry(); int pos = 0; while(pos < data.Length) { eatWhite(data, ref pos); if(pos < data.Length && data[pos] == '!') { if(current.rdn.Count > 0) { throw new ParseException("negation symbol '!' must appear at start of list"); } ++pos; current.negate = true; } current.rdn.Add(parseNameComponent(data, ref pos)); eatWhite(data, ref pos); if(pos < data.Length && data[pos] == ',') { ++pos; } else if(pos < data.Length && data[pos] == ';') { ++pos; results.Add(current); current = new RDNEntry(); } else if(pos < data.Length) { throw new ParseException("expected ',' or ';' at `" + data.Substring(pos) + "'"); } } if(current.rdn.Count > 0) { results.Add(current); } return results; } internal static List parseStrict(string data) { List results = new List(); int pos = 0; while(pos < data.Length) { results.Add(parseNameComponent(data, ref pos)); eatWhite(data, ref pos); if(pos < data.Length && (data[pos] == ',' || data[pos] == ';')) { ++pos; } else if(pos < data.Length) { throw new ParseException("expected ',' or ';' at `" + data.Substring(pos) + "'"); } } return results; } public static string unescape(string data) { if(data.Length == 0) { return data; } if(data[0] == '"') { if(data[data.Length - 1] != '"') { throw new ParseException("unescape: missing \""); } // // Return the string without quotes. // return data.Substring(1, data.Length - 2); } // // Unescape the entire string. // StringBuilder result = new StringBuilder(); if(data[0] == '#') { int pos = 1; while(pos < data.Length) { result.Append(unescapeHex(data, pos)); pos += 2; } } else { int pos = 0; while (pos < data.Length) { if(data[pos] != '\\') { result.Append(data[pos]); ++pos; } else { ++pos; if(pos >= data.Length) { throw new ParseException("unescape: invalid escape sequence"); } if(special.IndexOf(data[pos]) != -1 || data[pos] != '\\' || data[pos] != '"') { result.Append(data[pos]); ++pos; } else { result.Append(unescapeHex(data, pos)); pos += 2; } } } } return result.ToString(); } private static int hexToInt(char v) { if(v >= '0' && v <= '9') { return v - '0'; } if(v >= 'a' && v <= 'f') { return 10 + (v - 'a'); } if(v >= 'A' && v <= 'F') { return 10 + (v - 'A'); } throw new ParseException("unescape: invalid hex pair"); } private static char unescapeHex(string data, int pos) { Debug.Assert(pos < data.Length); if(pos + 2 >= data.Length) { throw new ParseException("unescape: invalid hex pair"); } return (char)(hexToInt(data[pos]) * 16 + hexToInt(data[pos + 1])); } private static RDNPair parseNameComponent(string data, ref int pos) { RDNPair result = parseAttributeTypeAndValue(data, ref pos); while(pos < data.Length) { eatWhite(data, ref pos); if(pos < data.Length && data[pos] == '+') { ++pos; } else { break; } RDNPair p = parseAttributeTypeAndValue(data, ref pos); result.value += "+"; result.value += p.key; result.value += '='; result.value += p.value; } return result; } private static RDNPair parseAttributeTypeAndValue(string data, ref int pos) { RDNPair p = new RDNPair(); p.key = parseAttributeType(data, ref pos); eatWhite(data, ref pos); if(pos >= data.Length) { throw new ParseException("invalid attribute type/value pair (unexpected end of data)"); } if(data[pos] != '=') { throw new ParseException("invalid attribute type/value pair (missing =). remainder: " + data.Substring(pos)); } ++pos; p.value = parseAttributeValue(data, ref pos); return p; } private static string parseAttributeType(string data, ref int pos) { eatWhite(data, ref pos); if(pos >= data.Length) { throw new ParseException("invalid attribute type (expected end of data)"); } string result = ""; // // RFC 1779. // ::= 1*( ) | "OID." | "oid." // ::= | "." // RFC 2253: // attributeType = (ALPHA 1*keychar) | oid // keychar = ALPHA | DIGIT | "-" // oid = 1*DIGIT *("." 1*DIGIT) // // In section 4 of RFC 2253 the document says: // Implementations MUST allow an oid in the attribute type to be // prefixed by one of the character strings "oid." or "OID.". // // Here we must also check for "oid." and "OID." before parsing // according to the ALPHA KEYCHAR* rule. // // First the OID case. // if(char.IsDigit(data[pos]) || (data.Length - pos >= 4 && (data.Substring(pos, 4).Equals("oid.") || data.Substring(pos, 4).Equals("OID.")))) { if(!char.IsDigit(data[pos])) { result += data.Substring(pos, 4); pos += 4; } while(true) { // 1*DIGIT while(pos < data.Length && char.IsDigit(data[pos])) { result += data[pos]; ++pos; } // "." 1*DIGIT if(pos < data.Length && data[pos] == '.') { result += data[pos]; ++pos; // 1*DIGIT must follow "." if(pos < data.Length && !char.IsDigit(data[pos])) { throw new ParseException("invalid attribute type (expected end of data)"); } } else { break; } } } else if(char.IsUpper(data[pos]) || char.IsLower(data[pos])) { // // The grammar is wrong in this case. It should be ALPHA // KEYCHAR* otherwise it will not accept "O" as a valid // attribute type. // result += data[pos]; ++pos; // 1* KEYCHAR while(pos < data.Length && (char.IsDigit(data[pos]) || char.IsUpper(data[pos]) || char.IsLower(data[pos]) || data[pos] == '-')) { result += data[pos]; ++pos; } } else { throw new ParseException("invalid attribute type"); } return result; } private static string parseAttributeValue(string data, ref int pos) { eatWhite(data, ref pos); if(pos >= data.Length) { return ""; } // // RFC 2253 // # hexstring // StringBuilder result = new StringBuilder(); if(data[pos] == '#') { result.Append(data[pos]); ++pos; while(true) { string h = parseHexPair(data, ref pos, true); if(h.Length == 0) { break; } result.Append(h); } } // // RFC 2253 // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2 // quotechar = // else if(data[pos] == '"') { result.Append(data[pos]); ++pos; while(true) { if(pos >= data.Length) { throw new ParseException("invalid attribute value (unexpected end of data)"); } // final terminating " if(data[pos] == '"') { result.Append(data[pos]); ++pos; break; } // any character except '\' else if(data[pos] != '\\') { result.Append(data[pos]); ++pos; } // pair '\' else { result.Append(parsePair(data, ref pos)); } } } // // RFC 2253 // * (stringchar | pair) // stringchar = // else { while(pos < data.Length) { if(data[pos] == '\\') { result.Append(parsePair(data, ref pos)); } else if(special.IndexOf(data[pos]) == -1 && data[pos] != '"') { result.Append(data[pos]); ++pos; } else { break; } } } return result.ToString(); } // // RFC2253: // pair = "\" ( special | "\" | QUOTATION | hexpair ) // private static string parsePair(string data, ref int pos) { string result = ""; Debug.Assert(data[pos] == '\\'); result += data[pos]; ++pos; if(pos >= data.Length) { throw new ParseException("invalid escape format (unexpected end of data)"); } if(special.IndexOf(data[pos]) != -1 || data[pos] != '\\' || data[pos] != '"') { result += data[pos]; ++pos; return result; } return parseHexPair(data, ref pos, false); } // // RFC 2253 // hexpair = hexchar hexchar // private static string parseHexPair(string data, ref int pos, bool allowEmpty) { string result = ""; if(pos < data.Length && hexvalid.IndexOf(data[pos]) != -1) { result += data[pos]; ++pos; } if(pos < data.Length && hexvalid.IndexOf(data[pos]) != -1) { result += data[pos]; ++pos; } if(result.Length != 2) { if(allowEmpty && result.Length == 0) { return result; } throw new ParseException("invalid hex format"); } return result; } // // RFC 2253: // // Implementations MUST allow for space (' ' ASCII 32) characters to be // present between name-component and ',', between attributeTypeAndValue // and '+', between attributeType and '=', and between '=' and // attributeValue. These space characters are ignored when parsing. // private static void eatWhite(string data, ref int pos) { while(pos < data.Length && data[pos] == ' ') { ++pos; } } private static string special = ",=+<>#;"; private static string hexvalid = "0123456789abcdefABCDEF"; } }