// ********************************************************************** // // Copyright (c) 2003-2010 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** package IceSSL; // // See RFC 2253 and RFC 1779. // class RFC2253 { static class ParseException extends Ice.LocalException { public ParseException() { } public ParseException(String reason) { this.reason = reason; } public String ice_name() { return "RFC2253::ParseException"; } public String reason; } static class RDNPair { String key; String value; } static class RDNEntry { java.util.List<RDNPair> rdn = new java.util.LinkedList<RDNPair>(); boolean negate = false; } static private class ParseState { String data; int pos; } public static java.util.List<RDNEntry> parse(String data) throws ParseException { java.util.List<RDNEntry> results = new java.util.LinkedList<RDNEntry>(); RDNEntry current = new RDNEntry(); ParseState state = new ParseState(); state.data = data; state.pos = 0; while(state.pos < state.data.length()) { eatWhite(state); if(state.pos < state.data.length() && state.data.charAt(state.pos) == '!') { if(!current.rdn.isEmpty()) { throw new ParseException("negation symbol '!' must appear at start of list"); } ++state.pos; current.negate = true; } current.rdn.add(parseNameComponent(state)); eatWhite(state); if(state.pos < state.data.length() && state.data.charAt(state.pos) == ',') { ++state.pos; } else if(state.pos < state.data.length() && state.data.charAt(state.pos) == ';') { ++state.pos; results.add(current); current = new RDNEntry(); } else if(state.pos < state.data.length()) { throw new ParseException("expected ',' or ';' at `" + state.data.substring(state.pos) + "'"); } } if(!current.rdn.isEmpty()) { results.add(current); } return results; } public static java.util.List<RDNPair> parseStrict(String data) throws ParseException { java.util.List<RDNPair> results = new java.util.LinkedList<RDNPair>(); ParseState state = new ParseState(); state.data = data; state.pos = 0; while(state.pos < state.data.length()) { results.add(parseNameComponent(state)); eatWhite(state); if(state.pos < state.data.length() && (state.data.charAt(state.pos) == ',' || state.data.charAt(state.pos) == ';')) { ++state.pos; } else if(state.pos < state.data.length()) { throw new ParseException("expected ',' or ';' at `" + state.data.substring(state.pos) + "'"); } } return results; } private static RDNPair parseNameComponent(ParseState state) throws ParseException { RDNPair result = parseAttributeTypeAndValue(state); while(state.pos < state.data.length()) { eatWhite(state); if(state.pos < state.data.length() && state.data.charAt(state.pos) == '+') { ++state.pos; } else { break; } RDNPair p = parseAttributeTypeAndValue(state); result.value += "+"; result.value += p.key; result.value += '='; result.value += p.value; } return result; } private static RDNPair parseAttributeTypeAndValue(ParseState state) throws ParseException { RDNPair p = new RDNPair(); p.key = parseAttributeType(state); eatWhite(state); if(state.pos >= state.data.length()) { throw new ParseException("invalid attribute type/value pair (unexpected end of state.data)"); } if(state.data.charAt(state.pos) != '=') { throw new ParseException("invalid attribute type/value pair (missing =)"); } ++state.pos; p.value = parseAttributeValue(state); return p; } private static String parseAttributeType(ParseState state) throws ParseException { eatWhite(state); if(state.pos >= state.data.length()) { throw new ParseException("invalid attribute type (expected end of state.data)"); } StringBuffer result = new StringBuffer(); // // RFC 1779. // <key> ::= 1*( <keychar> ) | "OID." <oid> | "oid." <oid> // <oid> ::= <digitString> | <digitstring> "." <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(Character.isDigit(state.data.charAt(state.pos)) || (state.data.length() - state.pos >= 4 && (state.data.substring(state.pos, state.pos + 4).equals("oid.") || state.data.substring(state.pos, state.pos + 4).equals("OID.")))) { if(!Character.isDigit(state.data.charAt(state.pos))) { result.append(state.data.substring(state.pos, state.pos + 4)); state.pos += 4; } while(true) { // 1*DIGIT while(state.pos < state.data.length() && Character.isDigit(state.data.charAt(state.pos))) { result.append(state.data.charAt(state.pos)); ++state.pos; } // "." 1*DIGIT if(state.pos < state.data.length() && state.data.charAt(state.pos) == '.') { result.append(state.data.charAt(state.pos)); ++state.pos; // 1*DIGIT must follow "." if(state.pos < state.data.length() && !Character.isDigit(state.data.charAt(state.pos))) { throw new ParseException("invalid attribute type (expected end of state.data)"); } } else { break; } } } else if(Character.isUpperCase(state.data.charAt(state.pos)) || Character.isLowerCase(state.data.charAt(state.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.append(state.data.charAt(state.pos)); ++state.pos; // 1* KEYCHAR while(state.pos < state.data.length() && (Character.isDigit(state.data.charAt(state.pos)) || Character.isUpperCase(state.data.charAt(state.pos)) || Character.isLowerCase(state.data.charAt(state.pos)) || state.data.charAt(state.pos) == '-')) { result.append(state.data.charAt(state.pos)); ++state.pos; } } else { throw new ParseException("invalid attribute type"); } return result.toString(); } private static String parseAttributeValue(ParseState state) throws ParseException { eatWhite(state); if(state.pos >= state.data.length()) { return ""; } // // RFC 2253 // # hexString // StringBuffer result = new StringBuffer(); if(state.data.charAt(state.pos) == '#') { result.append(state.data.charAt(state.pos)); ++state.pos; while(true) { String h = parseHexPair(state, true); if(h.length() == 0) { break; } result.append(h); } } // // RFC 2253 // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2 // quotechar = <any character except "\" or QUOTATION > // else if(state.data.charAt(state.pos) == '"') { result.append(state.data.charAt(state.pos)); ++state.pos; while(true) { if(state.pos >= state.data.length()) { throw new ParseException("invalid attribute value (unexpected end of state.data)"); } // final terminating " if(state.data.charAt(state.pos) == '"') { result.append(state.data.charAt(state.pos)); ++state.pos; break; } // any character except '\' else if(state.data.charAt(state.pos) != '\\') { result.append(state.data.charAt(state.pos)); ++state.pos; } // pair '\' else { result.append(parsePair(state)); } } } // // RFC 2253 // * (Stringchar | pair) // Stringchar = <any character except one of special, "\" or QUOTATION > // else { while(state.pos < state.data.length()) { if(state.data.charAt(state.pos) == '\\') { result.append(parsePair(state)); } else if(special.indexOf(state.data.charAt(state.pos)) == -1 && state.data.charAt(state.pos) != '"') { result.append(state.data.charAt(state.pos)); ++state.pos; } else { break; } } } return result.toString(); } // // RFC2253: // pair = "\" ( special | "\" | QUOTATION | hexpair ) // private static String parsePair(ParseState state) throws ParseException { String result = ""; assert(state.data.charAt(state.pos) == '\\'); result += state.data.charAt(state.pos); ++state.pos; if(state.pos >= state.data.length()) { throw new ParseException("invalid escape format (unexpected end of state.data)"); } if(special.indexOf(state.data.charAt(state.pos)) != -1 || state.data.charAt(state.pos) != '\\' || state.data.charAt(state.pos) != '"') { result += state.data.charAt(state.pos); ++state.pos; return result; } return parseHexPair(state, false); } // // RFC 2253 // hexpair = hexchar hexchar // private static String parseHexPair(ParseState state, boolean allowEmpty) throws ParseException { String result = ""; if(state.pos < state.data.length() && hexvalid.indexOf(state.data.charAt(state.pos)) != -1) { result += state.data.charAt(state.pos); ++state.pos; } if(state.pos < state.data.length() && hexvalid.indexOf(state.data.charAt(state.pos)) != -1) { result += state.data.charAt(state.pos); ++state.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(ParseState state) { while(state.pos < state.data.length() && state.data.charAt(state.pos) == ' ') { ++state.pos; } } private final static String special = ",=+<>#;"; private final static String hexvalid = "0123456789abcdefABCDEF"; }