1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 package org.backsource.utils.resource;
58
59 import java.io.IOException;
60 import java.io.Serializable;
61 import java.net.URL;
62 import java.net.MalformedURLException;
63
64 /***
65 *<p> A class to represent a Uniform Resource Identifier (URI) and to get a
66 * valid URL representation from the URI.
67 *
68 * <p>The URL conversion is done with the help of URIFactory, see {@link URIFactory} for more information.
69 *
70 * <p>This class
71 * is designed to handle the parsing of URIs and provide access to
72 * the various components (scheme, host, port, userinfo, path, query
73 * string and fragment) that may constitute a URI.
74 * <p>
75 * Parsing of a URI specification is done according to the URI
76 * syntax described in RFC 2396
77 * <http://www.ietf.org/rfc/rfc2396.txt?number=2396>. Every URI consists
78 * of a scheme, followed by a colon (':'), followed by a scheme-specific
79 * part. For URIs that follow the "generic URI" syntax, the scheme-
80 * specific part begins with two slashes ("//") and may be followed
81 * by an authority segment (comprised of user information, host, and
82 * port), path segment, query segment and fragment. Note that RFC 2396
83 * no longer specifies the use of the parameters segment and excludes
84 * the "user:password" syntax as part of the authority segment. If
85 * "user:password" appears in a URI, the entire user/password string
86 * is stored as userinfo.
87 * <p>
88 * For URIs that do not follow the "generic URI" syntax (e.g. mailto),
89 * the entire scheme-specific part is treated as the "path" portion
90 * of the URI.
91 * <p>
92 * Note that, unlike the java.net.URL class, this class does not provide
93 * any built-in network access functionality nor does it provide any
94 * scheme-specific functionality (for example, it does not know a
95 * default port for a specific scheme). Rather, it only knows the
96 * grammar and basic set of operations that can be applied to a URI.
97 *
98 * @version $Id: URI.java,v 1.1.1.1 2004/05/19 12:07:31 pra Exp $
99 *
100 */
101 public class URI implements Serializable
102 {
103
104 /*** reserved characters */
105 private static final String RESERVED_CHARACTERS = ";/?:@&=+$,";
106
107 /***
108 * URI punctuation mark characters - these, combined with
109 * alphanumerics, constitute the "unreserved" characters
110 */
111 private static final String MARK_CHARACTERS = "-_.!~*'() ";
112
113 /*** scheme can be composed of alphanumerics and these characters */
114 private static final String SCHEME_CHARACTERS = "+-.";
115
116 /***
117 * userinfo can be composed of unreserved, escaped and these
118 * characters
119 */
120 private static final String USERINFO_CHARACTERS = ";:&=+$,";
121
122 /*** Stores the scheme (usually the protocol) for this URI.
123 * @serial */
124 private String m_scheme = null;
125
126 /*** If specified, stores the userinfo for this URI; otherwise null.
127 * @serial */
128 private String m_userinfo = null;
129
130 /*** If specified, stores the host for this URI; otherwise null.
131 * @serial */
132 private String m_host = null;
133
134 /*** If specified, stores the port for this URI; otherwise -1.
135 * @serial */
136 private int m_port = -1;
137
138 /*** If specified, stores the path for this URI; otherwise null.
139 * @serial */
140 private String m_path = null;
141
142 /***
143 * If specified, stores the query string for this URI; otherwise
144 * null.
145 * @serial
146 */
147 private String m_queryString = null;
148
149 /*** If specified, stores the fragment for this URI; otherwise null.
150 * @serial */
151 private String m_fragment = null;
152
153 /*** Indicate whether in DEBUG mode */
154 private static boolean DEBUG = false;
155
156 private URIFactory uriFactory = null;
157
158
159 /***
160 * Construct a new and uninitialized URI.
161 */
162 public URI(){}
163
164 /***
165 * Construct a new URI from another URI. All fields for this URI are
166 * set equal to the fields of the URI passed in.
167 *
168 * @param p_other the URI to copy (cannot be null)
169 */
170 public URI(URI p_other)
171 {
172 initialize(p_other);
173 }
174
175 /***
176 * Construct a new URI from a URI specification string. If the
177 * specification follows the "generic URI" syntax, (two slashes
178 * following the first colon), the specification will be parsed
179 * accordingly - setting the scheme, userinfo, host,port, path, query
180 * string and fragment fields as necessary. If the specification does
181 * not follow the "generic URI" syntax, the specification is parsed
182 * into a scheme and scheme-specific part (stored as the path) only.
183 *
184 * @param p_uriSpec the URI specification string (cannot be null or
185 * empty)
186 *
187 * @throws MalformedURIException if p_uriSpec violates any syntax
188 * rules
189 */
190 public URI(String p_uriSpec) throws MalformedURIException
191 {
192 this((URI) null, p_uriSpec);
193 }
194 /***
195 * Construct a new URI from a URI specification string. If the
196 * specification follows the "generic URI" syntax, (two slashes
197 * following the first colon), the specification will be parsed
198 * accordingly - setting the scheme, userinfo, host,port, path, query
199 * string and fragment fields as necessary. If the specification does
200 * not follow the "generic URI" syntax, the specification is parsed
201 * into a scheme and scheme-specific part (stored as the path) only.
202 *
203 * @param p_uriSpec the URI specification string (cannot be null or
204 * empty)
205 * @param factory an URIFactorty to use for constructing an URL.
206 * @throws MalformedURIException if p_uriSpec violates any syntax
207 * rules
208 */
209 public URI(String p_uriSpec, URIFactory factory) throws MalformedURIException
210 {
211 this((URI) null, p_uriSpec);
212 this.uriFactory = factory;
213 }
214
215 /***
216 * Construct a new URI from a base URI and a URI specification string.
217 * The URI specification string may be a relative URI.
218 *
219 * @param p_base the base URI (cannot be null if p_uriSpec is null or
220 * empty)
221 * @param p_uriSpec the URI specification string (cannot be null or
222 * empty if p_base is null)
223 *
224 * @throws MalformedURIException if p_uriSpec violates any syntax
225 * rules
226 */
227 public URI(URI p_base, String p_uriSpec) throws MalformedURIException
228 {
229 initialize(p_base, p_uriSpec);
230 }
231
232 /***
233 * Construct a new URI that does not follow the generic URI syntax.
234 * Only the scheme and scheme-specific part (stored as the path) are
235 * initialized.
236 *
237 * @param p_scheme the URI scheme (cannot be null or empty)
238 * @param p_schemeSpecificPart the scheme-specific part (cannot be
239 * null or empty)
240 *
241 * @throws MalformedURIException if p_scheme violates any
242 * syntax rules
243 */
244 public URI(String p_scheme, String p_schemeSpecificPart)
245 throws MalformedURIException
246 {
247
248 if (p_scheme == null || p_scheme.trim().length() == 0)
249 {
250 throw new MalformedURIException(
251 "Cannot construct URI with null/empty scheme!");
252 }
253
254 if (p_schemeSpecificPart == null
255 || p_schemeSpecificPart.trim().length() == 0)
256 {
257 throw new MalformedURIException(
258 "Cannot construct URI with null/empty scheme-specific part!");
259 }
260
261 setScheme(p_scheme);
262 setPath(p_schemeSpecificPart);
263 }
264
265 /***
266 * Construct a new URI that follows the generic URI syntax from its
267 * component parts. Each component is validated for syntax and some
268 * basic semantic checks are performed as well. See the individual
269 * setter methods for specifics.
270 *
271 * @param p_scheme the URI scheme (cannot be null or empty)
272 * @param p_host the hostname or IPv4 address for the URI
273 * @param p_path the URI path - if the path contains '?' or '#',
274 * then the query string and/or fragment will be
275 * set from the path; however, if the query and
276 * fragment are specified both in the path and as
277 * separate parameters, an exception is thrown
278 * @param p_queryString the URI query string (cannot be specified
279 * if path is null)
280 * @param p_fragment the URI fragment (cannot be specified if path
281 * is null)
282 *
283 * @throws MalformedURIException if any of the parameters violates
284 * syntax rules or semantic rules
285 */
286 public URI(String p_scheme, String p_host, String p_path, String p_queryString, String p_fragment)
287 throws MalformedURIException
288 {
289 this(p_scheme, null, p_host, -1, p_path, p_queryString, p_fragment);
290 }
291
292 /***
293 * Construct a new URI that follows the generic URI syntax from its
294 * component parts. Each component is validated for syntax and some
295 * basic semantic checks are performed as well. See the individual
296 * setter methods for specifics.
297 *
298 * @param p_scheme the URI scheme (cannot be null or empty)
299 * @param p_userinfo the URI userinfo (cannot be specified if host
300 * is null)
301 * @param p_host the hostname or IPv4 address for the URI
302 * @param p_port the URI port (may be -1 for "unspecified"; cannot
303 * be specified if host is null)
304 * @param p_path the URI path - if the path contains '?' or '#',
305 * then the query string and/or fragment will be
306 * set from the path; however, if the query and
307 * fragment are specified both in the path and as
308 * separate parameters, an exception is thrown
309 * @param p_queryString the URI query string (cannot be specified
310 * if path is null)
311 * @param p_fragment the URI fragment (cannot be specified if path
312 * is null)
313 *
314 * @throws MalformedURIException if any of the parameters violates
315 * syntax rules or semantic rules
316 */
317 public URI(String p_scheme, String p_userinfo, String p_host, int p_port, String p_path, String p_queryString, String p_fragment)
318 throws MalformedURIException
319 {
320
321 if (p_scheme == null || p_scheme.trim().length() == 0)
322 {
323 throw new MalformedURIException("Scheme is required!");
324 }
325
326 if (p_host == null)
327 {
328 if (p_userinfo != null)
329 {
330 throw new MalformedURIException(
331 "Userinfo may not be specified if host is not specified!");
332 }
333
334 if (p_port != -1)
335 {
336 throw new MalformedURIException(
337 "Port may not be specified if host is not specified!");
338 }
339 }
340
341 if (p_path != null)
342 {
343 if (p_path.indexOf('?') != -1 && p_queryString != null)
344 {
345 throw new MalformedURIException(
346 "Query string cannot be specified in path and query string!");
347 }
348
349 if (p_path.indexOf('#') != -1 && p_fragment != null)
350 {
351 throw new MalformedURIException(
352 "Fragment cannot be specified in both the path and fragment!");
353 }
354 }
355
356 setScheme(p_scheme);
357 setHost(p_host);
358 setPort(p_port);
359 setUserinfo(p_userinfo);
360 setPath(p_path);
361 setQueryString(p_queryString);
362 setFragment(p_fragment);
363 }
364
365 /***
366 * Initialize all fields of this URI from another URI.
367 *
368 * @param p_other the URI to copy (cannot be null)
369 */
370 private void initialize(URI p_other)
371 {
372
373 m_scheme = p_other.getScheme();
374 m_userinfo = p_other.getUserinfo();
375 m_host = p_other.getHost();
376 m_port = p_other.getPort();
377 m_path = p_other.getPath();
378 m_queryString = p_other.getQueryString();
379 m_fragment = p_other.getFragment();
380 }
381
382 /***
383 * Initializes this URI from a base URI and a URI specification string.
384 * See RFC 2396 Section 4 and Appendix B for specifications on parsing
385 * the URI and Section 5 for specifications on resolving relative URIs
386 * and relative paths.
387 *
388 * @param p_base the base URI (may be null if p_uriSpec is an absolute
389 * URI)
390 * @param p_uriSpec the URI spec string which may be an absolute or
391 * relative URI (can only be null/empty if p_base
392 * is not null)
393 *
394 * @throws MalformedURIException if p_base is null and p_uriSpec
395 * is not an absolute URI or if
396 * p_uriSpec violates syntax rules
397 */
398 private void initialize(URI p_base, String p_uriSpec)
399 throws MalformedURIException
400 {
401
402 if (p_base == null
403 && (p_uriSpec == null || p_uriSpec.trim().length() == 0))
404 {
405 throw new MalformedURIException(
406 "Cannot initialize URI with empty parameters.");
407 }
408
409
410 if (p_uriSpec == null || p_uriSpec.trim().length() == 0)
411 {
412 initialize(p_base);
413
414 return;
415 }
416
417 String uriSpec = p_uriSpec.trim();
418 int uriSpecLen = uriSpec.length();
419 int index = 0;
420
421
422 if (uriSpec.indexOf(':') == -1)
423 {
424 if (p_base == null)
425 {
426 uriSpec = "default:" + uriSpec;
427 uriSpecLen = uriSpec.length();
428
429 }
430 }
431
432 {
433 initializeScheme(uriSpec);
434
435 index = m_scheme.length() + 1;
436 }
437
438
439 if (((index + 1) < uriSpecLen)
440 && (uriSpec.substring(index).startsWith("//")))
441 {
442 index += 2;
443
444 int startPos = index;
445
446
447 char testChar = '\0';
448
449 while (index < uriSpecLen)
450 {
451 testChar = uriSpec.charAt(index);
452
453 if (testChar == '/' || testChar == '?' || testChar == '#')
454 {
455 break;
456 }
457
458 index++;
459 }
460
461
462
463 if (index > startPos)
464 {
465 initializeAuthority(uriSpec.substring(startPos, index));
466 }
467 else
468 {
469 m_host = "";
470 }
471 }
472
473 initializePath(uriSpec.substring(index));
474
475
476
477
478
479
480 if (p_base != null)
481 {
482
483
484
485
486
487
488
489
490 if (m_path.length() == 0 && m_scheme == null && m_host == null)
491 {
492 m_scheme = p_base.getScheme();
493 m_userinfo = p_base.getUserinfo();
494 m_host = p_base.getHost();
495 m_port = p_base.getPort();
496 m_path = p_base.getPath();
497
498 if (m_queryString == null)
499 {
500 m_queryString = p_base.getQueryString();
501 }
502
503 return;
504 }
505
506
507
508 if (m_scheme == null)
509 {
510 m_scheme = p_base.getScheme();
511 }
512 else
513 {
514 return;
515 }
516
517
518
519 if (m_host == null)
520 {
521 m_userinfo = p_base.getUserinfo();
522 m_host = p_base.getHost();
523 m_port = p_base.getPort();
524 }
525 else
526 {
527 return;
528 }
529
530
531 if (m_path.length() > 0 && m_path.startsWith("/"))
532 {
533 return;
534 }
535
536
537
538 String path = new String();
539 String basePath = p_base.getPath();
540
541
542 if (basePath != null)
543 {
544 int lastSlash = basePath.lastIndexOf('/');
545
546 if (lastSlash != -1)
547 {
548 path = basePath.substring(0, lastSlash + 1);
549 }
550 }
551
552
553 path = path.concat(m_path);
554
555
556 index = -1;
557
558 while ((index = path.indexOf("/./")) != -1)
559 {
560 path = path.substring(0, index + 1).concat(path.substring(index + 3));
561 }
562
563
564 if (path.endsWith("/."))
565 {
566 path = path.substring(0, path.length() - 1);
567 }
568
569
570
571 index = -1;
572
573 int segIndex = -1;
574 String tempString = null;
575
576 while ((index = path.indexOf("/../", index)) > 0) {
577 tempString = path.substring(0, path.indexOf("/../"));
578 segIndex = tempString.lastIndexOf('/');
579 if (segIndex != -1) {
580 if (!tempString.substring(segIndex).equals("..")) {
581 path = path.substring(0, segIndex+1).concat(path.substring(index+4));
582 index = segIndex;
583 }
584 else
585 index += 4;
586 }
587 else
588 index += 4;
589 }
590
591
592
593
594 if (path.endsWith("/.."))
595 {
596 tempString = path.substring(0, path.length() - 3);
597 segIndex = tempString.lastIndexOf('/');
598
599 if (segIndex != -1)
600 {
601 path = path.substring(0, segIndex + 1);
602 }
603 }
604
605 m_path = path;
606 }
607 }
608
609 /***
610 * Initialize the scheme for this URI from a URI string spec.
611 *
612 * @param p_uriSpec the URI specification (cannot be null)
613 *
614 * @throws MalformedURIException if URI does not have a conformant
615 * scheme
616 */
617 private void initializeScheme(String p_uriSpec) throws MalformedURIException
618 {
619
620 int uriSpecLen = p_uriSpec.length();
621 int index = 0;
622 String scheme = null;
623 char testChar = '\0';
624
625 while (index < uriSpecLen)
626 {
627 testChar = p_uriSpec.charAt(index);
628
629 if (testChar == ':' || testChar == '/' || testChar == '?'
630 || testChar == '#')
631 {
632 break;
633 }
634
635 index++;
636 }
637
638 scheme = p_uriSpec.substring(0, index);
639
640 if (scheme.length() == 0)
641 {
642 throw new MalformedURIException("No scheme found in URI.");
643 }
644 else
645 {
646 setScheme(scheme);
647 }
648 }
649
650 /***
651 * Initialize the authority (userinfo, host and port) for this
652 * URI from a URI string spec.
653 *
654 * @param p_uriSpec the URI specification (cannot be null)
655 *
656 * @throws MalformedURIException if p_uriSpec violates syntax rules
657 */
658 private void initializeAuthority(String p_uriSpec)
659 throws MalformedURIException
660 {
661
662 int index = 0;
663 int start = 0;
664 int end = p_uriSpec.length();
665 char testChar = '\0';
666 String userinfo = null;
667
668
669 if (p_uriSpec.indexOf('@', start) != -1)
670 {
671 while (index < end)
672 {
673 testChar = p_uriSpec.charAt(index);
674
675 if (testChar == '@')
676 {
677 break;
678 }
679
680 index++;
681 }
682
683 userinfo = p_uriSpec.substring(start, index);
684
685 index++;
686 }
687
688
689 String host = null;
690
691 start = index;
692
693 while (index < end)
694 {
695 testChar = p_uriSpec.charAt(index);
696
697 if (testChar == ':')
698 {
699 break;
700 }
701
702 index++;
703 }
704
705 host = p_uriSpec.substring(start, index);
706
707 int port = -1;
708
709 if (host.length() > 0)
710 {
711
712
713 if (testChar == ':')
714 {
715 index++;
716
717 start = index;
718
719 while (index < end)
720 {
721 index++;
722 }
723
724 String portStr = p_uriSpec.substring(start, index);
725
726 if (portStr.length() > 0)
727 {
728 for (int i = 0; i < portStr.length(); i++)
729 {
730 if (!isDigit(portStr.charAt(i)))
731 {
732 throw new MalformedURIException(
733 portStr + " is invalid. Port should only contain digits!");
734 }
735 }
736
737 try
738 {
739 port = Integer.parseInt(portStr);
740 }
741 catch (NumberFormatException nfe)
742 {
743
744
745 }
746 }
747 }
748 }
749
750 setHost(host);
751 setPort(port);
752 setUserinfo(userinfo);
753 }
754
755 /***
756 * Initialize the path for this URI from a URI string spec.
757 *
758 * @param p_uriSpec the URI specification (cannot be null)
759 *
760 * @throws MalformedURIException if p_uriSpec violates syntax rules
761 */
762 private void initializePath(String p_uriSpec) throws MalformedURIException
763 {
764
765 if (p_uriSpec == null)
766 {
767 throw new MalformedURIException(
768 "Cannot initialize path from null string!");
769 }
770
771 int index = 0;
772 int start = 0;
773 int end = p_uriSpec.length();
774 char testChar = '\0';
775
776
777 while (index < end)
778 {
779 testChar = p_uriSpec.charAt(index);
780
781 if (testChar == '?' || testChar == '#')
782 {
783 break;
784 }
785
786
787 if (testChar == '%')
788 {
789 if (index + 2 >= end ||!isHex(p_uriSpec.charAt(index + 1))
790 ||!isHex(p_uriSpec.charAt(index + 2)))
791 {
792 throw new MalformedURIException(
793 "Path contains invalid escape sequence!");
794 }
795 }
796 else if (!isReservedCharacter(testChar)
797 &&!isUnreservedCharacter(testChar))
798 {
799 if ('//' != testChar)
800 throw new MalformedURIException("Path contains invalid character: "
801 + testChar);
802 }
803
804 index++;
805 }
806
807 m_path = p_uriSpec.substring(start, index);
808
809
810 if (testChar == '?')
811 {
812 index++;
813
814 start = index;
815
816 while (index < end)
817 {
818 testChar = p_uriSpec.charAt(index);
819
820 if (testChar == '#')
821 {
822 break;
823 }
824
825 if (testChar == '%')
826 {
827 if (index + 2 >= end ||!isHex(p_uriSpec.charAt(index + 1))
828 ||!isHex(p_uriSpec.charAt(index + 2)))
829 {
830 throw new MalformedURIException(
831 "Query string contains invalid escape sequence!");
832 }
833 }
834 else if (!isReservedCharacter(testChar)
835 &&!isUnreservedCharacter(testChar))
836 {
837 throw new MalformedURIException(
838 "Query string contains invalid character:" + testChar);
839 }
840
841 index++;
842 }
843
844 m_queryString = p_uriSpec.substring(start, index);
845 }
846
847
848 if (testChar == '#')
849 {
850 index++;
851
852 start = index;
853
854 while (index < end)
855 {
856 testChar = p_uriSpec.charAt(index);
857
858 if (testChar == '%')
859 {
860 if (index + 2 >= end ||!isHex(p_uriSpec.charAt(index + 1))
861 ||!isHex(p_uriSpec.charAt(index + 2)))
862 {
863 throw new MalformedURIException(
864 "Fragment contains invalid escape sequence!");
865 }
866 }
867 else if (!isReservedCharacter(testChar)
868 &&!isUnreservedCharacter(testChar))
869 {
870 throw new MalformedURIException(
871 "Fragment contains invalid character:" + testChar);
872 }
873
874 index++;
875 }
876
877 m_fragment = p_uriSpec.substring(start, index);
878 }
879 }
880
881 /***
882 * Set the URI factory to use when constructing an URL of the URI.
883 *
884 * @param factory a URIFactory.
885 */
886 public void setURIFactory(URIFactory factory) {
887 this.uriFactory = factory;
888 }
889
890
891 /***
892 * Get the scheme for this URI.
893 *
894 * @return the scheme for this URI
895 */
896 public String getScheme()
897 {
898 return m_scheme;
899 }
900
901 /***
902 * Get the scheme-specific part for this URI (everything following the
903 * scheme and the first colon). See RFC 2396 Section 5.2 for spec.
904 *
905 * @return the scheme-specific part for this URI
906 */
907 public String getSchemeSpecificPart()
908 {
909
910 StringBuffer schemespec = new StringBuffer();
911
912 if (m_userinfo != null || m_host != null || m_port != -1)
913 {
914 schemespec.append("//");
915 }
916
917 if (m_userinfo != null)
918 {
919 schemespec.append(m_userinfo);
920 schemespec.append('@');
921 }
922
923 if (m_host != null)
924 {
925 schemespec.append(m_host);
926 }
927
928 if (m_port != -1)
929 {
930 schemespec.append(':');
931 schemespec.append(m_port);
932 }
933
934 if (m_path != null)
935 {
936 schemespec.append((m_path));
937 }
938
939 if (m_queryString != null)
940 {
941 schemespec.append('?');
942 schemespec.append(m_queryString);
943 }
944
945 if (m_fragment != null)
946 {
947 schemespec.append('#');
948 schemespec.append(m_fragment);
949 }
950
951 return schemespec.toString();
952 }
953
954 /***
955 * Get the userinfo for this URI.
956 *
957 * @return the userinfo for this URI (null if not specified).
958 */
959 public String getUserinfo()
960 {
961 return m_userinfo;
962 }
963
964 /***
965 * Get the host for this URI.
966 *
967 * @return the host for this URI (null if not specified).
968 */
969 public String getHost()
970 {
971 return m_host;
972 }
973
974 /***
975 * Get the port for this URI.
976 *
977 * @return the port for this URI (-1 if not specified).
978 */
979 public int getPort()
980 {
981 return m_port;
982 }
983
984 /***
985 * Get the path for this URI (optionally with the query string and
986 * fragment).
987 *
988 * @param p_includeQueryString if true (and query string is not null),
989 * then a "?" followed by the query string
990 * will be appended
991 * @param p_includeFragment if true (and fragment is not null),
992 * then a "#" followed by the fragment
993 * will be appended
994 *
995 * @return the path for this URI possibly including the query string
996 * and fragment
997 */
998 public String getPath(boolean p_includeQueryString,
999 boolean p_includeFragment)
1000 {
1001
1002 StringBuffer pathString = new StringBuffer(m_path);
1003
1004 if (p_includeQueryString && m_queryString != null)
1005 {
1006 pathString.append('?');
1007 pathString.append(m_queryString);
1008 }
1009
1010 if (p_includeFragment && m_fragment != null)
1011 {
1012 pathString.append('#');
1013 pathString.append(m_fragment);
1014 }
1015
1016 return pathString.toString();
1017 }
1018
1019 /***
1020 * Get the path for this URI. Note that the value returned is the path
1021 * only and does not include the query string or fragment.
1022 *
1023 * @return the path for this URI.
1024 */
1025 public String getPath()
1026 {
1027 return m_path;
1028 }
1029
1030 /***
1031 * Get the query string for this URI.
1032 *
1033 * @return the query string for this URI. Null is returned if there
1034 * was no "?" in the URI spec, empty string if there was a
1035 * "?" but no query string following it.
1036 */
1037 public String getQueryString()
1038 {
1039 return m_queryString;
1040 }
1041
1042 /***
1043 * Get the fragment for this URI.
1044 *
1045 * @return the fragment for this URI. Null is returned if there
1046 * was no "#" in the URI spec, empty string if there was a
1047 * "#" but no fragment following it.
1048 */
1049 public String getFragment()
1050 {
1051 return m_fragment;
1052 }
1053
1054 /***
1055 * Set the scheme for this URI. The scheme is converted to lowercase
1056 * before it is set.
1057 *
1058 * @param p_scheme the scheme for this URI (cannot be null)
1059 *
1060 * @throws MalformedURIException if p_scheme is not a conformant
1061 * scheme name
1062 */
1063 public void setScheme(String p_scheme) throws MalformedURIException
1064 {
1065
1066 if (p_scheme == null)
1067 {
1068 throw new MalformedURIException("Cannot set scheme from null string!");
1069 }
1070
1071 if (!isConformantSchemeName(p_scheme))
1072 {
1073 throw new MalformedURIException("The scheme is not conformant.");
1074 }
1075
1076 m_scheme = p_scheme.toLowerCase();
1077 }
1078
1079 /***
1080 * Set the userinfo for this URI. If a non-null value is passed in and
1081 * the host value is null, then an exception is thrown.
1082 *
1083 * @param p_userinfo the userinfo for this URI
1084 *
1085 * @throws MalformedURIException if p_userinfo contains invalid
1086 * characters
1087 */
1088 public void setUserinfo(String p_userinfo) throws MalformedURIException
1089 {
1090
1091 if (p_userinfo == null)
1092 {
1093 m_userinfo = null;
1094 }
1095 else
1096 {
1097 if (m_host == null)
1098 {
1099 throw new MalformedURIException(
1100 "Userinfo cannot be set when host is null!");
1101 }
1102
1103
1104
1105 int index = 0;
1106 int end = p_userinfo.length();
1107 char testChar = '\0';
1108
1109 while (index < end)
1110 {
1111 testChar = p_userinfo.charAt(index);
1112
1113 if (testChar == '%')
1114 {
1115 if (index + 2 >= end ||!isHex(p_userinfo.charAt(index + 1))
1116 ||!isHex(p_userinfo.charAt(index + 2)))
1117 {
1118 throw new MalformedURIException(
1119 "Userinfo contains invalid escape sequence!");
1120 }
1121 }
1122 else if (!isUnreservedCharacter(testChar)
1123 && USERINFO_CHARACTERS.indexOf(testChar) == -1)
1124 {
1125 throw new MalformedURIException(
1126 "Userinfo contains invalid character:" + testChar);
1127 }
1128
1129 index++;
1130 }
1131 }
1132
1133 m_userinfo = p_userinfo;
1134 }
1135
1136 /***
1137 * Set the host for this URI. If null is passed in, the userinfo
1138 * field is also set to null and the port is set to -1.
1139 *
1140 * @param p_host the host for this URI
1141 *
1142 * @throws MalformedURIException if p_host is not a valid IP
1143 * address or DNS hostname.
1144 */
1145 public void setHost(String p_host) throws MalformedURIException
1146 {
1147
1148 if (p_host == null || p_host.trim().length() == 0)
1149 {
1150 m_host = p_host;
1151 m_userinfo = null;
1152 m_port = -1;
1153 }
1154 else if (!isWellFormedAddress(p_host))
1155 {
1156 throw new MalformedURIException("Host is not a well formed address!");
1157 }
1158
1159 m_host = p_host;
1160 }
1161
1162 /***
1163 * Set the port for this URI. -1 is used to indicate that the port is
1164 * not specified, otherwise valid port numbers are between 0 and 65535.
1165 * If a valid port number is passed in and the host field is null,
1166 * an exception is thrown.
1167 *
1168 * @param p_port the port number for this URI
1169 *
1170 * @throws MalformedURIException if p_port is not -1 and not a
1171 * valid port number
1172 */
1173 public void setPort(int p_port) throws MalformedURIException
1174 {
1175
1176 if (p_port >= 0 && p_port <= 65535)
1177 {
1178 if (m_host == null)
1179 {
1180 throw new MalformedURIException(
1181 "Port cannot be set when host is null!");
1182 }
1183 }
1184 else if (p_port != -1)
1185 {
1186 throw new MalformedURIException("Invalid port number!");
1187 }
1188
1189 m_port = p_port;
1190 }
1191
1192 /***
1193 * Set the path for this URI. If the supplied path is null, then the
1194 * query string and fragment are set to null as well. If the supplied
1195 * path includes a query string and/or fragment, these fields will be
1196 * parsed and set as well. Note that, for URIs following the "generic
1197 * URI" syntax, the path specified should start with a slash.
1198 * For URIs that do not follow the generic URI syntax, this method
1199 * sets the scheme-specific part.
1200 *
1201 * @param p_path the path for this URI (may be null)
1202 *
1203 * @throws MalformedURIException if p_path contains invalid
1204 * characters
1205 */
1206 public void setPath(String p_path) throws MalformedURIException
1207 {
1208
1209 if (p_path == null)
1210 {
1211 m_path = null;
1212 m_queryString = null;
1213 m_fragment = null;
1214 }
1215 else
1216 {
1217 initializePath(p_path);
1218 }
1219 }
1220
1221 /***
1222 * Append to the end of the path of this URI. If the current path does
1223 * not end in a slash and the path to be appended does not begin with
1224 * a slash, a slash will be appended to the current path before the
1225 * new segment is added. Also, if the current path ends in a slash
1226 * and the new segment begins with a slash, the extra slash will be
1227 * removed before the new segment is appended.
1228 *
1229 * @param p_addToPath the new segment to be added to the current path
1230 *
1231 * @throws MalformedURIException if p_addToPath contains syntax
1232 * errors
1233 */
1234 public void appendPath(String p_addToPath) throws MalformedURIException
1235 {
1236
1237 if (p_addToPath == null || p_addToPath.trim().length() == 0)
1238 {
1239 return;
1240 }
1241
1242 if (!isURIString(p_addToPath))
1243 {
1244 throw new MalformedURIException("Path contains invalid character!");
1245 }
1246
1247 if (m_path == null || m_path.trim().length() == 0)
1248 {
1249 if (p_addToPath.startsWith("/"))
1250 {
1251 m_path = p_addToPath;
1252 }
1253 else
1254 {
1255 m_path = "/" + p_addToPath;
1256 }
1257 }
1258 else if (m_path.endsWith("/"))
1259 {
1260 if (p_addToPath.startsWith("/"))
1261 {
1262 m_path = m_path.concat(p_addToPath.substring(1));
1263 }
1264 else
1265 {
1266 m_path = m_path.concat(p_addToPath);
1267 }
1268 }
1269 else
1270 {
1271 if (p_addToPath.startsWith("/"))
1272 {
1273 m_path = m_path.concat(p_addToPath);
1274 }
1275 else
1276 {
1277 m_path = m_path.concat("/" + p_addToPath);
1278 }
1279 }
1280 }
1281
1282 /***
1283 * Set the query string for this URI. A non-null value is valid only
1284 * if this is an URI conforming to the generic URI syntax and
1285 * the path value is not null.
1286 *
1287 * @param p_queryString the query string for this URI
1288 *
1289 * @throws MalformedURIException if p_queryString is not null and this
1290 * URI does not conform to the generic
1291 * URI syntax or if the path is null
1292 */
1293 public void setQueryString(String p_queryString)
1294 throws MalformedURIException
1295 {
1296
1297 if (p_queryString == null)
1298 {
1299 m_queryString = null;
1300 }
1301 else if (!isGenericURI())
1302 {
1303 throw new MalformedURIException(
1304 "Query string can only be set for a generic URI!");
1305 }
1306 else if (getPath() == null)
1307 {
1308 throw new MalformedURIException(
1309 "Query string cannot be set when path is null!");
1310 }
1311 else if (!isURIString(p_queryString))
1312 {
1313 throw new MalformedURIException(
1314 "Query string contains invalid character!");
1315 }
1316 else
1317 {
1318 m_queryString = p_queryString;
1319 }
1320 }
1321
1322 /***
1323 * Set the fragment for this URI. A non-null value is valid only
1324 * if this is a URI conforming to the generic URI syntax and
1325 * the path value is not null.
1326 *
1327 * @param p_fragment the fragment for this URI
1328 *
1329 * @throws MalformedURIException if p_fragment is not null and this
1330 * URI does not conform to the generic
1331 * URI syntax or if the path is null
1332 */
1333 public void setFragment(String p_fragment) throws MalformedURIException
1334 {
1335
1336 if (p_fragment == null)
1337 {
1338 m_fragment = null;
1339 }
1340 else if (!isGenericURI())
1341 {
1342 throw new MalformedURIException(
1343 "Fragment can only be set for a generic URI!");
1344 }
1345 else if (getPath() == null)
1346 {
1347 throw new MalformedURIException(
1348 "Fragment cannot be set when path is null!");
1349 }
1350 else if (!isURIString(p_fragment))
1351 {
1352 throw new MalformedURIException("Fragment contains invalid character!");
1353 }
1354 else
1355 {
1356 m_fragment = p_fragment;
1357 }
1358 }
1359
1360 /***
1361 * Determines if the passed-in Object is equivalent to this URI.
1362 *
1363 * @param p_test the Object to test for equality.
1364 *
1365 * @return true if p_test is a URI with all values equal to this
1366 * URI, false otherwise
1367 */
1368 public boolean equals(Object p_test)
1369 {
1370
1371 if (p_test instanceof URI)
1372 {
1373 URI testURI = (URI) p_test;
1374
1375 if (((m_scheme == null && testURI.m_scheme == null) || (m_scheme != null && testURI.m_scheme != null && m_scheme.equals(
1376 testURI.m_scheme))) && ((m_userinfo == null && testURI.m_userinfo == null) || (m_userinfo != null && testURI.m_userinfo != null && m_userinfo.equals(
1377 testURI.m_userinfo))) && ((m_host == null && testURI.m_host == null) || (m_host != null && testURI.m_host != null && m_host.equals(
1378 testURI.m_host))) && m_port == testURI.m_port && ((m_path == null && testURI.m_path == null) || (m_path != null && testURI.m_path != null && m_path.equals(
1379 testURI.m_path))) && ((m_queryString == null && testURI.m_queryString == null) || (m_queryString != null && testURI.m_queryString != null && m_queryString.equals(
1380 testURI.m_queryString))) && ((m_fragment == null && testURI.m_fragment == null) || (m_fragment != null && testURI.m_fragment != null && m_fragment.equals(
1381 testURI.m_fragment))))
1382 {
1383 return true;
1384 }
1385 }
1386
1387 return false;
1388 }
1389
1390 /***
1391 * <p>Get an URL represenation of this URI.
1392 *
1393 * <p>Either an URI factory from URIFactoryContext will be used a factory specifically set on this URI. If the URI is not possible to convert to an URL a MaformedURLException will be thrown. For URI:s fetched/constructed from for example the classloader or the servlet context, this means that a MalformedURLException will be thrown also when the resource was not found.
1394 *
1395 * @return URL a valid url for the URI where the resource the URI points to is possible to get.
1396 * @exception MalformedURLException thrown if the URI is not possible to convert to an URL, i.e does not point to a locatable resource or is not wellformed.
1397 */
1398 public URL getURL() throws MalformedURLException{
1399 if (uriFactory != null)
1400 return getURL(uriFactory);
1401 else
1402 return getURL( URIFactoryContext.get());
1403 }
1404
1405 /***
1406 * Get an URL represenation of this URI from the given URIFactory. @see getURL().
1407 *
1408 * @param factory an URIFactory to use when constructing the URL.
1409 * @return URL a valid url for the URI where the resource the URI points to is possible to get.
1410 * @exception MalformedURLException thrown if the URI is not possible to convert to an URL, i.e does not point to a locatable resource or is not wellformed.
1411 */
1412 public URL getURL(URIFactory factory) throws MalformedURLException{
1413 return factory.getURL(this);
1414 }
1415
1416 /***
1417 * Get the URI as a string specification. See RFC 2396 Section 5.2.
1418 *
1419 * @return the URI string specification
1420 */
1421 public String toString()
1422 {
1423
1424 StringBuffer uriSpecString = new StringBuffer();
1425
1426 if (m_scheme != null)
1427 {
1428 uriSpecString.append(m_scheme);
1429 uriSpecString.append(':');
1430 }
1431
1432 uriSpecString.append(getSchemeSpecificPart());
1433
1434 return uriSpecString.toString();
1435 }
1436
1437 public String toExternalForm() {
1438 return toString();
1439 }
1440
1441 /***
1442 * Get the indicator as to whether this URI uses the "generic URI"
1443 * syntax.
1444 *
1445 * @return true if this URI uses the "generic URI" syntax, false
1446 * otherwise
1447 */
1448 public boolean isGenericURI()
1449 {
1450
1451
1452
1453 return (m_host != null);
1454 }
1455
1456 /***
1457 * Determine whether a scheme conforms to the rules for a scheme name.
1458 * A scheme is conformant if it starts with an alphanumeric, and
1459 * contains only alphanumerics, '+','-' and '.'.
1460 *
1461 *
1462 * @param p_scheme The sheme name to check
1463 * @return true if the scheme is conformant, false otherwise
1464 */
1465 public static boolean isConformantSchemeName(String p_scheme)
1466 {
1467
1468 if (p_scheme == null || p_scheme.trim().length() == 0)
1469 {
1470 return false;
1471 }
1472
1473 if (!isAlpha(p_scheme.charAt(0)))
1474 {
1475 return false;
1476 }
1477
1478 char testChar;
1479
1480 for (int i = 1; i < p_scheme.length(); i++)
1481 {
1482 testChar = p_scheme.charAt(i);
1483
1484 if (!isAlphanum(testChar) && SCHEME_CHARACTERS.indexOf(testChar) == -1)
1485 {
1486 return false;
1487 }
1488 }
1489
1490 return true;
1491 }
1492
1493 /***
1494 * Determine whether a string is syntactically capable of representing
1495 * a valid IPv4 address or the domain name of a network host. A valid
1496 * IPv4 address consists of four decimal digit groups separated by a
1497 * '.'. A hostname consists of domain labels (each of which must
1498 * begin and end with an alphanumeric but may contain '-') separated
1499 * & by a '.'. See RFC 2396 Section 3.2.2.
1500 *
1501 *
1502 * @param p_address The address string to check
1503 * @return true if the string is a syntactically valid IPv4 address
1504 * or hostname
1505 */
1506 public static boolean isWellFormedAddress(String p_address)
1507 {
1508
1509 if (p_address == null)
1510 {
1511 return false;
1512 }
1513
1514 String address = p_address.trim();
1515 int addrLength = address.length();
1516
1517 if (addrLength == 0 || addrLength > 255)
1518 {
1519 return false;
1520 }
1521
1522 if (address.startsWith(".") || address.startsWith("-"))
1523 {
1524 return false;
1525 }
1526
1527
1528
1529
1530 int index = address.lastIndexOf('.');
1531
1532 if (address.endsWith("."))
1533 {
1534 index = address.substring(0, index).lastIndexOf('.');
1535 }
1536
1537 if (index + 1 < addrLength && isDigit(p_address.charAt(index + 1)))
1538 {
1539 char testChar;
1540 int numDots = 0;
1541
1542
1543
1544
1545 for (int i = 0; i < addrLength; i++)
1546 {
1547 testChar = address.charAt(i);
1548
1549 if (testChar == '.')
1550 {
1551 if (!isDigit(address.charAt(i - 1))
1552 || (i + 1 < addrLength &&!isDigit(address.charAt(i + 1))))
1553 {
1554 return false;
1555 }
1556
1557 numDots++;
1558 }
1559 else if (!isDigit(testChar))
1560 {
1561 return false;
1562 }
1563 }
1564
1565 if (numDots != 3)
1566 {
1567 return false;
1568 }
1569 }
1570 else
1571 {
1572
1573
1574
1575 char testChar;
1576
1577 for (int i = 0; i < addrLength; i++)
1578 {
1579 testChar = address.charAt(i);
1580
1581 if (testChar == '.')
1582 {
1583 if (!isAlphanum(address.charAt(i - 1)))
1584 {
1585 return false;
1586 }
1587
1588 if (i + 1 < addrLength &&!isAlphanum(address.charAt(i + 1)))
1589 {
1590 return false;
1591 }
1592 }
1593 else if (!isAlphanum(testChar) && testChar != '-')
1594 {
1595 return false;
1596 }
1597 }
1598 }
1599
1600 return true;
1601 }
1602
1603 /***
1604 * Determine whether a char is a digit.
1605 *
1606 *
1607 * @param p_char the character to check
1608 * @return true if the char is betweeen '0' and '9', false otherwise
1609 */
1610 private static boolean isDigit(char p_char)
1611 {
1612 return p_char >= '0' && p_char <= '9';
1613 }
1614
1615 /***
1616 * Determine whether a character is a hexadecimal character.
1617 *
1618 *
1619 * @param p_char the character to check
1620 * @return true if the char is betweeen '0' and '9', 'a' and 'f'
1621 * or 'A' and 'F', false otherwise
1622 */
1623 private static boolean isHex(char p_char)
1624 {
1625 return (isDigit(p_char) || (p_char >= 'a' && p_char <= 'f')
1626 || (p_char >= 'A' && p_char <= 'F'));
1627 }
1628
1629 /***
1630 * Determine whether a char is an alphabetic character: a-z or A-Z
1631 *
1632 *
1633 * @param p_char the character to check
1634 * @return true if the char is alphabetic, false otherwise
1635 */
1636 private static boolean isAlpha(char p_char)
1637 {
1638 return ((p_char >= 'a' && p_char <= 'z')
1639 || (p_char >= 'A' && p_char <= 'Z'));
1640 }
1641
1642 /***
1643 * Determine whether a char is an alphanumeric: 0-9, a-z or A-Z
1644 *
1645 *
1646 * @param p_char the character to check
1647 * @return true if the char is alphanumeric, false otherwise
1648 */
1649 private static boolean isAlphanum(char p_char)
1650 {
1651 return (isAlpha(p_char) || isDigit(p_char));
1652 }
1653
1654 /***
1655 * Determine whether a character is a reserved character:
1656 * ';', '/', '?', ':', '@', '&', '=', '+', '$' or ','
1657 *
1658 *
1659 * @param p_char the character to check
1660 * @return true if the string contains any reserved characters
1661 */
1662 private static boolean isReservedCharacter(char p_char)
1663 {
1664 return RESERVED_CHARACTERS.indexOf(p_char) != -1;
1665 }
1666
1667 /***
1668 * Determine whether a char is an unreserved character.
1669 *
1670 *
1671 * @param p_char the character to check
1672 * @return true if the char is unreserved, false otherwise
1673 */
1674 private static boolean isUnreservedCharacter(char p_char)
1675 {
1676 return (isAlphanum(p_char) || MARK_CHARACTERS.indexOf(p_char) != -1);
1677 }
1678
1679 /***
1680 * Determine whether a given string contains only URI characters (also
1681 * called "uric" in RFC 2396). uric consist of all reserved
1682 * characters, unreserved characters and escaped characters.
1683 *
1684 *
1685 * @param p_uric URI string
1686 * @return true if the string is comprised of uric, false otherwise
1687 */
1688 private static boolean isURIString(String p_uric)
1689 {
1690
1691 if (p_uric == null)
1692 {
1693 return false;
1694 }
1695
1696 int end = p_uric.length();
1697 char testChar = '\0';
1698
1699 for (int i = 0; i < end; i++)
1700 {
1701 testChar = p_uric.charAt(i);
1702
1703 if (testChar == '%')
1704 {
1705 if (i + 2 >= end ||!isHex(p_uric.charAt(i + 1))
1706 ||!isHex(p_uric.charAt(i + 2)))
1707 {
1708 return false;
1709 }
1710 else
1711 {
1712 i += 2;
1713
1714 continue;
1715 }
1716 }
1717
1718 if (isReservedCharacter(testChar) || isUnreservedCharacter(testChar))
1719 {
1720 continue;
1721 }
1722 else
1723 {
1724 return false;
1725 }
1726 }
1727
1728 return true;
1729 }
1730 }
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742