KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tomcat > util > net > URL


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.tomcat.util.net;
19
20
21 import java.io.Serializable JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23
24
25 /**
26  * <p><strong>URL</strong> is designed to provide public APIs for parsing
27  * and synthesizing Uniform Resource Locators as similar as possible to the
28  * APIs of <code>java.net.URL</code>, but without the ability to open a
29  * stream or connection. One of the consequences of this is that you can
30  * construct URLs for protocols for which a URLStreamHandler is not
31  * available (such as an "https" URL when JSSE is not installed).</p>
32  *
33  * <p><strong>WARNING</strong> - This class assumes that the string
34  * representation of a URL conforms to the <code>spec</code> argument
35  * as described in RFC 2396 "Uniform Resource Identifiers: Generic Syntax":
36  * <pre>
37  * &lt;scheme&gt;//&lt;authority&gt;&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt;
38  * </pre></p>
39  *
40  * <p><strong>FIXME</strong> - This class really ought to end up in a Commons
41  * package someplace.</p>
42  *
43  * @author Craig R. McClanahan
44  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
45  */

46
47 public final class URL implements Serializable JavaDoc {
48
49
50     // ----------------------------------------------------------- Constructors
51

52
53     /**
54      * Create a URL object from the specified String representation.
55      *
56      * @param spec String representation of the URL
57      *
58      * @exception MalformedURLException if the string representation
59      * cannot be parsed successfully
60      */

61     public URL(String JavaDoc spec) throws MalformedURLException JavaDoc {
62
63         this(null, spec);
64
65     }
66
67
68     /**
69      * Create a URL object by parsing a string representation relative
70      * to a specified context. Based on logic from JDK 1.3.1's
71      * <code>java.net.URL</code>.
72      *
73      * @param context URL against which the relative representation
74      * is resolved
75      * @param spec String representation of the URL (usually relative)
76      *
77      * @exception MalformedURLException if the string representation
78      * cannot be parsed successfully
79      */

80     public URL(URL context, String JavaDoc spec) throws MalformedURLException JavaDoc {
81
82         String JavaDoc original = spec;
83         int i, limit, c;
84         int start = 0;
85         String JavaDoc newProtocol = null;
86         boolean aRef = false;
87
88         try {
89
90             // Eliminate leading and trailing whitespace
91
limit = spec.length();
92             while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
93                 limit--;
94             }
95             while ((start < limit) && (spec.charAt(start) <= ' ')) {
96                 start++;
97             }
98
99             // If the string representation starts with "url:", skip it
100
if (spec.regionMatches(true, start, "url:", 0, 4)) {
101                 start += 4;
102             }
103
104             // Is this a ref relative to the context URL?
105
if ((start < spec.length()) && (spec.charAt(start) == '#')) {
106                 aRef = true;
107             }
108
109             // Parse out the new protocol
110
for (i = start; !aRef && (i < limit) ; i++) {
111                 c = spec.charAt(i);
112                 if (c == ':') {
113                     String JavaDoc s = spec.substring(start, i).toLowerCase();
114                     // Assume all protocols are valid
115
newProtocol = s;
116                     start = i + 1;
117                     break;
118                 } else if( c == '#' ) {
119                     aRef = true;
120                 } else if( !isSchemeChar((char)c) ) {
121                     break;
122                 }
123             }
124
125             // Only use our context if the protocols match
126
protocol = newProtocol;
127             if ((context != null) && ((newProtocol == null) ||
128                  newProtocol.equalsIgnoreCase(context.getProtocol()))) {
129                 // If the context is a hierarchical URL scheme and the spec
130
// contains a matching scheme then maintain backwards
131
// compatibility and treat it as if the spec didn't contain
132
// the scheme; see 5.2.3 of RFC2396
133
if ((context.getPath() != null) &&
134                     (context.getPath().startsWith("/")))
135                     newProtocol = null;
136                 if (newProtocol == null) {
137                     protocol = context.getProtocol();
138                     authority = context.getAuthority();
139                     userInfo = context.getUserInfo();
140                     host = context.getHost();
141                     port = context.getPort();
142                     file = context.getFile();
143                     int question = file.lastIndexOf("?");
144                     if (question < 0)
145                         path = file;
146                     else
147                         path = file.substring(0, question);
148                 }
149             }
150
151             if (protocol == null)
152                 throw new MalformedURLException JavaDoc("no protocol: " + original);
153
154             // Parse out any ref portion of the spec
155
i = spec.indexOf('#', start);
156             if (i >= 0) {
157                 ref = spec.substring(i + 1, limit);
158                 limit = i;
159             }
160
161             // Parse the remainder of the spec in a protocol-specific fashion
162
parse(spec, start, limit);
163             if (context != null)
164                 normalize();
165
166
167         } catch (MalformedURLException JavaDoc e) {
168             throw e;
169         } catch (Exception JavaDoc e) {
170             throw new MalformedURLException JavaDoc(e.toString());
171         }
172
173     }
174
175
176
177
178
179     /**
180      * Create a URL object from the specified components. The default port
181      * number for the specified protocol will be used.
182      *
183      * @param protocol Name of the protocol to use
184      * @param host Name of the host addressed by this protocol
185      * @param file Filename on the specified host
186      *
187      * @exception MalformedURLException is never thrown, but present for
188      * compatible APIs
189      */

190     public URL(String JavaDoc protocol, String JavaDoc host, String JavaDoc file)
191         throws MalformedURLException JavaDoc {
192
193         this(protocol, host, -1, file);
194
195     }
196
197
198     /**
199      * Create a URL object from the specified components. Specifying a port
200      * number of -1 indicates that the URL should use the default port for
201      * that protocol. Based on logic from JDK 1.3.1's
202      * <code>java.net.URL</code>.
203      *
204      * @param protocol Name of the protocol to use
205      * @param host Name of the host addressed by this protocol
206      * @param port Port number, or -1 for the default port for this protocol
207      * @param file Filename on the specified host
208      *
209      * @exception MalformedURLException is never thrown, but present for
210      * compatible APIs
211      */

212     public URL(String JavaDoc protocol, String JavaDoc host, int port, String JavaDoc file)
213         throws MalformedURLException JavaDoc {
214
215         this.protocol = protocol;
216         this.host = host;
217         this.port = port;
218
219         int hash = file.indexOf('#');
220         this.file = hash < 0 ? file : file.substring(0, hash);
221         this.ref = hash < 0 ? null : file.substring(hash + 1);
222         int question = file.lastIndexOf('?');
223         if (question >= 0) {
224             query = file.substring(question + 1);
225             path = file.substring(0, question);
226         } else
227             path = file;
228
229         if ((host != null) && (host.length() > 0))
230             authority = (port == -1) ? host : host + ":" + port;
231
232     }
233
234
235     // ----------------------------------------------------- Instance Variables
236

237
238     /**
239      * The authority part of the URL.
240      */

241     private String JavaDoc authority = null;
242
243
244     /**
245      * The filename part of the URL.
246      */

247     private String JavaDoc file = null;
248
249
250     /**
251      * The host name part of the URL.
252      */

253     private String JavaDoc host = null;
254
255
256     /**
257      * The path part of the URL.
258      */

259     private String JavaDoc path = null;
260
261
262     /**
263      * The port number part of the URL.
264      */

265     private int port = -1;
266
267
268     /**
269      * The protocol name part of the URL.
270      */

271     private String JavaDoc protocol = null;
272
273
274     /**
275      * The query part of the URL.
276      */

277     private String JavaDoc query = null;
278
279
280     /**
281      * The reference part of the URL.
282      */

283     private String JavaDoc ref = null;
284
285
286     /**
287      * The user info part of the URL.
288      */

289     private String JavaDoc userInfo = null;
290
291
292     // --------------------------------------------------------- Public Methods
293

294
295     /**
296      * Compare two URLs for equality. The result is <code>true</code> if and
297      * only if the argument is not null, and is a <code>URL</code> object
298      * that represents the same <code>URL</code> as this object. Two
299      * <code>URLs</code> are equal if they have the same protocol and
300      * reference the same host, the same port number on the host,
301      * and the same file and anchor on the host.
302      *
303      * @param obj The URL to compare against
304      */

305     public boolean equals(Object JavaDoc obj) {
306
307         if (obj == null)
308             return (false);
309         if (!(obj instanceof URL))
310             return (false);
311         URL other = (URL) obj;
312         if (!sameFile(other))
313             return (false);
314         return (compare(ref, other.getRef()));
315
316     }
317
318
319     /**
320      * Return the authority part of the URL.
321      */

322     public String JavaDoc getAuthority() {
323
324         return (this.authority);
325
326     }
327
328
329     /**
330      * Return the filename part of the URL. <strong>NOTE</strong> - For
331      * compatibility with <code>java.net.URL</code>, this value includes
332      * the query string if there was one. For just the path portion,
333      * call <code>getPath()</code> instead.
334      */

335     public String JavaDoc getFile() {
336
337         if (file == null)
338             return ("");
339         return (this.file);
340
341     }
342
343
344     /**
345      * Return the host name part of the URL.
346      */

347     public String JavaDoc getHost() {
348
349         return (this.host);
350
351     }
352
353
354     /**
355      * Return the path part of the URL.
356      */

357     public String JavaDoc getPath() {
358
359         if (this.path == null)
360             return ("");
361         return (this.path);
362
363     }
364
365
366     /**
367      * Return the port number part of the URL.
368      */

369     public int getPort() {
370
371         return (this.port);
372
373     }
374
375
376     /**
377      * Return the protocol name part of the URL.
378      */

379     public String JavaDoc getProtocol() {
380
381         return (this.protocol);
382
383     }
384
385
386     /**
387      * Return the query part of the URL.
388      */

389     public String JavaDoc getQuery() {
390
391         return (this.query);
392
393     }
394
395
396     /**
397      * Return the reference part of the URL.
398      */

399     public String JavaDoc getRef() {
400
401         return (this.ref);
402
403     }
404
405
406     /**
407      * Return the user info part of the URL.
408      */

409     public String JavaDoc getUserInfo() {
410
411         return (this.userInfo);
412
413     }
414
415
416     /**
417      * Normalize the <code>path</code> (and therefore <code>file</code>)
418      * portions of this URL.
419      * <p>
420      * <strong>NOTE</strong> - This method is not part of the public API
421      * of <code>java.net.URL</code>, but is provided as a value added
422      * service of this implementation.
423      *
424      * @exception MalformedURLException if a normalization error occurs,
425      * such as trying to move about the hierarchical root
426      */

427     public void normalize() throws MalformedURLException JavaDoc {
428
429         // Special case for null path
430
if (path == null) {
431             if (query != null)
432                 file = "?" + query;
433             else
434                 file = "";
435             return;
436         }
437
438         // Create a place for the normalized path
439
String JavaDoc normalized = path;
440         if (normalized.equals("/.")) {
441             path = "/";
442             if (query != null)
443                 file = path + "?" + query;
444             else
445                 file = path;
446             return;
447         }
448
449         // Normalize the slashes and add leading slash if necessary
450
if (normalized.indexOf('\\') >= 0)
451             normalized = normalized.replace('\\', '/');
452         if (!normalized.startsWith("/"))
453             normalized = "/" + normalized;
454
455         // Resolve occurrences of "//" in the normalized path
456
while (true) {
457             int index = normalized.indexOf("//");
458             if (index < 0)
459                 break;
460             normalized = normalized.substring(0, index) +
461                 normalized.substring(index + 1);
462         }
463
464         // Resolve occurrences of "/./" in the normalized path
465
while (true) {
466             int index = normalized.indexOf("/./");
467             if (index < 0)
468                 break;
469             normalized = normalized.substring(0, index) +
470                 normalized.substring(index + 2);
471         }
472
473         // Resolve occurrences of "/../" in the normalized path
474
while (true) {
475             int index = normalized.indexOf("/../");
476             if (index < 0)
477                 break;
478             if (index == 0)
479                 throw new MalformedURLException JavaDoc
480                     ("Invalid relative URL reference");
481             int index2 = normalized.lastIndexOf('/', index - 1);
482             normalized = normalized.substring(0, index2) +
483                 normalized.substring(index + 3);
484         }
485
486         // Resolve occurrences of "/." at the end of the normalized path
487
if (normalized.endsWith("/."))
488             normalized = normalized.substring(0, normalized.length() - 1);
489
490         // Resolve occurrences of "/.." at the end of the normalized path
491
if (normalized.endsWith("/..")) {
492             int index = normalized.length() - 3;
493             int index2 = normalized.lastIndexOf('/', index - 1);
494             if (index2 < 0)
495                 throw new MalformedURLException JavaDoc
496                     ("Invalid relative URL reference");
497             normalized = normalized.substring(0, index2 + 1);
498         }
499
500         // Return the normalized path that we have completed
501
path = normalized;
502         if (query != null)
503             file = path + "?" + query;
504         else
505             file = path;
506
507     }
508
509
510     /**
511      * Compare two URLs, excluding the "ref" fields. Returns <code>true</code>
512      * if this <code>URL</code> and the <code>other</code> argument both refer
513      * to the same resource. The two <code>URLs</code> might not both contain
514      * the same anchor.
515      */

516     public boolean sameFile(URL other) {
517
518         if (!compare(protocol, other.getProtocol()))
519             return (false);
520         if (!compare(host, other.getHost()))
521             return (false);
522         if (port != other.getPort())
523             return (false);
524         if (!compare(file, other.getFile()))
525             return (false);
526         return (true);
527
528     }
529
530
531     /**
532      * Return a string representation of this URL. This follow the rules in
533      * RFC 2396, Section 5.2, Step 7.
534      */

535     public String JavaDoc toExternalForm() {
536
537         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
538         if (protocol != null) {
539             sb.append(protocol);
540             sb.append(":");
541         }
542         if (authority != null) {
543             sb.append("//");
544             sb.append(authority);
545         }
546         if (path != null)
547             sb.append(path);
548         if (query != null) {
549             sb.append('?');
550             sb.append(query);
551         }
552         if (ref != null) {
553             sb.append('#');
554             sb.append(ref);
555         }
556         return (sb.toString());
557
558     }
559
560
561     /**
562      * Return a string representation of this object.
563      */

564     public String JavaDoc toString() {
565
566         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("URL[");
567         sb.append("authority=");
568         sb.append(authority);
569         sb.append(", file=");
570         sb.append(file);
571         sb.append(", host=");
572         sb.append(host);
573         sb.append(", port=");
574         sb.append(port);
575         sb.append(", protocol=");
576         sb.append(protocol);
577         sb.append(", query=");
578         sb.append(query);
579         sb.append(", ref=");
580         sb.append(ref);
581         sb.append(", userInfo=");
582         sb.append(userInfo);
583         sb.append("]");
584         return (sb.toString());
585
586         // return (toExternalForm());
587

588     }
589
590
591     // -------------------------------------------------------- Private Methods
592

593
594     /**
595      * Compare to String values for equality, taking appropriate care if one
596      * or both of the values are <code>null</code>.
597      *
598      * @param first First string
599      * @param second Second string
600      */

601     private boolean compare(String JavaDoc first, String JavaDoc second) {
602
603         if (first == null) {
604             if (second == null)
605                 return (true);
606             else
607                 return (false);
608         } else {
609             if (second == null)
610                 return (false);
611             else
612                 return (first.equals(second));
613         }
614
615     }
616
617
618     /**
619      * Parse the specified portion of the string representation of a URL,
620      * assuming that it has a format similar to that for <code>http</code>.
621      *
622      * <p><strong>FIXME</strong> - This algorithm can undoubtedly be optimized
623      * for performance. However, that needs to wait until after sufficient
624      * unit tests are implemented to guarantee correct behavior with no
625      * regressions.</p>
626      *
627      * @param spec String representation being parsed
628      * @param start Starting offset, which will be just after the ':' (if
629      * there is one) that determined the protocol name
630      * @param limit Ending position, which will be the position of the '#'
631      * (if there is one) that delimited the anchor
632      *
633      * @exception MalformedURLException if a parsing error occurs
634      */

635     private void parse(String JavaDoc spec, int start, int limit)
636         throws MalformedURLException JavaDoc {
637
638         // Trim the query string (if any) off the tail end
639
int question = spec.lastIndexOf('?', limit - 1);
640         if ((question >= 0) && (question < limit)) {
641             query = spec.substring(question + 1, limit);
642             limit = question;
643         } else {
644             query = null;
645         }
646
647         // Parse the authority section
648
if (spec.indexOf("//", start) == start) {
649             int pathStart = spec.indexOf("/", start + 2);
650             if ((pathStart >= 0) && (pathStart < limit)) {
651                 authority = spec.substring(start + 2, pathStart);
652                 start = pathStart;
653             } else {
654                 authority = spec.substring(start + 2, limit);
655                 start = limit;
656             }
657             if (authority.length() > 0) {
658                 int at = authority.indexOf('@');
659                 if( at >= 0 ) {
660                     userInfo = authority.substring(0,at);
661                 }
662                 int ipv6 = authority.indexOf('[',at+1);
663                 int hStart = at+1;
664                 if( ipv6 >= 0 ) {
665                     hStart = ipv6;
666                     ipv6 = authority.indexOf(']', ipv6);
667                     if( ipv6 < 0 ) {
668                         throw new MalformedURLException JavaDoc(
669                                                         "Closing ']' not found in IPV6 address: " + authority);
670                     } else {
671                         at = ipv6-1;
672                     }
673                 }
674                                                         
675                 int colon = authority.indexOf(':', at+1);
676                 if (colon >= 0) {
677                     try {
678                         port =
679                             Integer.parseInt(authority.substring(colon + 1));
680                     } catch (NumberFormatException JavaDoc e) {
681                         throw new MalformedURLException JavaDoc(e.toString());
682                     }
683                     host = authority.substring(hStart, colon);
684                 } else {
685                     host = authority.substring(hStart);
686                     port = -1;
687                 }
688             }
689         }
690
691         // Parse the path section
692
if (spec.indexOf("/", start) == start) { // Absolute path
693
path = spec.substring(start, limit);
694             if (query != null)
695                 file = path + "?" + query;
696             else
697                 file = path;
698             return;
699         }
700
701         // Resolve relative path against our context's file
702
if (path == null) {
703             if (query != null)
704                 file = "?" + query;
705             else
706                 file = null;
707             return;
708         }
709         if (!path.startsWith("/"))
710             throw new MalformedURLException JavaDoc
711                 ("Base path does not start with '/'");
712         if (!path.endsWith("/"))
713             path += "/../";
714         path += spec.substring(start, limit);
715         if (query != null)
716             file = path + "?" + query;
717         else
718             file = path;
719         return;
720
721     }
722
723     /**
724      * Determine if the character is allowed in the scheme of a URI.
725      * See RFC 2396, Section 3.1
726      */

727     public static boolean isSchemeChar(char c) {
728         return Character.isLetterOrDigit(c) ||
729             c == '+' || c == '-' || c == '.';
730     }
731
732 }
733
Popular Tags