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