/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/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 packager/legal/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. <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 */ protected String fullURL; /** * The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) . */ private String protocol; /** * The username to use when connecting */ private String username; /** * The password to use when connecting. */ private String password; /** * The host name to which to connect. */ private String host; /** * 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. */ private int port = -1; /** * The specified file name on that host. */ private String file; /** * # reference. */ private String ref; /** * Our hash code. */ private int hashCode = 0; /** * 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. */ 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. */ 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). */ public URLName(String url) { parseString(url); } /** * Constructs a string representation of this URLName. */ public String toString() { if (fullURL == null) { // add the "protocol:" StringBuffer tempURL = new StringBuffer(); 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. */ 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. */ public int getPort() { return port; } /** * Returns the protocol of this URLName. * Returns null if this URLName has no protocol. */ public String getProtocol() { return protocol; } /** * Returns the file name of this URLName. * Returns null if this URLName has no file name. */ public String getFile() { return file; } /** * Returns the reference of this URLName. * Returns null if this URLName has no reference. */ public String getRef() { return ref; } /** * Returns the host of this URLName. * Returns null if this URLName has no host. */ public String getHost() { return host; } /** * Returns the user name of this URLName. * Returns null if this URLName has no user name. */ public String getUsername() { return doEncode ? decode(username) : username; } /** * Returns the password of this URLName. * Returns null if this URLName has no password. */ public String getPassword() { return doEncode ? decode(password) : password; } /** * Constructs a URL from the URLName. */ public URL getURL() throws MalformedURLException { return new URL(getProtocol(), getHost(), getPort(), getFile()); } /** * 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. */ public boolean equals(Object obj) { if (!(obj instanceof URLName)) return false; URLName u2 = (URLName)obj; // compare protocols if (u2.protocol == null || !u2.protocol.equals(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. */ 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. */ 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 * <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 ".", "-", * "*", "_" remain the same. * <li>The space character '<code> </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 <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; StringBuffer out = new StringBuffer(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> </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 <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 StringBuffer sb = new StringBuffer(); 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. */ 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()); } */ }