001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: /*
038: * @(#)URLName.java 1.19 07/05/04
039: */
040:
041: package javax.mail;
042:
043: import java.net.*;
044:
045: import java.io.ByteArrayOutputStream;
046: import java.io.OutputStreamWriter;
047: import java.io.IOException;
048: import java.io.UnsupportedEncodingException;
049: import java.util.BitSet;
050: import java.util.Locale;
051:
052: /**
053: * The name of a URL. This class represents a URL name and also
054: * provides the basic parsing functionality to parse most internet
055: * standard URL schemes. <p>
056: *
057: * Note that this class differs from <code>java.net.URL</code>
058: * in that this class just represents the name of a URL, it does
059: * not model the connection to a URL.
060: *
061: * @version 1.19, 07/05/04
062: * @author Christopher Cotton
063: * @author Bill Shannon
064: */
065:
066: public class URLName {
067:
068: /**
069: * The full version of the URL
070: */
071: protected String fullURL;
072:
073: /**
074: * The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) .
075: */
076: private String protocol;
077:
078: /**
079: * The username to use when connecting
080: */
081: private String username;
082:
083: /**
084: * The password to use when connecting.
085: */
086: private String password;
087:
088: /**
089: * The host name to which to connect.
090: */
091: private String host;
092:
093: /**
094: * The host's IP address, used in equals and hashCode.
095: * Computed on demand.
096: */
097: private InetAddress hostAddress;
098: private boolean hostAddressKnown = false;
099:
100: /**
101: * The protocol port to connect to.
102: */
103: private int port = -1;
104:
105: /**
106: * The specified file name on that host.
107: */
108: private String file;
109:
110: /**
111: * # reference.
112: */
113: private String ref;
114:
115: /**
116: * Our hash code.
117: */
118: private int hashCode = 0;
119:
120: /**
121: * A way to turn off encoding, just in case...
122: */
123: private static boolean doEncode = true;
124:
125: static {
126: try {
127: doEncode = !Boolean.getBoolean("mail.URLName.dontencode");
128: } catch (Exception ex) {
129: // ignore any errors
130: }
131: }
132:
133: /**
134: * Creates a URLName object from the specified protocol,
135: * host, port number, file, username, and password. Specifying a port
136: * number of -1 indicates that the URL should use the default port for
137: * the protocol.
138: */
139: public URLName(String protocol, String host, int port, String file,
140: String username, String password) {
141: this .protocol = protocol;
142: this .host = host;
143: this .port = port;
144: int refStart;
145: if (file != null && (refStart = file.indexOf('#')) != -1) {
146: this .file = file.substring(0, refStart);
147: this .ref = file.substring(refStart + 1);
148: } else {
149: this .file = file;
150: this .ref = null;
151: }
152: this .username = doEncode ? encode(username) : username;
153: this .password = doEncode ? encode(password) : password;
154: }
155:
156: /**
157: * Construct a URLName from a java.net.URL object.
158: */
159: public URLName(URL url) {
160: this (url.toString());
161: }
162:
163: /**
164: * Construct a URLName from the string. Parses out all the possible
165: * information (protocol, host, port, file, username, password).
166: */
167: public URLName(String url) {
168: parseString(url);
169: }
170:
171: /**
172: * Constructs a string representation of this URLName.
173: */
174: public String toString() {
175: if (fullURL == null) {
176: // add the "protocol:"
177: StringBuffer tempURL = new StringBuffer();
178: if (protocol != null) {
179: tempURL.append(protocol);
180: tempURL.append(":");
181: }
182:
183: if (username != null || host != null) {
184: // add the "//"
185: tempURL.append("//");
186:
187: // add the user:password@
188: // XXX - can you just have a password? without a username?
189: if (username != null) {
190: tempURL.append(username);
191:
192: if (password != null) {
193: tempURL.append(":");
194: tempURL.append(password);
195: }
196:
197: tempURL.append("@");
198: }
199:
200: // add host
201: if (host != null) {
202: tempURL.append(host);
203: }
204:
205: // add port (if needed)
206: if (port != -1) {
207: tempURL.append(":");
208: tempURL.append(Integer.toString(port));
209: }
210: if (file != null)
211: tempURL.append("/");
212: }
213:
214: // add the file
215: if (file != null) {
216: tempURL.append(file);
217: }
218:
219: // add the ref
220: if (ref != null) {
221: tempURL.append("#");
222: tempURL.append(ref);
223: }
224:
225: // create the fullURL now
226: fullURL = tempURL.toString();
227: }
228:
229: return fullURL;
230: }
231:
232: /**
233: * Method which does all of the work of parsing the string.
234: */
235: protected void parseString(String url) {
236: // initialize everything in case called from subclass
237: // (URLName really should be a final class)
238: protocol = file = ref = host = username = password = null;
239: port = -1;
240:
241: int len = url.length();
242:
243: // find the protocol
244: // XXX - should check for only legal characters before the colon
245: // (legal: a-z, A-Z, 0-9, "+", ".", "-")
246: int protocolEnd = url.indexOf(':');
247: if (protocolEnd != -1)
248: protocol = url.substring(0, protocolEnd);
249:
250: // is this an Internet standard URL that contains a host name?
251: if (url.regionMatches(protocolEnd + 1, "//", 0, 2)) {
252: // find where the file starts
253: String fullhost = null;
254: int fileStart = url.indexOf('/', protocolEnd + 3);
255: if (fileStart != -1) {
256: fullhost = url.substring(protocolEnd + 3, fileStart);
257: if (fileStart + 1 < len)
258: file = url.substring(fileStart + 1);
259: else
260: file = "";
261: } else
262: fullhost = url.substring(protocolEnd + 3);
263:
264: // examine the fullhost, for username password etc.
265: int i = fullhost.indexOf('@');
266: if (i != -1) {
267: String fulluserpass = fullhost.substring(0, i);
268: fullhost = fullhost.substring(i + 1);
269:
270: // get user and password
271: int passindex = fulluserpass.indexOf(':');
272: if (passindex != -1) {
273: username = fulluserpass.substring(0, passindex);
274: password = fulluserpass.substring(passindex + 1);
275: } else {
276: username = fulluserpass;
277: }
278: }
279:
280: // get the port (if there)
281: int portindex;
282: if (fullhost.length() > 0 && fullhost.charAt(0) == '[') {
283: // an IPv6 address?
284: portindex = fullhost
285: .indexOf(':', fullhost.indexOf(']'));
286: } else {
287: portindex = fullhost.indexOf(':');
288: }
289: if (portindex != -1) {
290: String portstring = fullhost.substring(portindex + 1);
291: if (portstring.length() > 0) {
292: try {
293: port = Integer.parseInt(portstring);
294: } catch (NumberFormatException nfex) {
295: port = -1;
296: }
297: }
298:
299: host = fullhost.substring(0, portindex);
300: } else {
301: host = fullhost;
302: }
303: } else {
304: if (protocolEnd + 1 < len)
305: file = url.substring(protocolEnd + 1);
306: }
307:
308: // extract the reference from the file name, if any
309: int refStart;
310: if (file != null && (refStart = file.indexOf('#')) != -1) {
311: ref = file.substring(refStart + 1);
312: file = file.substring(0, refStart);
313: }
314: }
315:
316: /**
317: * Returns the port number of this URLName.
318: * Returns -1 if the port is not set.
319: */
320: public int getPort() {
321: return port;
322: }
323:
324: /**
325: * Returns the protocol of this URLName.
326: * Returns null if this URLName has no protocol.
327: */
328: public String getProtocol() {
329: return protocol;
330: }
331:
332: /**
333: * Returns the file name of this URLName.
334: * Returns null if this URLName has no file name.
335: */
336: public String getFile() {
337: return file;
338: }
339:
340: /**
341: * Returns the reference of this URLName.
342: * Returns null if this URLName has no reference.
343: */
344: public String getRef() {
345: return ref;
346: }
347:
348: /**
349: * Returns the host of this URLName.
350: * Returns null if this URLName has no host.
351: */
352: public String getHost() {
353: return host;
354: }
355:
356: /**
357: * Returns the user name of this URLName.
358: * Returns null if this URLName has no user name.
359: */
360: public String getUsername() {
361: return doEncode ? decode(username) : username;
362: }
363:
364: /**
365: * Returns the password of this URLName.
366: * Returns null if this URLName has no password.
367: */
368: public String getPassword() {
369: return doEncode ? decode(password) : password;
370: }
371:
372: /**
373: * Constructs a URL from the URLName.
374: */
375: public URL getURL() throws MalformedURLException {
376: return new URL(getProtocol(), getHost(), getPort(), getFile());
377: }
378:
379: /**
380: * Compares two URLNames. The result is true if and only if the
381: * argument is not null and is a URLName object that represents the
382: * same URLName as this object. Two URLName objects are equal if
383: * they have the same protocol and the same host,
384: * the same port number on the host, the same username,
385: * and the same file on the host. The fields (host, username,
386: * file) are also considered the same if they are both
387: * null. <p>
388: *
389: * Hosts are considered equal if the names are equal (case independent)
390: * or if host name lookups for them both succeed and they both reference
391: * the same IP address. <p>
392: *
393: * Note that URLName has no knowledge of default port numbers for
394: * particular protocols, so "imap://host" and "imap://host:143"
395: * would not compare as equal. <p>
396: *
397: * Note also that the password field is not included in the comparison,
398: * nor is any reference field appended to the filename.
399: */
400: public boolean equals(Object obj) {
401: if (!(obj instanceof URLName))
402: return false;
403: URLName u2 = (URLName) obj;
404:
405: // compare protocols
406: if (u2.protocol == null || !u2.protocol.equals(protocol))
407: return false;
408:
409: // compare hosts
410: InetAddress a1 = getHostAddress(), a2 = u2.getHostAddress();
411: // if we have internet address for both, and they're not the same, fail
412: if (a1 != null && a2 != null) {
413: if (!a1.equals(a2))
414: return false;
415: // else, if we have host names for both, and they're not the same, fail
416: } else if (host != null && u2.host != null) {
417: if (!host.equalsIgnoreCase(u2.host))
418: return false;
419: // else, if not both null
420: } else if (host != u2.host) {
421: return false;
422: }
423: // at this point, hosts match
424:
425: // compare usernames
426: if (!(username == u2.username || (username != null && username
427: .equals(u2.username))))
428: return false;
429:
430: // Forget about password since it doesn't
431: // really denote a different store.
432:
433: // compare files
434: String f1 = file == null ? "" : file;
435: String f2 = u2.file == null ? "" : u2.file;
436:
437: if (!f1.equals(f2))
438: return false;
439:
440: // compare ports
441: if (port != u2.port)
442: return false;
443:
444: // all comparisons succeeded, they're equal
445: return true;
446: }
447:
448: /**
449: * Compute the hash code for this URLName.
450: */
451: public int hashCode() {
452: if (hashCode != 0)
453: return hashCode;
454: if (protocol != null)
455: hashCode += protocol.hashCode();
456: InetAddress addr = getHostAddress();
457: if (addr != null)
458: hashCode += addr.hashCode();
459: else if (host != null)
460: hashCode += host.toLowerCase(Locale.ENGLISH).hashCode();
461: if (username != null)
462: hashCode += username.hashCode();
463: if (file != null)
464: hashCode += file.hashCode();
465: hashCode += port;
466: return hashCode;
467: }
468:
469: /**
470: * Get the IP address of our host. Look up the
471: * name the first time and remember that we've done
472: * so, whether the lookup fails or not.
473: */
474: private synchronized InetAddress getHostAddress() {
475: if (hostAddressKnown)
476: return hostAddress;
477: if (host == null)
478: return null;
479: try {
480: hostAddress = InetAddress.getByName(host);
481: } catch (UnknownHostException ex) {
482: hostAddress = null;
483: }
484: hostAddressKnown = true;
485: return hostAddress;
486: }
487:
488: /**
489: * The class contains a utility method for converting a
490: * <code>String</code> into a MIME format called
491: * "<code>x-www-form-urlencoded</code>" format.
492: * <p>
493: * To convert a <code>String</code>, each character is examined in turn:
494: * <ul>
495: * <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
496: * '<code>A</code>' through '<code>Z</code>', '<code>0</code>'
497: * through '<code>9</code>', and ".", "-",
498: * "*", "_" remain the same.
499: * <li>The space character '<code> </code>' is converted into a
500: * plus sign '<code>+</code>'.
501: * <li>All other characters are converted into the 3-character string
502: * "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
503: * hexadecimal representation of the lower 8-bits of the character.
504: * </ul>
505: *
506: * @author Herb Jellinek
507: * @version 1.16, 10/23/99
508: * @since JDK1.0
509: */
510: static BitSet dontNeedEncoding;
511: static final int caseDiff = ('a' - 'A');
512:
513: /* The list of characters that are not encoded have been determined by
514: referencing O'Reilly's "HTML: The Definitive Guide" (page 164). */
515:
516: static {
517: dontNeedEncoding = new BitSet(256);
518: int i;
519: for (i = 'a'; i <= 'z'; i++) {
520: dontNeedEncoding.set(i);
521: }
522: for (i = 'A'; i <= 'Z'; i++) {
523: dontNeedEncoding.set(i);
524: }
525: for (i = '0'; i <= '9'; i++) {
526: dontNeedEncoding.set(i);
527: }
528: /* encoding a space to a + is done in the encode() method */
529: dontNeedEncoding.set(' ');
530: dontNeedEncoding.set('-');
531: dontNeedEncoding.set('_');
532: dontNeedEncoding.set('.');
533: dontNeedEncoding.set('*');
534: }
535:
536: /**
537: * Translates a string into <code>x-www-form-urlencoded</code> format.
538: *
539: * @param s <code>String</code> to be translated.
540: * @return the translated <code>String</code>.
541: */
542: static String encode(String s) {
543: if (s == null)
544: return null;
545: // the common case is no encoding is needed
546: for (int i = 0; i < s.length(); i++) {
547: int c = (int) s.charAt(i);
548: if (c == ' ' || !dontNeedEncoding.get(c))
549: return _encode(s);
550: }
551: return s;
552: }
553:
554: private static String _encode(String s) {
555: int maxBytesPerChar = 10;
556: StringBuffer out = new StringBuffer(s.length());
557: ByteArrayOutputStream buf = new ByteArrayOutputStream(
558: maxBytesPerChar);
559: OutputStreamWriter writer = new OutputStreamWriter(buf);
560:
561: for (int i = 0; i < s.length(); i++) {
562: int c = (int) s.charAt(i);
563: if (dontNeedEncoding.get(c)) {
564: if (c == ' ') {
565: c = '+';
566: }
567: out.append((char) c);
568: } else {
569: // convert to external encoding before hex conversion
570: try {
571: writer.write(c);
572: writer.flush();
573: } catch (IOException e) {
574: buf.reset();
575: continue;
576: }
577: byte[] ba = buf.toByteArray();
578: for (int j = 0; j < ba.length; j++) {
579: out.append('%');
580: char ch = Character
581: .forDigit((ba[j] >> 4) & 0xF, 16);
582: // converting to use uppercase letter as part of
583: // the hex value if ch is a letter.
584: if (Character.isLetter(ch)) {
585: ch -= caseDiff;
586: }
587: out.append(ch);
588: ch = Character.forDigit(ba[j] & 0xF, 16);
589: if (Character.isLetter(ch)) {
590: ch -= caseDiff;
591: }
592: out.append(ch);
593: }
594: buf.reset();
595: }
596: }
597:
598: return out.toString();
599: }
600:
601: /**
602: * The class contains a utility method for converting from
603: * a MIME format called "<code>x-www-form-urlencoded</code>"
604: * to a <code>String</code>
605: * <p>
606: * To convert to a <code>String</code>, each character is examined in turn:
607: * <ul>
608: * <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
609: * '<code>A</code>' through '<code>Z</code>', and '<code>0</code>'
610: * through '<code>9</code>' remain the same.
611: * <li>The plus sign '<code>+</code>'is converted into a
612: * space character '<code> </code>'.
613: * <li>The remaining characters are represented by 3-character
614: * strings which begin with the percent sign,
615: * "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
616: * hexadecimal representation of the lower 8-bits of the character.
617: * </ul>
618: *
619: * @author Mark Chamness
620: * @author Michael McCloskey
621: * @version 1.7, 10/22/99
622: * @since 1.2
623: */
624:
625: /**
626: * Decodes a "x-www-form-urlencoded"
627: * to a <tt>String</tt>.
628: * @param s the <code>String</code> to decode
629: * @return the newly decoded <code>String</code>
630: */
631: static String decode(String s) {
632: if (s == null)
633: return null;
634: if (indexOfAny(s, "+%") == -1)
635: return s; // the common case
636:
637: StringBuffer sb = new StringBuffer();
638: for (int i = 0; i < s.length(); i++) {
639: char c = s.charAt(i);
640: switch (c) {
641: case '+':
642: sb.append(' ');
643: break;
644: case '%':
645: try {
646: sb.append((char) Integer.parseInt(s.substring(
647: i + 1, i + 3), 16));
648: } catch (NumberFormatException e) {
649: throw new IllegalArgumentException();
650: }
651: i += 2;
652: break;
653: default:
654: sb.append(c);
655: break;
656: }
657: }
658: // Undo conversion to external encoding
659: String result = sb.toString();
660: try {
661: byte[] inputBytes = result.getBytes("8859_1");
662: result = new String(inputBytes);
663: } catch (UnsupportedEncodingException e) {
664: // The system should always have 8859_1
665: }
666: return result;
667: }
668:
669: /**
670: * Return the first index of any of the characters in "any" in "s",
671: * or -1 if none are found.
672: *
673: * This should be a method on String.
674: */
675: private static int indexOfAny(String s, String any) {
676: return indexOfAny(s, any, 0);
677: }
678:
679: private static int indexOfAny(String s, String any, int start) {
680: try {
681: int len = s.length();
682: for (int i = start; i < len; i++) {
683: if (any.indexOf(s.charAt(i)) >= 0)
684: return i;
685: }
686: return -1;
687: } catch (StringIndexOutOfBoundsException e) {
688: return -1;
689: }
690: }
691:
692: /*
693: // Do not remove, this is needed when testing new URL cases
694: public static void main(String[] argv) {
695: String [] testURLNames = {
696: "protocol://userid:password@host:119/file",
697: "http://funny/folder/file.html",
698: "http://funny/folder/file.html#ref",
699: "http://funny/folder/file.html#",
700: "http://funny/#ref",
701: "imap://jmr:secret@labyrinth//var/mail/jmr",
702: "nntp://fred@labyrinth:143/save/it/now.mbox",
703: "imap://jmr@labyrinth/INBOX",
704: "imap://labryrinth",
705: "imap://labryrinth/",
706: "file:",
707: "file:INBOX",
708: "file:/home/shannon/mail/foo",
709: "/tmp/foo",
710: "//host/tmp/foo",
711: ":/tmp/foo",
712: "/really/weird:/tmp/foo#bar",
713: ""
714: };
715:
716: URLName url =
717: new URLName("protocol", "host", 119, "file", "userid", "password");
718: System.out.println("Test URL: " + url.toString());
719: if (argv.length == 0) {
720: for (int i = 0; i < testURLNames.length; i++) {
721: print(testURLNames[i]);
722: System.out.println();
723: }
724: } else {
725: for (int i = 0; i < argv.length; i++) {
726: print(argv[i]);
727: System.out.println();
728: }
729: if (argv.length == 2) {
730: URLName u1 = new URLName(argv[0]);
731: URLName u2 = new URLName(argv[1]);
732: System.out.println("URL1 hash code: " + u1.hashCode());
733: System.out.println("URL2 hash code: " + u2.hashCode());
734: if (u1.equals(u2))
735: System.out.println("success, equal");
736: else
737: System.out.println("fail, not equal");
738: if (u2.equals(u1))
739: System.out.println("success, equal");
740: else
741: System.out.println("fail, not equal");
742: if (u1.hashCode() == u2.hashCode())
743: System.out.println("success, hashCodes equal");
744: else
745: System.out.println("fail, hashCodes not equal");
746: }
747: }
748: }
749:
750: private static void print(String name) {
751: URLName url = new URLName(name);
752: System.out.println("Original URL: " + name);
753: System.out.println("The fullUrl : " + url.toString());
754: if (!name.equals(url.toString()))
755: System.out.println(" : NOT EQUAL!");
756: System.out.println("The protocol is: " + url.getProtocol());
757: System.out.println("The host is: " + url.getHost());
758: System.out.println("The port is: " + url.getPort());
759: System.out.println("The user is: " + url.getUsername());
760: System.out.println("The password is: " + url.getPassword());
761: System.out.println("The file is: " + url.getFile());
762: System.out.println("The ref is: " + url.getRef());
763: }
764: */
765: }
|