/* ************************************************************************* */ /* $Workfile: lvclurll.jp $ $Revision: #2 $ */ /* COPYRIGHT (C) 1998 DATA CONNECTION LIMITED */ /*****************************************************************************/ /* LVCLURLL.JP */ /* */ /* Package: uk.co.datcon.odg.dclava */ /*****************************************************************************/ package jeffaschenk.commons.frameworks.cnxidx.utility.ldap; import java.net.*; import java.util.*; public class LDAPUrl { /** * Constructs an LDAPUrl object using the string provided * * @param xiurl the URL to base the LDAPUrl object on, as a String * @throws java.net.MalformedURLException thrown if the String fails to conform to * the correct syntax for an LDAP URL */ public LDAPUrl(String xiurl) throws MalformedURLException { decodeURL(xiurl); } /** * Constructs an LDAPUrl object with the specified parameters. * <p/> * <p>All parameters not directly specified will be set to their default * values (search scope base, filter (objectclass=*)). * <p/> * <p>Note that this constructor assumes these have NO escaped characters in * them (no %HH sequences). Any such escaped sequences will be mangled, as * the initial % character will be escaped. * * @param xihost the host to connect to * @param xiport the port to connect to * @param xidn the DN to base the operation on */ public LDAPUrl(String xihost, int xiport, String xidn) { this(xihost, xiport, xidn, null, LDAPv2.SCOPE_BASE, DEFAULT_FILTER); } /** * constructs an LDAPUrl object with the specified parameters. * <p/> * <p>Note that this constructor assumes these have NO escaped characters in * them (no %HH sequences; \HH filter sequences are OK)). Any such escaped * sequences will be mangled, as the initial % character will be escaped * * @param xihost the host to connect to * @param xiport the port to connect to * @param xidn the DN to base the operation on * @param xiattrnames the list of attributes to return, or null for all user * attributes. The identifier "*" may also be included in * the array to indicate all user attributes are to be * returned (LDAPv3 only, useful if you also want some * non-user attributes) * @param xiscope scope for searches, must be one of "base", "one" or "sub" * @param xifilter a filter, as specified in * "draft-ietf-asid-ldapv3-filter-03.txt". * Note that \HH (not %HH) escapes may be present here, as * specified in the draft RFC. This does not support * \<character> type escapes (as specified in RFC 1960). */ public LDAPUrl(String xihost, int xiport, String xidn, String[] xiattrnames, int xiscope, String xifilter) { StringBuffer lurl; /* we need to create the String representation of */ /* this URL, based on the arguments above. They are */ /* encoded and added to this buffer as we construct */ /* the String representation */ int lloop; /* used to loop through list of attrnames */ lurl = new StringBuffer("ldap://"); /*************************************************************************/ /* We call encode on dn and filter in constructing the String */ /* representation of this URL as they may contain dangerous characters. */ /* The others must contain only safe characters. */ /*************************************************************************/ m_host = xihost; m_port = xiport; m_dn = xidn; m_attrnames = xiattrnames; m_scope = xiscope; if (xifilter != null) { m_filter = xifilter; } else { m_filter = DEFAULT_FILTER; } /*************************************************************************/ /* Include hostport info, if present. */ /*************************************************************************/ if (m_host != null) { lurl.append(m_host); lurl.append(":"); lurl.append(m_port); } lurl.append("/"); /*************************************************************************/ /* DN must be non-null if other arguments are (see BNF for valid LDAP */ /* URLs). If DN isn't present, stop encoding the String representation */ /* of this URL. */ /*************************************************************************/ if (m_dn != null) { /***********************************************************************/ /* DN may contain unsafe characters, so we encode it */ /***********************************************************************/ lurl.append(encode(m_dn)); lurl.append("?"); /***********************************************************************/ /* Attrnames are optional; if present, add them as a comma delimited */ /* list. */ /***********************************************************************/ if (m_attrnames != null) { lurl.append(m_attrnames[0]); for (lloop = 1; lloop < m_attrnames.length; lloop++) { lurl.append(","); lurl.append(m_attrnames[lloop]); } } lurl.append("?"); /***********************************************************************/ /* Scope must be one of base, one or sub. Default to base. */ /***********************************************************************/ switch (m_scope) { case LDAPv2.SCOPE_BASE: lurl.append("base"); break; case LDAPv2.SCOPE_ONE: lurl.append("one"); break; case LDAPv2.SCOPE_SUB: lurl.append("sub"); break; default: lurl.append("base"); break; } /***********************************************************************/ /* Filter may contain unsafe characters, so we encode it */ /***********************************************************************/ lurl.append("?"); lurl.append(encode(m_filter)); /***********************************************************************/ /* This doesn't yet support extensions, so we are done here */ /* */ /* vv Extensions */ /***********************************************************************/ m_url = lurl.toString(); } } /***************************************************************************/ /* Package visibility named constants */ /** * *********************************************************************** */ static final String DEFAULT_HOST = null; static final int DEFAULT_PORT = 389; static final String DEFAULT_DN = ""; static final String[] DEFAULT_ATTRNAMES = null; static final int DEFAULT_SCOPE = LDAPv2.SCOPE_BASE; static final String DEFAULT_FILTER = "(objectClass=*)"; /***************************************************************************/ /* Private instance variables. All but m_url do not have dangerous */ /* characters escaped. */ /***************************************************************************/ /***************************************************************************/ /* The string representation of this URL, fully escaped. */ /** * *********************************************************************** */ private String m_url; /***************************************************************************/ /* Host to connect to. */ /** * *********************************************************************** */ private String m_host; /***************************************************************************/ /* Port to connect to. */ /** * *********************************************************************** */ private int m_port; /***************************************************************************/ /* DN of object to base search from. */ /** * *********************************************************************** */ private String m_dn; /***************************************************************************/ /* Array of attribute names to return (null or {"*"} for all user */ /* attributes (note that '*' is an LDAPv3 representation of all user */ /* attributes)). */ /** * *********************************************************************** */ private String[] m_attrnames; /***************************************************************************/ /* Scope of search, takes value of one of SCOPE_BASE, SCOPE_ONE or */ /* SCOPE_SUB from LDAPv2. */ /** * *********************************************************************** */ private int m_scope; /***************************************************************************/ /* Search filter (default (objectclass=*)). */ /** * *********************************************************************** */ private String m_filter; /***************************************************************************/ /* Accessors (public visibility) */ /* */ /* Any of these except getUrl() may return null (or zero for the integers) */ /* in the event that this LDAPUrl object is storing a non-LDAP URL. */ /* */ /* toString() is overridden to return the string representation of this */ /* URL. */ /***************************************************************************/ /***************************************************************************/ /* Note this returns a reference to the array, not a copy. */ /** * *********************************************************************** */ public String[] getAttributeArray() { return (m_attrnames); } public Enumeration getAttributes() { int lloop; Vector<String> lattrnames; Enumeration lresult; lattrnames = new Vector<>(m_attrnames.length); for (lloop = 0; lloop < m_attrnames.length; lloop++) { lattrnames.addElement(m_attrnames[lloop]); } lresult = lattrnames.elements(); return (lresult); } public String getDN() { return (m_dn); } public String getFilter() { return (m_filter); } public String getHost() { return (m_host); } public int getPort() { return (m_port); } public int getScope() { return (m_scope); } /** * returns the String URL this object represents */ public String getUrl() { return (m_url); } /** * returns the String URL this object represents */ public String toString() { return (m_url); } /*****************************************************************************/ /* Public methods (static) */ /*****************************************************************************/ /*****************************************************************************/ /* $Workfile: lvmludec.mtd $ $Revision: #2 $ */ /* COPYRIGHT (C) 1998 DATA CONNECTION LIMITED */ /*****************************************************************************/ /** Package: uk.co.datcon.odg.dclava */ /** * Decodes a String with %HH escaped characters into an ordinary String * * @param xiurlencoded the escaped String * @return String the decoded string * @throws java.net.MalformedURLException thrown if there are any sequences that * could not be decoded, like a badly formed * %HH sequence (i.e. '%H H'). It does no * other checking of the String (e.g. it * doesn't check that it begins ldap:) * <p/> * Called by LDAPUrl(String) to decode the components of an URL string. * <p/> * Scans through replacing %HH sequences with the relevant character, * throwing an exception upon encountering a bad sequence. */ public static String decode(String xiurlencoded) throws MalformedURLException { StringBuffer lbuffer; /* characters are added to this as they are */ /* decoded */ int loffset; /* offset into the undecoded string */ int llength; /* length of the undecoded string */ int lintvalue; /* integer value of HH escape sequence */ char lchar; /* character value of %HH escape sequence */ lbuffer = new StringBuffer(); loffset = 0; llength = xiurlencoded.length(); /***************************************************************************/ /* Loop through characters adding unescaped ones to buffer, and adding */ /* decoded value of %HH escape sequences to buffer. */ /***************************************************************************/ while (loffset < llength) { lchar = xiurlencoded.charAt(loffset); if (lchar != '%') { /***********************************************************************/ /* Add normal character */ /***********************************************************************/ lbuffer.append(lchar); loffset++; } else { /***********************************************************************/ /* Attempt to decode escape sequence */ /***********************************************************************/ try { loffset++; /*********************************************************************/ /* parse the next two characters into an integer. */ /*********************************************************************/ lintvalue = Integer.parseInt( xiurlencoded.substring(loffset, loffset + 2), 16); lchar = (char) lintvalue; lbuffer.append(lchar); loffset += 2; } catch (StringIndexOutOfBoundsException e) { /*********************************************************************/ /* This is thrown if there are not at least 2 characters following */ /* the escape character '%'. If that is the case, the string is */ /* malformed and we throw an exception */ /*********************************************************************/ throw new MalformedURLException(); } catch (NumberFormatException e) { /*********************************************************************/ /* This is thrown if the two characters following an escape */ /* character '%' are not valid hex characters. */ /*********************************************************************/ throw new MalformedURLException(); } } } return (lbuffer.toString()); } /* $Workfile: lvmluenc.mtd $ $Revision: #2 $ */ /* COPYRIGHT (C) 1998 DATA CONNECTION LIMITED */ /** Package: uk.co.datcon.odg.dclava */ /** * Encodes unsafe characters as %HH * * @param xitoencode the string to encode * @return the encoded string * <p/> * This methods encodes a String, escaping all unsafe characters to %HH * form. It would be inappropriate to call this method on an entire URL, as * it would encode all the delimiter characters, like "/", "?" and such, * thereby mangling the URL * <p/> * If this is passed null, it returns an empty string "". * <p/> * Scans through the string adding safe characters to a buffer and * adding %HH escapes to the buffer in place of unsafe characters, then * returns the contents of the buffer * <p/> * The characters escaped are * <p/> * <>"#%{}|\^~[]`/?:@=&;, and " " (space) * <p/> * as they are unsafe <RFC1738 sec 2.2 and draft-ietf-asid-ldapv3-url-04.txt * sec 3> */ public static String encode(String xitoencode) { StringBuffer lencoded; int loffset; int llength; char lchar; lencoded = new StringBuffer(); /***************************************************************************/ /* If xiencoded is null, we will return an empty string, "". Otherwise, */ /* encode any unsafe characters. */ /***************************************************************************/ if (xitoencode != null) { loffset = 0; llength = xitoencode.length(); /*************************************************************************/ /* Loop adding characters to the buffer, encoding where necessary. */ /*************************************************************************/ while (loffset < llength) { lchar = xitoencode.charAt(loffset); loffset++; /***********************************************************************/ /* The following characters are unsafe, so we encode them. Others are */ /* added directly to the buffer intact. */ /***********************************************************************/ if ((lchar == '<') || (lchar == '>') || (lchar == '\"') || (lchar == '#') || (lchar == '%') || (lchar == '{') || (lchar == '}') || (lchar == '|') || (lchar == '\\') || (lchar == '^') || (lchar == '~') || (lchar == '[') || (lchar == ']') || (lchar == '`') || (lchar == '/') || (lchar == '?') || (lchar == ':') || (lchar == '@') || (lchar == '=') || (lchar == '&') || (lchar == ';') || (lchar == ',') || (lchar == ' ')) { lencoded.append("%"); /*********************************************************************/ /* Integer.toHexString encodes integers in the least possible number */ /* of hex digits, so for characters < 0x10 we need to add a leading */ /* 0 to the hex escape sequence. */ /* */ /* vv Unicode characters > 0xFF would break this */ /*********************************************************************/ if (lchar < 0x10) { lencoded.append("0"); lencoded.append(Integer.toHexString((int) lchar)); } else { lencoded.append(Integer.toHexString((int) lchar)); } } else { lencoded.append(lchar); } } } return (lencoded.toString()); } /*****************************************************************************/ /* Default (package) visibility methods */ /*****************************************************************************/ /*****************************************************************************/ /* $Workfile: lvmludur.mtd $ $Revision: #2 $ */ /* COPYRIGHT (C) 1998 DATA CONNECTION LIMITED */ /*****************************************************************************/ /** Package: uk.co.datcon.odg.dclava */ /** * Decodes unsafe characters from %HH, parses the URL * * @param xiurl the URL to decode * @throws java.net.MalformedURLException thrown if the URL can't be parsed properly * <p/> * Called by the constructors of this class * <p/> * Parses and decodes the URL, storing parsed contents in the private * instance variables of this class. */ void decodeURL(String xiurl) throws MalformedURLException { StringTokenizer ltokens; /* xiurl broken into tokens */ StringTokenizer lattrtokens; /* comma delimited list of attributes */ /* broken into tokens */ String ltoken; /* one token */ String ldecodedtoken; /* one token with %HH escapes removed */ int lnumtokens; /* number of tokens in xiurl */ int lnumread; /* number of tokens already processed */ int lnumattrs; /* number of attributes (tokens in */ /* lattrtokens) */ int lloop; /* loop index */ /***************************************************************************/ /* The form of LDAP URLs is */ /* */ /* "ldap://" [hostport] ["/" [dn ["?" [attributes] ["?" [scope] */ /* ["?" [filter] ["?" extensions ]]]]]] */ /* */ /* hostport is defined in RFC 1959 to be */ /* */ /* hostport = host [ ":" port ] */ /* */ /* Throughout the following, we use the example URL */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* */ /***************************************************************************/ /***************************************************************************/ /* Check that we have actually got a String of positive length */ /***************************************************************************/ if ((xiurl == null) || (xiurl.length() == 0)) { throw new MalformedURLException(); } /***************************************************************************/ /* Break xiurl into tokens. There must be at least one, so we should have */ /* lnumtokens > 0 and the first call to nextToken should not be in danger */ /* of throwing a NoSuchElementException. */ /* */ /* After the following block of commands we would have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "ldap" */ /* lnumtokens = 15 */ /* lnumread = 1 */ /***************************************************************************/ m_url = xiurl; ltokens = new StringTokenizer(xiurl, ":/?", true); lnumtokens = ltokens.countTokens(); ltoken = ltokens.nextToken(); lnumread = 1; /***************************************************************************/ /* Check that it is an LDAP URL (as opposed to ftp, http, etc) */ /* */ /* This will implicitly check that lnumtokens is at least 4. */ /***************************************************************************/ if (!(xiurl.toLowerCase().startsWith("ldap://"))) { throw new MalformedURLException("Not an LDAP URL"); } /***************************************************************************/ /* Discard the next three tokens; we know they are ":", "/" and "/", or an */ /* exception would have been thrown before this point. */ /* */ /* After the following block of commands we would have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "/" */ /* lnumtokens = 15 */ /* lnumread = 4 */ /***************************************************************************/ ltoken = ltokens.nextToken(); ltoken = ltokens.nextToken(); ltoken = ltokens.nextToken(); lnumread += 3; /***************************************************************************/ /* All the remaining elements of the URL are optional (see <draft-ietf- */ /* asid-ldapv3-url-04.txt> */ /* */ /* Set them to their default values, then decode the remainder of the URL */ /***************************************************************************/ m_host = DEFAULT_HOST; m_port = DEFAULT_PORT; m_dn = DEFAULT_DN; m_attrnames = DEFAULT_ATTRNAMES; m_scope = DEFAULT_SCOPE; m_filter = DEFAULT_FILTER; if (lnumread < lnumtokens) { /*************************************************************************/ /* We have tokens remaining, so read the next one. If it isn't "/", we */ /* must have hostport specified (it is optional). */ /*************************************************************************/ ltoken = ltokens.nextToken(); lnumread++; /*************************************************************************/ /* If ltoken is not '/', we must have hostport present, so we extract */ /* it. Otherwise use the default host & port. */ /*************************************************************************/ if (!(ltoken.equals("/"))) { /***********************************************************************/ /* Read in host; this must be present */ /***********************************************************************/ m_host = ltoken; /***********************************************************************/ /* Port may be present. If it is, the next tokens will be ":" and */ /* "<port number>" */ /***********************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals(":")) { /*********************************************************************/ /* Port is present; read it in. If it doesn't parse properly as a */ /* number, throw an exception. */ /*********************************************************************/ if (lnumread < lnumtokens) { try { m_port = Integer.parseInt(ltokens.nextToken()); lnumread++; } catch (NumberFormatException e) { throw new MalformedURLException("Invalid port specification"); } /*****************************************************************/ /* Read in the next token, if present, so when we exit this */ /* branch of the if statement we are in the same position as if */ /* we had gone through the other (e.g. about to read the DN). */ /* We verify that the next token (if present) is '/', throwing */ /* an exception if it isn't. */ /*****************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals("/")) { } else { throw new MalformedURLException(); } } } else { throw new MalformedURLException("Port not specified after \":\""); } } else if (!(ltoken.equals("/"))) { /*********************************************************************/ /* The token after hostname wasn't ":" and it wasn't "/", so this is */ /* a badly formed URL. */ /*********************************************************************/ throw new MalformedURLException("\":\" or \"/\" expected after host)"); } else { /*********************************************************************/ /* We have the host name, but port wasn't specified so use the */ /* default. */ /*********************************************************************/ } } else { /*********************************************************************/ /* No more tokens; use default port. */ /*********************************************************************/ } } else { /***********************************************************************/ /* Use default host and port */ /***********************************************************************/ } if (lnumread < lnumtokens) { /***********************************************************************/ /* If there are more tokens, then DN must be present (DN is optional */ /* only if there are no more tokens; if there are more tokens, the */ /* first must be DN) */ /* */ /* At this point in our example, we have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "/" */ /* lnumtokens = 15 */ /* lnumread = 8 */ /* */ /* If host and port had not been present, we would have at this point */ /* */ /* ldap:///c=GB?sn,cn?sub(cn=ISF) */ /* | */ /* current offset */ /* */ /* We can be sure that the next token will be the entire DN in URL */ /* encoded form, as any characters that might have confused the */ /* StringTokenizer should already be encoded (if this is a valid URL). */ /* */ /* If this is not a valid URL and dangerous characters are present in */ /* the DN, we hope to, but don't guarantee, that we catch them at a */ /* later point. Rigourous checking of the URL would require checking */ /* that all decoded attribute descriptions and filters conformed */ /* exactly to their respective syntaxes wrt allowed characters (e.g. */ /* no '=' in attribute descriptions). We consider checking that to be */ /* an unnecessary degree of rigour. */ /***********************************************************************/ ltoken = ltokens.nextToken(); lnumread++; m_dn = decode(ltoken); if (lnumread < lnumtokens) { /*********************************************************************/ /* BNF for LDAP URLS indicates if more tokens are present, the first */ /* must be "?". Ensure that it is. */ /*********************************************************************/ ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals("?")) { } else { throw new MalformedURLException(); } if (lnumread < lnumtokens) { /*******************************************************************/ /* There are more tokens. If the next is not "?", we have a list */ /* of attributes. Otherwise, we have an empty list (signifying all */ /* user attributes). */ /*******************************************************************/ ltoken = ltokens.nextToken(); lnumread++; if (!(ltoken.equals("?"))) { /*****************************************************************/ /* We have a list of attribute descriptions. The entire list is */ /* extracted as the next token, and we extract the attributes */ /* from this list with another StringTokenizer. */ /* */ /* At this point in our example, we have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* ltoken = "sn,cn" */ /* lnumtokens = 15 */ /* lnumread = 11 */ /* */ /*****************************************************************/ /*****************************************************************/ /* Attribute list non-empty, parse it into a String array */ /*****************************************************************/ lattrtokens = new StringTokenizer(ltoken, ","); lnumattrs = lattrtokens.countTokens(); m_attrnames = new String[lnumattrs]; /*****************************************************************/ /* Loop through attributes adding them to array */ /* */ /* In our example, we end up with the array { "sn", "cn" } */ /*****************************************************************/ lloop = 0; while (lloop < lnumattrs) { m_attrnames[lloop] = decode(lattrtokens.nextToken()); lloop++; } /*****************************************************************/ /* If more tokens are present, the next must be "?". Throw an */ /* exception otherwise. */ /*****************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals("?")) { } else { throw new MalformedURLException(); } } } else { /*****************************************************************/ /* Attribute list empty. */ /*****************************************************************/ m_attrnames = null; } /*******************************************************************/ /* Extract scope, if present */ /* */ /* At this point in our example, we have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "?" */ /* lnumtokens = 15 */ /* lnumread = 12 */ /* */ /*******************************************************************/ if (lnumread < lnumtokens) { /*****************************************************************/ /* If the next token is not "?", we have a scope specification */ /*****************************************************************/ ltoken = ltokens.nextToken(); lnumread++; if (!(ltoken.equals("?"))) { /***************************************************************/ /* Scope present */ /* */ /* At this point in our example, we have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "sub" */ /* lnumtokens = 15 */ /* lnumread = 13 */ /* */ /***************************************************************/ ldecodedtoken = decode(ltoken); if (ldecodedtoken.equals("base")) { m_scope = LDAPv2.SCOPE_BASE; } else if (ldecodedtoken.equals("one")) { m_scope = LDAPv2.SCOPE_ONE; } else if (ldecodedtoken.equals("sub")) { m_scope = LDAPv2.SCOPE_SUB; } else { throw new MalformedURLException(); } /***************************************************************/ /* If more tokens are present, the next must be "?". Throw an */ /* exception otherwise. */ /***************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals("?")) { } else { throw new MalformedURLException(); } } } else { /***************************************************************/ /* Scope not present, using scope base */ /***************************************************************/ m_scope = LDAPv2.SCOPE_BASE; } /*****************************************************************/ /* Extract filter, if present */ /*****************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (!(ltoken.equals("?"))) { /*************************************************************/ /* Filter present */ /* */ /* At this point in our example, we have */ /* */ /* ldap://myhost:389/c=GB?sn,cn?sub?(cn=ISF) */ /* | */ /* current offset */ /* */ /* ltoken = "(cn=ISF) */ /* lnumtokens = 15 */ /* lnumread = 15 */ /* */ /*************************************************************/ ldecodedtoken = decode(ltoken); m_filter = ldecodedtoken; /*************************************************************/ /* If more tokens are present, the next must be "?". Throw */ /* an exception otherwise. */ /*************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; if (ltoken.equals("?")) { } else { throw new MalformedURLException(); } } } else { /*************************************************************/ /* Filter not present, using default */ /*************************************************************/ m_filter = DEFAULT_FILTER; } /***************************************************************/ /* If there are any more tokens left, they are part of an */ /* extension specification. We don't know how to deal with */ /* any extentions. If they're not critical we discard them, */ /* and if they are critical we throw an exception saying we */ /* don't support any extentions. */ /***************************************************************/ if (lnumread < lnumtokens) { ltoken = ltokens.nextToken(); lnumread++; /*************************************************************/ /* If critical extension present, throw exception; we can't */ /* deal with it. */ /*************************************************************/ if (ltoken.indexOf("!") != -1) { throw new MalformedURLException( "Critical Extension not supported"); } /*************************************************************/ /* The previous token should have been the last; if the URL */ /* is valid any of the delimiter characters present in */ /* extension values would have been %HH escaped. Therefore, */ /* if there are any more tokens we don't have a well formed */ /* URL. */ /*************************************************************/ if (ltokens.hasMoreElements()) { throw new MalformedURLException(); } } } } } } } } } }