/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail;

import java.net.*;

import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
import java.util.Locale;


The name of a URL. This class represents a URL name and also provides the basic parsing functionality to parse most internet standard URL schemes.

Note that this class differs from java.net.URL in that this class just represents the name of a URL, it does not model the connection to a URL.

Author: Christopher Cotton, Bill Shannon
/** * The name of a URL. This class represents a URL name and also * provides the basic parsing functionality to parse most internet * standard URL schemes. <p> * * Note that this class differs from <code>java.net.URL</code> * in that this class just represents the name of a URL, it does * not model the connection to a URL. * * @author Christopher Cotton * @author Bill Shannon */
public class URLName {
The full version of the URL
/** * The full version of the URL */
protected String fullURL;
The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) .
/** * The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) . */
private String protocol;
The username to use when connecting
/** * The username to use when connecting */
private String username;
The password to use when connecting.
/** * The password to use when connecting. */
private String password;
The host name to which to connect.
/** * The host name to which to connect. */
private String host;
The host's IP address, used in equals and hashCode. Computed on demand.
/** * The host's IP address, used in equals and hashCode. * Computed on demand. */
private InetAddress hostAddress; private boolean hostAddressKnown = false;
The protocol port to connect to.
/** * The protocol port to connect to. */
private int port = -1;
The specified file name on that host.
/** * The specified file name on that host. */
private String file;
# reference.
/** * # reference. */
private String ref;
Our hash code.
/** * Our hash code. */
private int hashCode = 0;
A way to turn off encoding, just in case...
/** * A way to turn off encoding, just in case... */
private static boolean doEncode = true; static { try { doEncode = !Boolean.getBoolean("mail.URLName.dontencode"); } catch (Exception ex) { // ignore any errors } }
Creates a URLName object from the specified protocol, host, port number, file, username, and password. Specifying a port number of -1 indicates that the URL should use the default port for the protocol.
Params:
  • protocol – the protocol
  • host – the host name
  • port – the port number
  • file – the file
  • username – the user name
  • password – the password
/** * Creates a URLName object from the specified protocol, * host, port number, file, username, and password. Specifying a port * number of -1 indicates that the URL should use the default port for * the protocol. * * @param protocol the protocol * @param host the host name * @param port the port number * @param file the file * @param username the user name * @param password the password */
public URLName( String protocol, String host, int port, String file, String username, String password ) { this.protocol = protocol; this.host = host; this.port = port; int refStart; if (file != null && (refStart = file.indexOf('#')) != -1) { this.file = file.substring(0, refStart); this.ref = file.substring(refStart + 1); } else { this.file = file; this.ref = null; } this.username = doEncode ? encode(username) : username; this.password = doEncode ? encode(password) : password; }
Construct a URLName from a java.net.URL object.
Params:
  • url – the URL
/** * Construct a URLName from a java.net.URL object. * * @param url the URL */
public URLName(URL url) { this(url.toString()); }
Construct a URLName from the string. Parses out all the possible information (protocol, host, port, file, username, password).
Params:
  • url – the URL string
/** * Construct a URLName from the string. Parses out all the possible * information (protocol, host, port, file, username, password). * * @param url the URL string */
public URLName(String url) { parseString(url); }
Constructs a string representation of this URLName.
/** * Constructs a string representation of this URLName. */
@Override public String toString() { if (fullURL == null) { // add the "protocol:" StringBuilder tempURL = new StringBuilder(); if (protocol != null) { tempURL.append(protocol); tempURL.append(":"); } if (username != null || host != null) { // add the "//" tempURL.append("//"); // add the user:password@ // XXX - can you just have a password? without a username? if (username != null) { tempURL.append(username); if (password != null){ tempURL.append(":"); tempURL.append(password); } tempURL.append("@"); } // add host if (host != null) { tempURL.append(host); } // add port (if needed) if (port != -1) { tempURL.append(":"); tempURL.append(Integer.toString(port)); } if (file != null) tempURL.append("/"); } // add the file if (file != null) { tempURL.append(file); } // add the ref if (ref != null) { tempURL.append("#"); tempURL.append(ref); } // create the fullURL now fullURL = tempURL.toString(); } return fullURL; }
Method which does all of the work of parsing the string.
Params:
  • url – the URL string to parse
/** * Method which does all of the work of parsing the string. * * @param url the URL string to parse */
protected void parseString(String url) { // initialize everything in case called from subclass // (URLName really should be a final class) protocol = file = ref = host = username = password = null; port = -1; int len = url.length(); // find the protocol // XXX - should check for only legal characters before the colon // (legal: a-z, A-Z, 0-9, "+", ".", "-") int protocolEnd = url.indexOf(':'); if (protocolEnd != -1) protocol = url.substring(0, protocolEnd); // is this an Internet standard URL that contains a host name? if (url.regionMatches(protocolEnd + 1, "//", 0, 2)) { // find where the file starts String fullhost = null; int fileStart = url.indexOf('/', protocolEnd + 3); if (fileStart != -1) { fullhost = url.substring(protocolEnd + 3, fileStart); if (fileStart + 1 < len) file = url.substring(fileStart + 1); else file = ""; } else fullhost = url.substring(protocolEnd + 3); // examine the fullhost, for username password etc. int i = fullhost.indexOf('@'); if (i != -1) { String fulluserpass = fullhost.substring(0, i); fullhost = fullhost.substring(i + 1); // get user and password int passindex = fulluserpass.indexOf(':'); if (passindex != -1) { username = fulluserpass.substring(0, passindex); password = fulluserpass.substring(passindex + 1); } else { username = fulluserpass; } } // get the port (if there) int portindex; if (fullhost.length() > 0 && fullhost.charAt(0) == '[') { // an IPv6 address? portindex = fullhost.indexOf(':', fullhost.indexOf(']')); } else { portindex = fullhost.indexOf(':'); } if (portindex != -1) { String portstring = fullhost.substring(portindex + 1); if (portstring.length() > 0) { try { port = Integer.parseInt(portstring); } catch (NumberFormatException nfex) { port = -1; } } host = fullhost.substring(0, portindex); } else { host = fullhost; } } else { if (protocolEnd + 1 < len) file = url.substring(protocolEnd + 1); } // extract the reference from the file name, if any int refStart; if (file != null && (refStart = file.indexOf('#')) != -1) { ref = file.substring(refStart + 1); file = file.substring(0, refStart); } }
Returns the port number of this URLName. Returns -1 if the port is not set.
Returns: the port number
/** * Returns the port number of this URLName. * Returns -1 if the port is not set. * * @return the port number */
public int getPort() { return port; }
Returns the protocol of this URLName. Returns null if this URLName has no protocol.
Returns: the protocol
/** * Returns the protocol of this URLName. * Returns null if this URLName has no protocol. * * @return the protocol */
public String getProtocol() { return protocol; }
Returns the file name of this URLName. Returns null if this URLName has no file name.
Returns: the file name of this URLName
/** * Returns the file name of this URLName. * Returns null if this URLName has no file name. * * @return the file name of this URLName */
public String getFile() { return file; }
Returns the reference of this URLName. Returns null if this URLName has no reference.
Returns: the reference part of the URLName
/** * Returns the reference of this URLName. * Returns null if this URLName has no reference. * * @return the reference part of the URLName */
public String getRef() { return ref; }
Returns the host of this URLName. Returns null if this URLName has no host.
Returns: the host name
/** * Returns the host of this URLName. * Returns null if this URLName has no host. * * @return the host name */
public String getHost() { return host; }
Returns the user name of this URLName. Returns null if this URLName has no user name.
Returns: the user name
/** * Returns the user name of this URLName. * Returns null if this URLName has no user name. * * @return the user name */
public String getUsername() { return doEncode ? decode(username) : username; }
Returns the password of this URLName. Returns null if this URLName has no password.
Returns: the password
/** * Returns the password of this URLName. * Returns null if this URLName has no password. * * @return the password */
public String getPassword() { return doEncode ? decode(password) : password; }
Constructs a URL from the URLName.
Throws:
Returns: the URL
/** * Constructs a URL from the URLName. * * @return the URL * @exception MalformedURLException if the URL is malformed */
public URL getURL() throws MalformedURLException { // URL expects the file to include the separating "/" String f = getFile(); if (f == null) f = ""; else f = "/" + f; return new URL(getProtocol(), getHost(), getPort(), f); }
Compares two URLNames. The result is true if and only if the argument is not null and is a URLName object that represents the same URLName as this object. Two URLName objects are equal if they have the same protocol and the same host, the same port number on the host, the same username, and the same file on the host. The fields (host, username, file) are also considered the same if they are both null.

Hosts are considered equal if the names are equal (case independent) or if host name lookups for them both succeed and they both reference the same IP address.

Note that URLName has no knowledge of default port numbers for particular protocols, so "imap://host" and "imap://host:143" would not compare as equal.

Note also that the password field is not included in the comparison, nor is any reference field appended to the filename.

/** * Compares two URLNames. The result is true if and only if the * argument is not null and is a URLName object that represents the * same URLName as this object. Two URLName objects are equal if * they have the same protocol and the same host, * the same port number on the host, the same username, * and the same file on the host. The fields (host, username, * file) are also considered the same if they are both * null. <p> * * Hosts are considered equal if the names are equal (case independent) * or if host name lookups for them both succeed and they both reference * the same IP address. <p> * * Note that URLName has no knowledge of default port numbers for * particular protocols, so "imap://host" and "imap://host:143" * would not compare as equal. <p> * * Note also that the password field is not included in the comparison, * nor is any reference field appended to the filename. */
@Override public boolean equals(Object obj) { if (!(obj instanceof URLName)) return false; URLName u2 = (URLName)obj; // compare protocols if (!(protocol == u2.protocol || (protocol != null && protocol.equals(u2.protocol)))) return false; // compare hosts InetAddress a1 = getHostAddress(), a2 = u2.getHostAddress(); // if we have internet address for both, and they're not the same, fail if (a1 != null && a2 != null) { if (!a1.equals(a2)) return false; // else, if we have host names for both, and they're not the same, fail } else if (host != null && u2.host != null) { if (!host.equalsIgnoreCase(u2.host)) return false; // else, if not both null } else if (host != u2.host) { return false; } // at this point, hosts match // compare usernames if (!(username == u2.username || (username != null && username.equals(u2.username)))) return false; // Forget about password since it doesn't // really denote a different store. // compare files String f1 = file == null ? "" : file; String f2 = u2.file == null ? "" : u2.file; if (!f1.equals(f2)) return false; // compare ports if (port != u2.port) return false; // all comparisons succeeded, they're equal return true; }
Compute the hash code for this URLName.
/** * Compute the hash code for this URLName. */
@Override public int hashCode() { if (hashCode != 0) return hashCode; if (protocol != null) hashCode += protocol.hashCode(); InetAddress addr = getHostAddress(); if (addr != null) hashCode += addr.hashCode(); else if (host != null) hashCode += host.toLowerCase(Locale.ENGLISH).hashCode(); if (username != null) hashCode += username.hashCode(); if (file != null) hashCode += file.hashCode(); hashCode += port; return hashCode; }
Get the IP address of our host. Look up the name the first time and remember that we've done so, whether the lookup fails or not.
/** * Get the IP address of our host. Look up the * name the first time and remember that we've done * so, whether the lookup fails or not. */
private synchronized InetAddress getHostAddress() { if (hostAddressKnown) return hostAddress; if (host == null) return null; try { hostAddress = InetAddress.getByName(host); } catch (UnknownHostException ex) { hostAddress = null; } hostAddressKnown = true; return hostAddress; }
The class contains a utility method for converting a String into a MIME format called "x-www-form-urlencoded" format.

To convert a String, each character is examined in turn:

  • The ASCII characters 'a' through 'z', 'A' through 'Z', '0' through '9', and ".", "-", "*", "_" remain the same.
  • The space character ' ' is converted into a plus sign '+'.
  • All other characters are converted into the 3-character string "%xy", where xy is the two-digit hexadecimal representation of the lower 8-bits of the character.
Author: Herb Jellinek
Since: JDK1.0
/** * The class contains a utility method for converting a * <code>String</code> into a MIME format called * "<code>x-www-form-urlencoded</code>" format. * <p> * To convert a <code>String</code>, each character is examined in turn: * <ul> * <li>The ASCII characters '<code>a</code>' through '<code>z</code>', * '<code>A</code>' through '<code>Z</code>', '<code>0</code>' * through '<code>9</code>', and &quot;.&quot;, &quot;-&quot;, * &quot;*&quot;, &quot;_&quot; remain the same. * <li>The space character '<code>&nbsp;</code>' is converted into a * plus sign '<code>+</code>'. * <li>All other characters are converted into the 3-character string * "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit * hexadecimal representation of the lower 8-bits of the character. * </ul> * * @author Herb Jellinek * @since JDK1.0 */
static BitSet dontNeedEncoding; static final int caseDiff = ('a' - 'A'); /* The list of characters that are not encoded have been determined by referencing O'Reilly's "HTML: The Definitive Guide" (page 164). */ static { dontNeedEncoding = new BitSet(256); int i; for (i = 'a'; i <= 'z'; i++) { dontNeedEncoding.set(i); } for (i = 'A'; i <= 'Z'; i++) { dontNeedEncoding.set(i); } for (i = '0'; i <= '9'; i++) { dontNeedEncoding.set(i); } /* encoding a space to a + is done in the encode() method */ dontNeedEncoding.set(' '); dontNeedEncoding.set('-'); dontNeedEncoding.set('_'); dontNeedEncoding.set('.'); dontNeedEncoding.set('*'); }
Translates a string into x-www-form-urlencoded format.
Params:
  • s – String to be translated.
Returns: the translated String.
/** * Translates a string into <code>x-www-form-urlencoded</code> format. * * @param s <code>String</code> to be translated. * @return the translated <code>String</code>. */
static String encode(String s) { if (s == null) return null; // the common case is no encoding is needed for (int i = 0; i < s.length(); i++) { int c = (int)s.charAt(i); if (c == ' ' || !dontNeedEncoding.get(c)) return _encode(s); } return s; } private static String _encode(String s) { int maxBytesPerChar = 10; StringBuilder out = new StringBuilder(s.length()); ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); OutputStreamWriter writer = new OutputStreamWriter(buf); for (int i = 0; i < s.length(); i++) { int c = (int)s.charAt(i); if (dontNeedEncoding.get(c)) { if (c == ' ') { c = '+'; } out.append((char)c); } else { // convert to external encoding before hex conversion try { writer.write(c); writer.flush(); } catch(IOException e) { buf.reset(); continue; } byte[] ba = buf.toByteArray(); for (int j = 0; j < ba.length; j++) { out.append('%'); char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); // converting to use uppercase letter as part of // the hex value if ch is a letter. if (Character.isLetter(ch)) { ch -= caseDiff; } out.append(ch); ch = Character.forDigit(ba[j] & 0xF, 16); if (Character.isLetter(ch)) { ch -= caseDiff; } out.append(ch); } buf.reset(); } } return out.toString(); } /** * The class contains a utility method for converting from * a MIME format called "<code>x-www-form-urlencoded</code>" * to a <code>String</code> * <p> * To convert to a <code>String</code>, each character is examined in turn: * <ul> * <li>The ASCII characters '<code>a</code>' through '<code>z</code>', * '<code>A</code>' through '<code>Z</code>', and '<code>0</code>' * through '<code>9</code>' remain the same. * <li>The plus sign '<code>+</code>'is converted into a * space character '<code>&nbsp;</code>'. * <li>The remaining characters are represented by 3-character * strings which begin with the percent sign, * "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit * hexadecimal representation of the lower 8-bits of the character. * </ul> * * @author Mark Chamness * @author Michael McCloskey * @since 1.2 */
Decodes a "x-www-form-urlencoded" to a String.
Params:
  • s – the String to decode
Returns:the newly decoded String
/** * Decodes a &quot;x-www-form-urlencoded&quot; * to a <tt>String</tt>. * @param s the <code>String</code> to decode * @return the newly decoded <code>String</code> */
static String decode(String s) { if (s == null) return null; if (indexOfAny(s, "+%") == -1) return s; // the common case StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '+': sb.append(' '); break; case '%': try { sb.append((char)Integer.parseInt( s.substring(i+1,i+3),16)); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Illegal URL encoded value: " + s.substring(i,i+3)); } i += 2; break; default: sb.append(c); break; } } // Undo conversion to external encoding String result = sb.toString(); try { byte[] inputBytes = result.getBytes("8859_1"); result = new String(inputBytes); } catch (UnsupportedEncodingException e) { // The system should always have 8859_1 } return result; }
Return the first index of any of the characters in "any" in "s", or -1 if none are found. This should be a method on String.
/** * Return the first index of any of the characters in "any" in "s", * or -1 if none are found. * * This should be a method on String. */
private static int indexOfAny(String s, String any) { return indexOfAny(s, any, 0); } private static int indexOfAny(String s, String any, int start) { try { int len = s.length(); for (int i = start; i < len; i++) { if (any.indexOf(s.charAt(i)) >= 0) return i; } return -1; } catch (StringIndexOutOfBoundsException e) { return -1; } } /* // Do not remove, this is needed when testing new URL cases public static void main(String[] argv) { String [] testURLNames = { "protocol://userid:password@host:119/file", "http://funny/folder/file.html", "http://funny/folder/file.html#ref", "http://funny/folder/file.html#", "http://funny/#ref", "imap://jmr:secret@labyrinth//var/mail/jmr", "nntp://fred@labyrinth:143/save/it/now.mbox", "imap://jmr@labyrinth/INBOX", "imap://labryrinth", "imap://labryrinth/", "file:", "file:INBOX", "file:/home/shannon/mail/foo", "/tmp/foo", "//host/tmp/foo", ":/tmp/foo", "/really/weird:/tmp/foo#bar", "" }; URLName url = new URLName("protocol", "host", 119, "file", "userid", "password"); System.out.println("Test URL: " + url.toString()); if (argv.length == 0) { for (int i = 0; i < testURLNames.length; i++) { print(testURLNames[i]); System.out.println(); } } else { for (int i = 0; i < argv.length; i++) { print(argv[i]); System.out.println(); } if (argv.length == 2) { URLName u1 = new URLName(argv[0]); URLName u2 = new URLName(argv[1]); System.out.println("URL1 hash code: " + u1.hashCode()); System.out.println("URL2 hash code: " + u2.hashCode()); if (u1.equals(u2)) System.out.println("success, equal"); else System.out.println("fail, not equal"); if (u2.equals(u1)) System.out.println("success, equal"); else System.out.println("fail, not equal"); if (u1.hashCode() == u2.hashCode()) System.out.println("success, hashCodes equal"); else System.out.println("fail, hashCodes not equal"); } } } private static void print(String name) { URLName url = new URLName(name); System.out.println("Original URL: " + name); System.out.println("The fullUrl : " + url.toString()); if (!name.equals(url.toString())) System.out.println(" : NOT EQUAL!"); System.out.println("The protocol is: " + url.getProtocol()); System.out.println("The host is: " + url.getHost()); System.out.println("The port is: " + url.getPort()); System.out.println("The user is: " + url.getUsername()); System.out.println("The password is: " + url.getPassword()); System.out.println("The file is: " + url.getFile()); System.out.println("The ref is: " + url.getRef()); } */ }