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 
  26 package com.sun.jndi.ldap;
  27 
  28 import javax.naming.*;
  29 import java.net.MalformedURLException;
  30 import java.io.UnsupportedEncodingException;
  31 import java.util.StringTokenizer;
  32 import com.sun.jndi.toolkit.url.Uri;
  33 import 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 
  65 final 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.isEmpty()) {
 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 }