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
26package javax.naming.ldap;
27
28import java.util.List;
29import java.util.ArrayList;
30
31import javax.naming.InvalidNameException;
32
33/*
34 * RFC2253Parser implements a recursive descent parser for a single DN.
35 */
36final 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         */
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
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         */
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         */
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         */
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         */
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         */
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
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
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
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
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         */
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         */
250        private static boolean isWhitespace(char c) {
251            return (c == ' ' || c == '\r');
252        }
253    }
254