1 /* 2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.naming.ldap; 27 28 import java.util.List; 29 import java.util.ArrayList; 30 31 import javax.naming.InvalidNameException; 32 33 /* 34 * RFC2253Parser implements a recursive descent parser for a single DN. 35 */ 36 final class Rfc2253Parser { 37 38 private final String name; // DN being parsed 39 private final char[] chars; // characters in LDAP name being parsed 40 private final int len; // length of "chars" 41 private int cur = 0; // index of first unconsumed char in "chars" 42 43 /* 44 * Given an LDAP DN in string form, returns a parser for it. 45 */ Rfc2253Parser(String name)46 Rfc2253Parser(String name) { 47 this.name = name; 48 len = name.length(); 49 chars = name.toCharArray(); 50 } 51 52 /* 53 * Parses the DN, returning a List of its RDNs. 54 */ 55 // public List<Rdn> getDN() throws InvalidNameException { 56 parseDn()57 List<Rdn> parseDn() throws InvalidNameException { 58 cur = 0; 59 60 // ArrayList<Rdn> rdns = 61 // new ArrayList<Rdn>(len / 3 + 10); // leave room for growth 62 63 ArrayList<Rdn> rdns = 64 new ArrayList<>(len / 3 + 10); // leave room for growth 65 66 if (len == 0) { 67 return rdns; 68 } 69 70 rdns.add(doParse(new Rdn())); 71 while (cur < len) { 72 if (chars[cur] == ',' || chars[cur] == ';') { 73 ++cur; 74 rdns.add(0, doParse(new Rdn())); 75 } else { 76 throw new InvalidNameException("Invalid name: " + name); 77 } 78 } 79 return rdns; 80 } 81 82 /* 83 * Parses the DN, if it is known to contain a single RDN. 84 */ parseRdn()85 Rdn parseRdn() throws InvalidNameException { 86 return parseRdn(new Rdn()); 87 } 88 89 /* 90 * Parses the DN, if it is known to contain a single RDN. 91 */ parseRdn(Rdn rdn)92 Rdn parseRdn(Rdn rdn) throws InvalidNameException { 93 rdn = doParse(rdn); 94 if (cur < len) { 95 throw new InvalidNameException("Invalid RDN: " + name); 96 } 97 return rdn; 98 } 99 100 /* 101 * Parses the next RDN and returns it. Throws an exception if 102 * none is found. Leading and trailing whitespace is consumed. 103 */ doParse(Rdn rdn)104 private Rdn doParse(Rdn rdn) throws InvalidNameException { 105 106 while (cur < len) { 107 consumeWhitespace(); 108 String attrType = parseAttrType(); 109 consumeWhitespace(); 110 if (cur >= len || chars[cur] != '=') { 111 throw new InvalidNameException("Invalid name: " + name); 112 } 113 ++cur; // consume '=' 114 consumeWhitespace(); 115 String value = parseAttrValue(); 116 consumeWhitespace(); 117 118 rdn.put(attrType, Rdn.unescapeValue(value)); 119 if (cur >= len || chars[cur] != '+') { 120 break; 121 } 122 ++cur; // consume '+' 123 } 124 rdn.sort(); 125 return rdn; 126 } 127 128 /* 129 * Returns the attribute type that begins at the next unconsumed 130 * char. No leading whitespace is expected. 131 * This routine is more generous than RFC 2253. It accepts 132 * attribute types composed of any nonempty combination of Unicode 133 * letters, Unicode digits, '.', '-', and internal space characters. 134 */ parseAttrType()135 private String parseAttrType() throws InvalidNameException { 136 137 final int beg = cur; 138 while (cur < len) { 139 char c = chars[cur]; 140 if (Character.isLetterOrDigit(c) || 141 c == '.' || 142 c == '-' || 143 c == ' ') { 144 ++cur; 145 } else { 146 break; 147 } 148 } 149 // Back out any trailing spaces. 150 while ((cur > beg) && (chars[cur - 1] == ' ')) { 151 --cur; 152 } 153 154 if (beg == cur) { 155 throw new InvalidNameException("Invalid name: " + name); 156 } 157 return new String(chars, beg, cur - beg); 158 } 159 160 /* 161 * Returns the attribute value that begins at the next unconsumed 162 * char. No leading whitespace is expected. 163 */ parseAttrValue()164 private String parseAttrValue() throws InvalidNameException { 165 166 if (cur < len && chars[cur] == '#') { 167 return parseBinaryAttrValue(); 168 } else if (cur < len && chars[cur] == '"') { 169 return parseQuotedAttrValue(); 170 } else { 171 return parseStringAttrValue(); 172 } 173 } 174 parseBinaryAttrValue()175 private String parseBinaryAttrValue() throws InvalidNameException { 176 final int beg = cur; 177 ++cur; // consume '#' 178 while ((cur < len) && 179 Character.isLetterOrDigit(chars[cur])) { 180 ++cur; 181 } 182 return new String(chars, beg, cur - beg); 183 } 184 parseQuotedAttrValue()185 private String parseQuotedAttrValue() throws InvalidNameException { 186 187 final int beg = cur; 188 ++cur; // consume '"' 189 190 while ((cur < len) && chars[cur] != '"') { 191 if (chars[cur] == '\\') { 192 ++cur; // consume backslash, then what follows 193 } 194 ++cur; 195 } 196 if (cur >= len) { // no closing quote 197 throw new InvalidNameException("Invalid name: " + name); 198 } 199 ++cur; // consume closing quote 200 201 return new String(chars, beg, cur - beg); 202 } 203 parseStringAttrValue()204 private String parseStringAttrValue() throws InvalidNameException { 205 206 final int beg = cur; 207 int esc = -1; // index of the most recently escaped character 208 209 while ((cur < len) && !atTerminator()) { 210 if (chars[cur] == '\\') { 211 ++cur; // consume backslash, then what follows 212 esc = cur; 213 } 214 ++cur; 215 } 216 if (cur > len) { // 'twas backslash followed by nothing 217 throw new InvalidNameException("Invalid name: " + name); 218 } 219 220 // Trim off (unescaped) trailing whitespace. 221 int end; 222 for (end = cur; end > beg; end--) { 223 if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) { 224 break; 225 } 226 } 227 return new String(chars, beg, end - beg); 228 } 229 consumeWhitespace()230 private void consumeWhitespace() { 231 while ((cur < len) && isWhitespace(chars[cur])) { 232 ++cur; 233 } 234 } 235 236 /* 237 * Returns true if next unconsumed character is one that terminates 238 * a string attribute value. 239 */ atTerminator()240 private boolean atTerminator() { 241 return (cur < len && 242 (chars[cur] == ',' || 243 chars[cur] == ';' || 244 chars[cur] == '+')); 245 } 246 247 /* 248 * Best guess as to what RFC 2253 means by "whitespace". 249 */ isWhitespace(char c)250 private static boolean isWhitespace(char c) { 251 return (c == ' ' || c == '\r'); 252 } 253 } 254