1faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath/*
2faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  Licensed to the Apache Software Foundation (ASF) under one or more
3faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  contributor license agreements.  See the NOTICE file distributed with
4faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  this work for additional information regarding copyright ownership.
5faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  The ASF licenses this file to You under the Apache License, Version 2.0
6faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  (the "License"); you may not use this file except in compliance with
7faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  the License.  You may obtain a copy of the License at
8faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *
9faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *     http://www.apache.org/licenses/LICENSE-2.0
10faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *
11faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  Unless required by applicable law or agreed to in writing, software
12faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  distributed under the License is distributed on an "AS IS" BASIS,
13faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  See the License for the specific language governing permissions and
15faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath *  limitations under the License.
16faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath */
17faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
18faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathpackage com.squareup.okhttp.internal.tls;
19faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
20faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathimport javax.security.auth.x500.X500Principal;
21faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
22faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath/**
23faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * A distinguished name (DN) parser. This parser only supports extracting a
24faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath * string value from a DN. It doesn't support values in the hex-string style.
25faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath */
26faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamathfinal class DistinguishedNameParser {
27faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private final String dn;
28faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private final int length;
29faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private int pos;
30faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private int beg;
31faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private int end;
32faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
33faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /** Temporary variable to store positions of the currently parsed item. */
34faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private int cur;
35faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
36faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /** Distinguished name characters. */
37faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private char[] chars;
38faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
39faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  public DistinguishedNameParser(X500Principal principal) {
40faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // RFC2253 is used to ensure we get attributes in the reverse
41faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // order of the underlying ASN.1 encoding, so that the most
42faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // significant values of repeated attributes occur first.
43faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.dn = principal.getName(X500Principal.RFC2253);
44faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    this.length = this.dn.length();
45faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
46faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
47faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // gets next attribute type: (ALPHA 1*keychar) / oid
48faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private String nextAT() {
49faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // skip preceding space chars, they can present after
50faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // comma or semicolon (compatibility with RFC 1779)
51faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (; pos < length && chars[pos] == ' '; pos++) {
52faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
53faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (pos == length) {
54faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return null; // reached the end of DN
55faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
56faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
57faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // mark the beginning of attribute type
58faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    beg = pos;
59faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
60faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // attribute type chars
61faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++;
62faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
63faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // we don't follow exact BNF syntax here:
64faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // accept any char except space and '='
65faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
66faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (pos >= length) {
67faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Unexpected end of DN: " + dn);
68faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
69faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
70faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // mark the end of attribute type
71faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    end = pos;
72faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
73faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // skip trailing space chars between attribute type and '='
74faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // (compatibility with RFC 1779)
75faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (chars[pos] == ' ') {
76faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
77faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
78faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
79faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (chars[pos] != '=' || pos == length) {
80faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalStateException("Unexpected end of DN: " + dn);
81faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
82faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
83faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
84faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++; //skip '=' char
85faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
86faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // skip space chars between '=' and attribute value
87faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // (compatibility with RFC 1779)
88faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (; pos < length && chars[pos] == ' '; pos++) {
89faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
90faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
91faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // in case of oid attribute type skip its prefix: "oid." or "OID."
92faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // (compatibility with RFC 1779)
93faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if ((end - beg > 4) && (chars[beg + 3] == '.')
94faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        && (chars[beg] == 'O' || chars[beg] == 'o')
95faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
96faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
97faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      beg += 4;
98faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
99faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
100faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return new String(chars, beg, end - beg);
101faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
102faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
103faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
104faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private String quotedAV() {
105faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++;
106faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    beg = pos;
107faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    end = beg;
108faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    while (true) {
109faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
110faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (pos == length) {
111faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalStateException("Unexpected end of DN: " + dn);
112faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
113faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
114faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (chars[pos] == '"') {
115faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // enclosing quotation was found
116faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        pos++;
117faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        break;
118faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else if (chars[pos] == '\\') {
119faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        chars[end] = getEscaped();
120faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else {
121faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // shift char: required for string with escaped chars
122faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        chars[end] = chars[pos];
123faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
124faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      pos++;
125faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      end++;
126faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
127faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
128faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // skip trailing space chars before comma or semicolon.
129faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // (compatibility with RFC 1779)
130faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (; pos < length && chars[pos] == ' '; pos++) {
131faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
132faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
133faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return new String(chars, beg, end - beg);
134faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
135faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
136faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // gets hex string attribute value: "#" hexstring
137faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private String hexAV() {
138faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (pos + 4 >= length) {
139faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // encoded byte array  must be not less then 4 c
140faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Unexpected end of DN: " + dn);
141faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
142faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
143faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    beg = pos; // store '#' position
144faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++;
145faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    while (true) {
146faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
147faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // check for end of attribute value
148faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // looks for space and component separators
149faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (pos == length || chars[pos] == '+' || chars[pos] == ','
150faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          || chars[pos] == ';') {
151faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        end = pos;
152faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        break;
153faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
154faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
155faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (chars[pos] == ' ') {
156faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        end = pos;
157faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        pos++;
158faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // skip trailing space chars before comma or semicolon.
159faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // (compatibility with RFC 1779)
160faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        for (; pos < length && chars[pos] == ' '; pos++) {
161faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
162faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        break;
163faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
164faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        chars[pos] += 32; //to low case
165faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
166faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
167faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      pos++;
168faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
169faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
170faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // verify length of hex string
171faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // encoded byte array  must be not less then 4 and must be even number
172faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    int hexLen = end - beg; // skip first '#' char
173faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (hexLen < 5 || (hexLen & 1) == 0) {
174faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Unexpected end of DN: " + dn);
175faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
176faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
177faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // get byte encoding from string representation
178faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    byte[] encoded = new byte[hexLen / 2];
179faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
180faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      encoded[i] = (byte) getByte(p);
181faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
182faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
183faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return new String(chars, beg, hexLen);
184faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
185faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
186faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // gets string attribute value: *( stringchar / pair )
187faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private String escapedAV() {
188faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    beg = pos;
189faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    end = pos;
190faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    while (true) {
191faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (pos >= length) {
192faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // the end of DN has been found
193faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return new String(chars, beg, end - beg);
194faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
195faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
196faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      switch (chars[pos]) {
197faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case '+':
198faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case ',':
199faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case ';':
200faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // separator char has been found
201faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          return new String(chars, beg, end - beg);
202faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case '\\':
203faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // escaped char
204faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          chars[end++] = getEscaped();
205faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          pos++;
206faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
207faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case ' ':
208faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // need to figure out whether space defines
209faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          // the end of attribute value or not
210faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          cur = end;
211faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
212faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          pos++;
213faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          chars[end++] = ' ';
214faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
215faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          for (; pos < length && chars[pos] == ' '; pos++) {
216faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            chars[end++] = ' ';
217faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          }
218faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          if (pos == length || chars[pos] == ',' || chars[pos] == '+'
219faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath              || chars[pos] == ';') {
220faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            // separator char or the end of DN has been found
221faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath            return new String(chars, beg, cur - beg);
222faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          }
223faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
224faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        default:
225faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          chars[end++] = chars[pos];
226faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          pos++;
227faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
228faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
229faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
230faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
231faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // returns escaped char
232faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private char getEscaped() {
233faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++;
234faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (pos == length) {
235faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Unexpected end of DN: " + dn);
236faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
237faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
238faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    switch (chars[pos]) {
239faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '"':
240faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '\\':
241faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case ',':
242faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '=':
243faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '+':
244faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '<':
245faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '>':
246faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '#':
247faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case ';':
248faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case ' ':
249faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '*':
250faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '%':
251faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      case '_':
252faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        //FIXME: escaping is allowed only for leading or trailing space char
253faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return chars[pos];
254faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      default:
255faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // RFC doesn't explicitly say that escaped hex pair is
256faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        // interpreted as UTF-8 char. It only contains an example of such DN.
257faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return getUTF8();
258faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
259faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
260faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
261faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // decodes UTF-8 char
262faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // see http://www.unicode.org for UTF-8 bit distribution table
263faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private char getUTF8() {
264faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    int res = getByte(pos);
265faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos++; //FIXME tmp
266faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
267faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (res < 128) { // one byte: 0-7F
268faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return (char) res;
269faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else if (res >= 192 && res <= 247) {
270faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
271faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      int count;
272faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (res <= 223) { // two bytes: C0-DF
273faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        count = 1;
274faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        res = res & 0x1F;
275faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else if (res <= 239) { // three bytes: E0-EF
276faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        count = 2;
277faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        res = res & 0x0F;
278faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else { // four bytes: F0-F7
279faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        count = 3;
280faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        res = res & 0x07;
281faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
282faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
283faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      int b;
284faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      for (int i = 0; i < count; i++) {
285faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        pos++;
286faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        if (pos == length || chars[pos] != '\\') {
287faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
288faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
289faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        pos++;
290faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
291faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        b = getByte(pos);
292faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        pos++; //FIXME tmp
293faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        if ((b & 0xC0) != 0x80) {
294faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
295faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        }
296faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
297faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        res = (res << 6) + (b & 0x3F);
298faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
299faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return (char) res;
300faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
301faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
302faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
303faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
304faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
305faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // Returns byte representation of a char pair
306faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // The char pair is composed of DN char in
307faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // specified 'position' and the next char
308faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // According to BNF syntax:
309faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
310faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  //                    / "a" / "b" / "c" / "d" / "e" / "f"
311faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  private int getByte(int position) {
312faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (position + 1 >= length) {
313faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Malformed DN: " + dn);
314faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
315faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
316faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    int b1, b2;
317faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
318faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    b1 = chars[position];
319faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (b1 >= '0' && b1 <= '9') {
320faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b1 = b1 - '0';
321faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else if (b1 >= 'a' && b1 <= 'f') {
322faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b1 = b1 - 87; // 87 = 'a' - 10
323faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else if (b1 >= 'A' && b1 <= 'F') {
324faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b1 = b1 - 55; // 55 = 'A' - 10
325faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
326faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Malformed DN: " + dn);
327faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
328faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
329faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    b2 = chars[position + 1];
330faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (b2 >= '0' && b2 <= '9') {
331faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b2 = b2 - '0';
332faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else if (b2 >= 'a' && b2 <= 'f') {
333faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b2 = b2 - 87; // 87 = 'a' - 10
334faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else if (b2 >= 'A' && b2 <= 'F') {
335faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      b2 = b2 - 55; // 55 = 'A' - 10
336faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    } else {
337faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      throw new IllegalStateException("Malformed DN: " + dn);
338faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
339faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
340faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    return (b1 << 4) + b2;
341faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
342faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
343faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  /**
344faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * Parses the DN and returns the most significant attribute value
345faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * for an attribute type, or null if none found.
346faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   *
347faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   * @param attributeType attribute type to look for (e.g. "ca")
348faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath   */
349faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  public String findMostSpecific(String attributeType) {
350faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    // Initialize internal state.
351faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    pos = 0;
352faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    beg = 0;
353faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    end = 0;
354faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    cur = 0;
355faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    chars = dn.toCharArray();
356faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
357faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    String attType = nextAT();
358faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    if (attType == null) {
359faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      return null;
360faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
361faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    while (true) {
362faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      String attValue = "";
363faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
364faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (pos == length) {
365faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return null;
366faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
367faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
368faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      switch (chars[pos]) {
369faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case '"':
370faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          attValue = quotedAV();
371faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
372faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case '#':
373faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          attValue = hexAV();
374faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
375faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case '+':
376faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case ',':
377faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        case ';': // compatibility with RFC 1779: semicolon can separate RDNs
378faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          //empty attribute value
379faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          break;
380faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        default:
381faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath          attValue = escapedAV();
382faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
383faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
384faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // Values are ordered from most specific to least specific
385faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // due to the RFC2253 formatting. So take the first match
386faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      // we see.
387faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (attributeType.equalsIgnoreCase(attType)) {
388faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return attValue;
389faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
390faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
391faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (pos >= length) {
392faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        return null;
393faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
394faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
395faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (chars[pos] == ',' || chars[pos] == ';') {
396faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      } else if (chars[pos] != '+') {
397faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalStateException("Malformed DN: " + dn);
398faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
399faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath
400faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      pos++;
401faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      attType = nextAT();
402faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      if (attType == null) {
403faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath        throw new IllegalStateException("Malformed DN: " + dn);
404faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath      }
405faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath    }
406faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath  }
407faf49723fb689c626f69876e718c58018eff8ee7Narayan Kamath}
408