1/*
2 * Copyright (c) 1999, 2002, 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 com.sun.jndi.ldap;
27
28import javax.naming.*;
29import java.net.MalformedURLException;
30import java.io.UnsupportedEncodingException;
31import java.util.StringTokenizer;
32import com.sun.jndi.toolkit.url.Uri;
33import com.sun.jndi.toolkit.url.UrlUtil;
34
35/*
36 * Extract components of an LDAP URL.
37 *
38 * The format of an LDAP URL is defined in RFC 2255 as follows:
39 *
40 *     ldapurl    = scheme "://" [hostport] ["/"
41 *                  [dn ["?" [attributes] ["?" [scope]
42 *                  ["?" [filter] ["?" extensions]]]]]]
43 *     scheme     = "ldap"
44 *     attributes = attrdesc *("," attrdesc)
45 *     scope      = "base" / "one" / "sub"
46 *     dn         = distinguishedName from Section 3 of [1]
47 *     hostport   = hostport from Section 5 of RFC 1738 [5]
48 *     attrdesc   = AttributeDescription from Section 4.1.5 of [2]
49 *     filter     = filter from Section 4 of [4]
50 *     extensions = extension *("," extension)
51 *     extension  = ["!"] extype ["=" exvalue]
52 *     extype     = token / xtoken
53 *     exvalue    = LDAPString from section 4.1.2 of [2]
54 *     token      = oid from section 4.1 of [3]
55 *     xtoken     = ("X-" / "x-") token
56 *
57 * For example,
58 *
59 *     ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US
60 *     ldap://host.com:6666/o=IMC,c=US??sub?(cn=Babs%20Jensen)
61 *
62 * This class also supports ldaps URLs.
63 */
64
65final public class LdapURL extends Uri {
66
67    private boolean useSsl = false;
68    private String DN = null;
69    private String attributes = null;
70    private String scope = null;
71    private String filter = null;
72    private String extensions = null;
73
74    /**
75     * Creates an LdapURL object from an LDAP URL string.
76     */
77    public LdapURL(String url) throws NamingException {
78
79        super();
80
81        try {
82            init(url); // scheme, host, port, path, query
83            useSsl = scheme.equalsIgnoreCase("ldaps");
84
85            if (! (scheme.equalsIgnoreCase("ldap") || useSsl)) {
86                throw new MalformedURLException("Not an LDAP URL: " + url);
87            }
88
89            parsePathAndQuery(); // DN, attributes, scope, filter, extensions
90
91        } catch (MalformedURLException e) {
92            NamingException ne = new NamingException("Cannot parse url: " + url);
93            ne.setRootCause(e);
94            throw ne;
95        } catch (UnsupportedEncodingException e) {
96            NamingException ne = new NamingException("Cannot parse url: " + url);
97            ne.setRootCause(e);
98            throw ne;
99        }
100    }
101
102    /**
103     * Returns true if the URL is an LDAPS URL.
104     */
105    public boolean useSsl() {
106        return useSsl;
107    }
108
109    /**
110     * Returns the LDAP URL's distinguished name.
111     */
112    public String getDN() {
113        return DN;
114    }
115
116    /**
117     * Returns the LDAP URL's attributes.
118     */
119    public String getAttributes() {
120        return attributes;
121    }
122
123    /**
124     * Returns the LDAP URL's scope.
125     */
126    public String getScope() {
127        return scope;
128    }
129
130    /**
131     * Returns the LDAP URL's filter.
132     */
133    public String getFilter() {
134        return filter;
135    }
136
137    /**
138     * Returns the LDAP URL's extensions.
139     */
140    public String getExtensions() {
141        return extensions;
142    }
143
144    /**
145     * Given a space-separated list of LDAP URLs, returns an array of strings.
146     */
147    public static String[] fromList(String urlList) throws NamingException {
148
149        String[] urls = new String[(urlList.length() + 1) / 2];
150        int i = 0;              // next available index in urls
151        StringTokenizer st = new StringTokenizer(urlList, " ");
152
153        while (st.hasMoreTokens()) {
154            urls[i++] = st.nextToken();
155        }
156        String[] trimmed = new String[i];
157        System.arraycopy(urls, 0, trimmed, 0, i);
158        return trimmed;
159    }
160
161    /**
162     * Determines whether an LDAP URL has query components.
163     */
164    public static boolean hasQueryComponents(String url) {
165        return (url.lastIndexOf('?') != -1);
166    }
167
168    /*
169     * Assembles an LDAP or LDAPS URL string from its components.
170     * If "host" is an IPv6 literal, it may optionally include delimiting
171     * brackets.
172     */
173    static String toUrlString(String host, int port, String dn, boolean useSsl)
174        {
175
176        try {
177            String h = (host != null) ? host : "";
178            if ((h.indexOf(':') != -1) && (h.charAt(0) != '[')) {
179                h = "[" + h + "]";          // IPv6 literal
180            }
181            String p = (port != -1) ? (":" + port) : "";
182            String d = (dn != null) ? ("/" + UrlUtil.encode(dn, "UTF8")) : "";
183
184            return useSsl ? "ldaps://" + h + p + d : "ldap://" + h + p + d;
185        } catch (UnsupportedEncodingException e) {
186            // UTF8 should always be supported
187            throw new IllegalStateException("UTF-8 encoding unavailable");
188        }
189    }
190
191    /*
192     * Parses the path and query components of an URL and sets this
193     * object's fields accordingly.
194     */
195    private void parsePathAndQuery() throws MalformedURLException,
196        UnsupportedEncodingException {
197
198        // path begins with a '/' or is empty
199
200        if (path.equals("")) {
201            return;
202        }
203
204        DN = path.startsWith("/") ? path.substring(1) : path;
205        if (DN.length() > 0) {
206            DN = UrlUtil.decode(DN, "UTF8");
207        }
208
209        // query begins with a '?' or is null
210
211        if (query == null || query.length() < 2) {
212            return;
213        }
214
215        int currentIndex = 1;
216        int nextQmark;
217        int endIndex;
218
219        // attributes:
220        nextQmark = query.indexOf('?', currentIndex);
221        endIndex = nextQmark == -1 ? query.length() : nextQmark;
222        if (endIndex - currentIndex > 0) {
223            attributes = query.substring(currentIndex, endIndex);
224        }
225        currentIndex = endIndex + 1;
226        if (currentIndex >= query.length()) {
227            return;
228        }
229
230        // scope:
231        nextQmark = query.indexOf('?', currentIndex);
232        endIndex = nextQmark == -1 ? query.length() : nextQmark;
233        if (endIndex - currentIndex > 0) {
234            scope = query.substring(currentIndex, endIndex);
235        }
236        currentIndex = endIndex + 1;
237        if (currentIndex >= query.length()) {
238            return;
239        }
240
241        // filter:
242        nextQmark = query.indexOf('?', currentIndex);
243        endIndex = nextQmark == -1 ? query.length() : nextQmark;
244        if (endIndex - currentIndex > 0) {
245            filter = query.substring(currentIndex, endIndex);
246            filter = UrlUtil.decode(filter, "UTF8");
247        }
248        currentIndex = endIndex + 1;
249        if (currentIndex >= query.length()) {
250            return;
251        }
252
253        // extensions:
254        if (query.length() - currentIndex > 0) {
255            extensions = query.substring(currentIndex);
256            extensions = UrlUtil.decode(extensions, "UTF8");
257        }
258    }
259
260/*
261    public static void main(String[] args) throws Exception {
262
263        LdapURL url = new LdapURL(args[0]);
264
265        System.out.println("Example LDAP URL: " + url.toString());
266        System.out.println("  scheme: " + url.getScheme());
267        System.out.println("    host: " + url.getHost());
268        System.out.println("    port: " + url.getPort());
269        System.out.println("      DN: " + url.getDN());
270        System.out.println("   attrs: " + url.getAttributes());
271        System.out.println("   scope: " + url.getScope());
272        System.out.println("  filter: " + url.getFilter());
273        System.out.println("  extens: " + url.getExtensions());
274        System.out.println("");
275    }
276*/
277}
278