source: code/Website/open-flash-chart/com/adobe/net/URI.as@ 7849

Last change on this file since 7849 was 7849, checked in by dennisw, 15 years ago
File size: 74.5 KB
Line 
1/*
2Adobe Systems Incorporated(r) Source Code License Agreement
3Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
4
5Please read this Source Code License Agreement carefully before using
6the source code.
7
8Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
9no-charge, royalty-free, irrevocable copyright license, to reproduce,
10prepare derivative works of, publicly display, publicly perform, and
11distribute this source code and such derivative works in source or
12object code form without any attribution requirements.
13
14The name "Adobe Systems Incorporated" must not be used to endorse or promote products
15derived from the source code without prior written permission.
16
17You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
18against any loss, damage, claims or lawsuits, including attorney's
19fees that arise or result from your use or distribution of the source
20code.
21
22THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
23ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
24BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
26NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
27OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
30OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
32OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
33ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34*/
35
36package com.adobe.net
37{
38 import flash.utils.ByteArray;
39
40 /**
41 * This class implements functions and utilities for working with URI's
42 * (Universal Resource Identifiers). For technical description of the
43 * URI syntax, please see RFC 3986 at http://www.ietf.org/rfc/rfc3986.txt
44 * or do a web search for "rfc 3986".
45 *
46 * <p>The most important aspect of URI's to understand is that URI's
47 * and URL's are not strings. URI's are complex data structures that
48 * encapsulate many pieces of information. The string version of a
49 * URI is the serialized representation of that data structure. This
50 * string serialization is used to provide a human readable
51 * representation and a means to transport the data over the network
52 * where it can then be parsed back into its' component parts.</p>
53 *
54 * <p>URI's fall into one of three categories:
55 * <ul>
56 * <li>&lt;scheme&gt;:&lt;scheme-specific-part&gt;#&lt;fragment&gt; (non-hierarchical)</li>
57 * <li>&lt;scheme&gt;:<authority&gt;&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt; (hierarchical)</li>
58 * <li>&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt; (relative hierarchical)</li>
59 * </ul></p>
60 *
61 * <p>The query and fragment parts are optional.</p>
62 *
63 * <p>This class supports both non-hierarchical and hierarchical URI's</p>
64 *
65 * <p>This class is intended to be used "as-is" for the vast majority
66 * of common URI's. However, if your application requires a custom
67 * URI syntax (e.g. custom query syntax or special handling of
68 * non-hierarchical URI's), this class can be fully subclassed. If you
69 * intended to subclass URI, please see the source code for complete
70 * documation on protected members and protected fuctions.</p>
71 *
72 * @langversion ActionScript 3.0
73 * @playerversion Flash 9.0
74 */
75 public class URI
76 {
77 // Here we define which characters must be escaped for each
78 // URI part. The characters that must be escaped for each
79 // part differ depending on what would cause ambiguous parsing.
80 // RFC 3986 sec. 2.4 states that characters should only be
81 // encoded when they would conflict with subcomponent delimiters.
82 // We don't want to over-do the escaping. We only want to escape
83 // the minimum needed to prevent parsing problems.
84
85 // space and % must be escaped in all cases. '%' is the delimiter
86 // for escaped characters.
87 public static const URImustEscape:String = " %";
88
89 // Baseline of what characters must be escaped
90 public static const URIbaselineEscape:String = URImustEscape + ":?#/@";
91
92 // Characters that must be escaped in the part part.
93 public static const URIpathEscape:String = URImustEscape + "?#";
94
95 // Characters that must be escaped in the query part, if setting
96 // the query as a whole string. If the query is set by
97 // name/value, URIqueryPartEscape is used instead.
98 public static const URIqueryEscape:String = URImustEscape + "#";
99
100 // This is what each name/value pair must escape "&=" as well
101 // so they don't conflict with the "param=value&param2=value2"
102 // syntax.
103 public static const URIqueryPartEscape:String = URImustEscape + "#&=";
104
105 // Non-hierarchical URI's can have query and fragment parts, but
106 // we also want to prevent '/' otherwise it might end up looking
107 // like a hierarchical URI to the parser.
108 public static const URInonHierEscape:String = URImustEscape + "?#/";
109
110 // Baseline uninitialized setting for the URI scheme.
111 public static const UNKNOWN_SCHEME:String = "unknown";
112
113 // The following bitmaps are used for performance enhanced
114 // character escaping.
115
116 // Baseline characters that need to be escaped. Many parts use
117 // this.
118 protected static const URIbaselineExcludedBitmap:URIEncodingBitmap =
119 new URIEncodingBitmap(URIbaselineEscape);
120
121 // Scheme escaping bitmap
122 protected static const URIschemeExcludedBitmap:URIEncodingBitmap =
123 URIbaselineExcludedBitmap;
124
125 // User/pass escaping bitmap
126 protected static const URIuserpassExcludedBitmap:URIEncodingBitmap =
127 URIbaselineExcludedBitmap;
128
129 // Authority escaping bitmap
130 protected static const URIauthorityExcludedBitmap:URIEncodingBitmap =
131 URIbaselineExcludedBitmap;
132
133 // Port escaping bitmap
134 protected static const URIportExludedBitmap:URIEncodingBitmap =
135 URIbaselineExcludedBitmap;
136
137 // Path escaping bitmap
138 protected static const URIpathExcludedBitmap:URIEncodingBitmap =
139 new URIEncodingBitmap(URIpathEscape);
140
141 // Query (whole) escaping bitmap
142 protected static const URIqueryExcludedBitmap:URIEncodingBitmap =
143 new URIEncodingBitmap(URIqueryEscape);
144
145 // Query (individual parts) escaping bitmap
146 protected static const URIqueryPartExcludedBitmap:URIEncodingBitmap =
147 new URIEncodingBitmap(URIqueryPartEscape);
148
149 // Fragments are the last part in the URI. They only need to
150 // escape space, '#', and '%'. Turns out that is what query
151 // uses too.
152 protected static const URIfragmentExcludedBitmap:URIEncodingBitmap =
153 URIqueryExcludedBitmap;
154
155 // Characters that need to be escaped in the non-hierarchical part
156 protected static const URInonHierexcludedBitmap:URIEncodingBitmap =
157 new URIEncodingBitmap(URInonHierEscape);
158
159 // Values used by getRelation()
160 public static const NOT_RELATED:int = 0;
161 public static const CHILD:int = 1;
162 public static const EQUAL:int = 2;
163 public static const PARENT:int = 3;
164
165 //-------------------------------------------------------------------
166 // protected class members
167 //-------------------------------------------------------------------
168 protected var _valid:Boolean = false;
169 protected var _relative:Boolean = false;
170 protected var _scheme:String = "";
171 protected var _authority:String = "";
172 protected var _username:String = "";
173 protected var _password:String = "";
174 protected var _port:String = "";
175 protected var _path:String = "";
176 protected var _query:String = "";
177 protected var _fragment:String = "";
178 protected var _nonHierarchical:String = "";
179 protected static var _resolver:IURIResolver = null;
180
181
182 /**
183 * URI Constructor. If no string is given, this will initialize
184 * this URI object to a blank URI.
185 */
186 public function URI(uri:String = null) : void
187 {
188 if (uri == null)
189 initialize();
190 else
191 constructURI(uri);
192 }
193
194
195 /**
196 * @private
197 * Method that loads the URI from the given string.
198 */
199 protected function constructURI(uri:String) : Boolean
200 {
201 if (!parseURI(uri))
202 _valid = false;
203
204 return isValid();
205 }
206
207
208 /**
209 * @private Private initializiation.
210 */
211 protected function initialize() : void
212 {
213 _valid = false;
214 _relative = false;
215
216 _scheme = UNKNOWN_SCHEME;
217 _authority = "";
218 _username = "";
219 _password = "";
220 _port = "";
221 _path = "";
222 _query = "";
223 _fragment = "";
224
225 _nonHierarchical = "";
226 }
227
228 /**
229 * @private Accessor to explicitly set/get the hierarchical
230 * state of the URI.
231 */
232 protected function set hierState(state:Boolean) : void
233 {
234 if (state)
235 {
236 // Clear the non-hierarchical data
237 _nonHierarchical = "";
238
239 // Also set the state vars while we are at it
240 if (_scheme == "" || _scheme == UNKNOWN_SCHEME)
241 _relative = true;
242 else
243 _relative = false;
244
245 if (_authority.length == 0 && _path.length == 0)
246 _valid = false;
247 else
248 _valid = true;
249 }
250 else
251 {
252 // Clear the hierarchical data
253 _authority = "";
254 _username = "";
255 _password = "";
256 _port = "";
257 _path = "";
258
259 _relative = false;
260
261 if (_scheme == "" || _scheme == UNKNOWN_SCHEME)
262 _valid = false;
263 else
264 _valid = true;
265 }
266 }
267 protected function get hierState() : Boolean
268 {
269 return (_nonHierarchical.length == 0);
270 }
271
272
273 /**
274 * @private Functions that performs some basic consistency validation.
275 */
276 protected function validateURI() : Boolean
277 {
278 // Check the scheme
279 if (isAbsolute())
280 {
281 if (_scheme.length <= 1 || _scheme == UNKNOWN_SCHEME)
282 {
283 // we probably parsed a C:\ type path or no scheme
284 return false;
285 }
286 else if (verifyAlpha(_scheme) == false)
287 return false; // Scheme contains bad characters
288 }
289
290 if (hierState)
291 {
292 if (_path.search('\\') != -1)
293 return false; // local path
294 else if (isRelative() == false && _scheme == UNKNOWN_SCHEME)
295 return false; // It's an absolute URI, but it has a bad scheme
296 }
297 else
298 {
299 if (_nonHierarchical.search('\\') != -1)
300 return false; // some kind of local path
301 }
302
303 // Looks like it's ok.
304 return true;
305 }
306
307
308 /**
309 * @private
310 *
311 * Given a URI in string format, parse that sucker into its basic
312 * components and assign them to this object. A URI is of the form:
313 * <scheme>:<authority><path>?<query>#<fragment>
314 *
315 * For simplicity, we parse the URI in the following order:
316 *
317 * 1. Fragment (anchors)
318 * 2. Query (CGI stuff)
319 * 3. Scheme ("http")
320 * 4. Authority (host name)
321 * 5. Username/Password (if any)
322 * 6. Port (server port if any)
323 * 7. Path (/homepages/mypage.html)
324 *
325 * The reason for this order is to minimize any parsing ambiguities.
326 * Fragments and queries can contain almost anything (they are parts
327 * that can contain custom data with their own syntax). Parsing
328 * them out first removes a large chance of parsing errors. This
329 * method expects well formed URI's, but performing the parse in
330 * this order makes us a little more tolerant of user error.
331 *
332 * REGEXP
333 * Why doesn't this use regular expressions to parse the URI? We
334 * have found that in a real world scenario, URI's are not always
335 * well formed. Sometimes characters that should have been escaped
336 * are not, and those situations would break a regexp pattern. This
337 * function attempts to be smart about what it is parsing based on
338 * location of characters relative to eachother. This function has
339 * been proven through real-world use to parse the vast majority
340 * of URI's correctly.
341 *
342 * NOTE
343 * It is assumed that the string in URI form is escaped. This function
344 * does not escape anything. If you constructed the URI string by
345 * hand, and used this to parse in the URI and still need it escaped,
346 * call forceEscape() on your URI object.
347 *
348 * Parsing Assumptions
349 * This routine assumes that the URI being passed is well formed.
350 * Passing things like local paths, malformed URI's, and the such
351 * will result in parsing errors. This function can handle
352 * - absolute hierarchical (e.g. "http://something.com/index.html),
353 * - relative hierarchical (e.g. "../images/flower.gif"), or
354 * - non-hierarchical URIs (e.g. "mailto:jsmith@fungoo.com").
355 *
356 * Anything else will probably result in a parsing error, or a bogus
357 * URI object.
358 *
359 * Note that non-hierarchical URIs *MUST* have a scheme, otherwise
360 * they will be mistaken for relative URI's.
361 *
362 * If you are not sure what is being passed to you (like manually
363 * entered text from UI), you can construct a blank URI object and
364 * call unknownToURI() passing in the unknown string.
365 *
366 * @return true if successful, false if there was some kind of
367 * parsing error
368 */
369 protected function parseURI(uri:String) : Boolean
370 {
371 var baseURI:String = uri;
372 var index:int, index2:int;
373
374 // Make sure this object is clean before we start. If it was used
375 // before and we are now parsing a new URI, we don't want any stale
376 // info lying around.
377 initialize();
378
379 // Remove any fragments (anchors) from the URI
380 index = baseURI.indexOf("#");
381 if (index != -1)
382 {
383 // Store the fragment piece if any
384 if (baseURI.length > (index + 1)) // +1 is to skip the '#'
385 _fragment = baseURI.substr(index + 1, baseURI.length - (index + 1));
386
387 // Trim off the fragment
388 baseURI = baseURI.substr(0, index);
389 }
390
391 // We need to strip off any CGI parameters (eg '?param=bob')
392 index = baseURI.indexOf("?");
393 if (index != -1)
394 {
395 if (baseURI.length > (index + 1))
396 _query = baseURI.substr(index + 1, baseURI.length - (index + 1)); // +1 is to skip the '?'
397
398 // Trim off the query
399 baseURI = baseURI.substr(0, index);
400 }
401
402 // Now try to find the scheme part
403 index = baseURI.search(':');
404 index2 = baseURI.search('/');
405
406 var containsColon:Boolean = (index != -1);
407 var containsSlash:Boolean = (index2 != -1);
408
409 // This value is indeterminate if "containsColon" is false.
410 // (if there is no colon, does the slash come before or
411 // after said non-existing colon?)
412 var colonBeforeSlash:Boolean = (!containsSlash || index < index2);
413
414 // If it has a colon and it's before the first slash, we will treat
415 // it as a scheme. If a slash is before a colon, there must be a
416 // stray colon in a path or something. In which case, the colon is
417 // not the separator for the scheme. Technically, we could consider
418 // this an error, but since this is not an ambiguous state (we know
419 // 100% that this has no scheme), we will keep going.
420 if (containsColon && colonBeforeSlash)
421 {
422 // We found a scheme
423 _scheme = baseURI.substr(0, index);
424
425 // Normalize the scheme
426 _scheme = _scheme.toLowerCase();
427
428 baseURI = baseURI.substr(index + 1);
429
430 if (baseURI.substr(0, 2) == "//")
431 {
432 // This is a hierarchical URI
433 _nonHierarchical = "";
434
435 // Trim off the "//"
436 baseURI = baseURI.substr(2, baseURI.length - 2);
437 }
438 else
439 {
440 // This is a non-hierarchical URI like "mailto:bob@mail.com"
441 _nonHierarchical = baseURI;
442
443 if ((_valid = validateURI()) == false)
444 initialize(); // Bad URI. Clear it.
445
446 // No more parsing to do for this case
447 return isValid();
448 }
449 }
450 else
451 {
452 // No scheme. We will consider this a relative URI
453 _scheme = "";
454 _relative = true;
455 _nonHierarchical = "";
456 }
457
458 // Ok, what we have left is everything after the <scheme>://
459
460 // Now that we have stripped off any query and fragment parts, we
461 // need to split the authority from the path
462
463 if (isRelative())
464 {
465 // Don't bother looking for the authority. It's a relative URI
466 _authority = "";
467 _port = "";
468 _path = baseURI;
469 }
470 else
471 {
472 // Check for malformed UNC style file://///server/type/path/
473 // By the time we get here, we have already trimmed the "file://"
474 // so baseURI will be ///server/type/path. If baseURI only
475 // has one slash, we leave it alone because that is valid (that
476 // is the case of "file:///path/to/file.txt" where there is no
477 // server - implicit "localhost").
478 if (baseURI.substr(0, 2) == "//")
479 {
480 // Trim all leading slashes
481 while(baseURI.charAt(0) == "/")
482 baseURI = baseURI.substr(1, baseURI.length - 1);
483 }
484
485 index = baseURI.search('/');
486 if (index == -1)
487 {
488 // No path. We must have passed something like "http://something.com"
489 _authority = baseURI;
490 _path = "";
491 }
492 else
493 {
494 _authority = baseURI.substr(0, index);
495 _path = baseURI.substr(index, baseURI.length - index);
496 }
497
498 // Check to see if the URI has any username or password information.
499 // For example: ftp://username:password@server.com
500 index = _authority.search('@');
501 if (index != -1)
502 {
503 // We have a username and possibly a password
504 _username = _authority.substr(0, index);
505
506 // Remove the username/password from the authority
507 _authority = _authority.substr(index + 1); // Skip the '@'
508
509 // Now check to see if the username also has a password
510 index = _username.search(':');
511 if (index != -1)
512 {
513 _password = _username.substring(index + 1, _username.length);
514 _username = _username.substr(0, index);
515 }
516 else
517 _password = "";
518 }
519 else
520 {
521 _username = "";
522 _password = "";
523 }
524
525 // Lastly, check to see if the authorty has a port number.
526 // This is parsed after the username/password to avoid conflicting
527 // with the ':' in the 'username:password' if one exists.
528 index = _authority.search(':');
529 if (index != -1)
530 {
531 _port = _authority.substring(index + 1, _authority.length); // skip the ':'
532 _authority = _authority.substr(0, index);
533 }
534 else
535 {
536 _port = "";
537 }
538
539 // Lastly, normalize the authority. Domain names
540 // are case insensitive.
541 _authority = _authority.toLowerCase();
542 }
543
544 if ((_valid = validateURI()) == false)
545 initialize(); // Bad URI. Clear it
546
547 return isValid();
548 }
549
550
551 /********************************************************************
552 * Copy function.
553 */
554 public function copyURI(uri:URI) : void
555 {
556 this._scheme = uri._scheme;
557 this._authority = uri._authority;
558 this._username = uri._username;
559 this._password = uri._password;
560 this._port = uri._port;
561 this._path = uri._path;
562 this._query = uri._query;
563 this._fragment = uri._fragment;
564 this._nonHierarchical = uri._nonHierarchical;
565
566 this._valid = uri._valid;
567 this._relative = uri._relative;
568 }
569
570
571 /**
572 * @private
573 * Checks if the given string only contains a-z or A-Z.
574 */
575 protected function verifyAlpha(str:String) : Boolean
576 {
577 var pattern:RegExp = /[^a-z]/;
578 var index:int;
579
580 str = str.toLowerCase();
581 index = str.search(pattern);
582
583 if (index == -1)
584 return true;
585 else
586 return false;
587 }
588
589 /**
590 * Is this a valid URI?
591 *
592 * @return true if this object represents a valid URI, false
593 * otherwise.
594 */
595 public function isValid() : Boolean
596 {
597 return this._valid;
598 }
599
600
601 /**
602 * Is this URI an absolute URI? An absolute URI is a complete, fully
603 * qualified reference to a resource. e.g. http://site.com/index.htm
604 * Non-hierarchical URI's are always absolute.
605 */
606 public function isAbsolute() : Boolean
607 {
608 return !this._relative;
609 }
610
611
612 /**
613 * Is this URI a relative URI? Relative URI's do not have a scheme
614 * and only contain a relative path with optional anchor and query
615 * parts. e.g. "../reports/index.htm". Non-hierarchical URI's
616 * will never be relative.
617 */
618 public function isRelative() : Boolean
619 {
620 return this._relative;
621 }
622
623
624 /**
625 * Does this URI point to a resource that is a directory/folder?
626 * The URI specification dictates that any path that ends in a slash
627 * is a directory. This is needed to be able to perform correct path
628 * logic when combining relative URI's with absolute URI's to
629 * obtain the correct absolute URI to a resource.
630 *
631 * @see URI.chdir
632 *
633 * @return true if this URI represents a directory resource, false
634 * if this URI represents a file resource.
635 */
636 public function isDirectory() : Boolean
637 {
638 if (_path.length == 0)
639 return false;
640
641 return (_path.charAt(path.length - 1) == '/');
642 }
643
644
645 /**
646 * Is this URI a hierarchical URI? URI's can be
647 */
648 public function isHierarchical() : Boolean
649 {
650 return hierState;
651 }
652
653
654 /**
655 * The scheme of the URI.
656 */
657 public function get scheme() : String
658 {
659 return URI.unescapeChars(_scheme);
660 }
661 public function set scheme(schemeStr:String) : void
662 {
663 // Normalize the scheme
664 var normalized:String = schemeStr.toLowerCase();
665 _scheme = URI.fastEscapeChars(normalized, URI.URIschemeExcludedBitmap);
666 }
667
668
669 /**
670 * The authority (host) of the URI. Only valid for
671 * hierarchical URI's. If the URI is relative, this will
672 * be an empty string. When setting this value, the string
673 * given is assumed to be unescaped. When retrieving this
674 * value, the resulting string is unescaped.
675 */
676 public function get authority() : String
677 {
678 return URI.unescapeChars(_authority);
679 }
680 public function set authority(authorityStr:String) : void
681 {
682 // Normalize the authority
683 authorityStr = authorityStr.toLowerCase();
684
685 _authority = URI.fastEscapeChars(authorityStr,
686 URI.URIauthorityExcludedBitmap);
687
688 // Only hierarchical URI's can have an authority, make
689 // sure this URI is of the proper format.
690 this.hierState = true;
691 }
692
693
694 /**
695 * The username of the URI. Only valid for hierarchical
696 * URI's. If the URI is relative, this will be an empty
697 * string.
698 *
699 * <p>The URI specification allows for authentication
700 * credentials to be embedded in the URI as such:</p>
701 *
702 * <p>http://user:passwd@host/path/to/file.htm</p>
703 *
704 * <p>When setting this value, the string
705 * given is assumed to be unescaped. When retrieving this
706 * value, the resulting string is unescaped.</p>
707 */
708 public function get username() : String
709 {
710 return URI.unescapeChars(_username);
711 }
712 public function set username(usernameStr:String) : void
713 {
714 _username = URI.fastEscapeChars(usernameStr, URI.URIuserpassExcludedBitmap);
715
716 // Only hierarchical URI's can have a username.
717 this.hierState = true;
718 }
719
720
721 /**
722 * The password of the URI. Similar to username.
723 * @see URI.username
724 */
725 public function get password() : String
726 {
727 return URI.unescapeChars(_password);
728 }
729 public function set password(passwordStr:String) : void
730 {
731 _password = URI.fastEscapeChars(passwordStr,
732 URI.URIuserpassExcludedBitmap);
733
734 // Only hierarchical URI's can have a password.
735 this.hierState = true;
736 }
737
738
739 /**
740 * The host port number. Only valid for hierarchical URI's. If
741 * the URI is relative, this will be an empty string. URI's can
742 * contain the port number of the remote host:
743 *
744 * <p>http://site.com:8080/index.htm</p>
745 */
746 public function get port() : String
747 {
748 return URI.unescapeChars(_port);
749 }
750 public function set port(portStr:String) : void
751 {
752 _port = URI.escapeChars(portStr);
753
754 // Only hierarchical URI's can have a port.
755 this.hierState = true;
756 }
757
758
759 /**
760 * The path portion of the URI. Only valid for hierarchical
761 * URI's. When setting this value, the string
762 * given is assumed to be unescaped. When retrieving this
763 * value, the resulting string is unescaped.
764 *
765 * <p>The path portion can be in one of two formats. 1) an absolute
766 * path, or 2) a relative path. An absolute path starts with a
767 * slash ('/'), a relative path does not.</p>
768 *
769 * <p>An absolute path may look like:</p>
770 * <listing>/full/path/to/my/file.htm</listing>
771 *
772 * <p>A relative path may look like:</p>
773 * <listing>
774 * path/to/my/file.htm
775 * ../images/logo.gif
776 * ../../reports/index.htm
777 * </listing>
778 *
779 * <p>Paths can be absolute or relative. Note that this not the same as
780 * an absolute or relative URI. An absolute URI can only have absolute
781 * paths. For example:</p>
782 *
783 * <listing>http:/site.com/path/to/file.htm</listing>
784 *
785 * <p>This absolute URI has an absolute path of "/path/to/file.htm".</p>
786 *
787 * <p>Relative URI's can have either absolute paths or relative paths.
788 * All of the following relative URI's are valid:</p>
789 *
790 * <listing>
791 * /absolute/path/to/file.htm
792 * path/to/file.htm
793 * ../path/to/file.htm
794 * </listing>
795 */
796 public function get path() : String
797 {
798 return URI.unescapeChars(_path);
799 }
800 public function set path(pathStr:String) : void
801 {
802 this._path = URI.fastEscapeChars(pathStr, URI.URIpathExcludedBitmap);
803
804 if (this._scheme == UNKNOWN_SCHEME)
805 {
806 // We set the path. This is a valid URI now.
807 this._scheme = "";
808 }
809
810 // Only hierarchical URI's can have a path.
811 hierState = true;
812 }
813
814
815 /**
816 * The query (CGI) portion of the URI. This part is valid for
817 * both hierarchical and non-hierarchical URI's.
818 *
819 * <p>This accessor should only be used if a custom query syntax
820 * is used. This URI class supports the common "param=value"
821 * style query syntax via the get/setQueryValue() and
822 * get/setQueryByMap() functions. Those functions should be used
823 * instead if the common syntax is being used.
824 *
825 * <p>The URI RFC does not specify any particular
826 * syntax for the query part of a URI. It is intended to allow
827 * any format that can be agreed upon by the two communicating hosts.
828 * However, most systems have standardized on the typical CGI
829 * format:</p>
830 *
831 * <listing>http://site.com/script.php?param1=value1&param2=value2</listing>
832 *
833 * <p>This class has specific support for this query syntax</p>
834 *
835 * <p>This common query format is an array of name/value
836 * pairs with its own syntax that is different from the overall URI
837 * syntax. The query has its own escaping logic. For a query part
838 * to be properly escaped and unescaped, it must be split into its
839 * component parts. This accessor escapes/unescapes the entire query
840 * part without regard for it's component parts. This has the
841 * possibliity of leaving the query string in an ambiguious state in
842 * regards to its syntax. If the contents of the query part are
843 * important, it is recommended that get/setQueryValue() or
844 * get/setQueryByMap() are used instead.</p>
845 *
846 * If a different query syntax is being used, a subclass of URI
847 * can be created to handle that specific syntax.
848 *
849 * @see URI.getQueryValue, URI.getQueryByMap
850 */
851 public function get query() : String
852 {
853 return URI.unescapeChars(_query);
854 }
855 public function set query(queryStr:String) : void
856 {
857 _query = URI.fastEscapeChars(queryStr, URI.URIqueryExcludedBitmap);
858
859 // both hierarchical and non-hierarchical URI's can
860 // have a query. Do not set the hierState.
861 }
862
863 /**
864 * Accessor to the raw query data. If you are using a custom query
865 * syntax, this accessor can be used to get and set the query part
866 * directly with no escaping/unescaping. This should ONLY be used
867 * if your application logic is handling custom query logic and
868 * handling the proper escaping of the query part.
869 */
870 public function get queryRaw() : String
871 {
872 return _query;
873 }
874 public function set queryRaw(queryStr:String) : void
875 {
876 _query = queryStr;
877 }
878
879
880 /**
881 * The fragment (anchor) portion of the URI. This is valid for
882 * both hierarchical and non-hierarchical URI's.
883 */
884 public function get fragment() : String
885 {
886 return URI.unescapeChars(_fragment);
887 }
888 public function set fragment(fragmentStr:String) : void
889 {
890 _fragment = URI.fastEscapeChars(fragmentStr, URIfragmentExcludedBitmap);
891
892 // both hierarchical and non-hierarchical URI's can
893 // have a fragment. Do not set the hierState.
894 }
895
896
897 /**
898 * The non-hierarchical part of the URI. For example, if
899 * this URI object represents "mailto:somebody@company.com",
900 * this will contain "somebody@company.com". This is valid only
901 * for non-hierarchical URI's.
902 */
903 public function get nonHierarchical() : String
904 {
905 return URI.unescapeChars(_nonHierarchical);
906 }
907 public function set nonHierarchical(nonHier:String) : void
908 {
909 _nonHierarchical = URI.fastEscapeChars(nonHier, URInonHierexcludedBitmap);
910
911 // This is a non-hierarchical URI.
912 this.hierState = false;
913 }
914
915
916 /**
917 * Quick shorthand accessor to set the parts of this URI.
918 * The given parts are assumed to be in unescaped form. If
919 * the URI is non-hierarchical (e.g. mailto:) you will need
920 * to call SetScheme() and SetNonHierarchical().
921 */
922 public function setParts(schemeStr:String, authorityStr:String,
923 portStr:String, pathStr:String, queryStr:String,
924 fragmentStr:String) : void
925 {
926 this.scheme = schemeStr;
927 this.authority = authorityStr;
928 this.port = portStr;
929 this.path = pathStr;
930 this.query = queryStr;
931 this.fragment = fragmentStr;
932
933 hierState = true;
934 }
935
936
937 /**
938 * URI escapes the given character string. This is similar in function
939 * to the global encodeURIComponent() function in ActionScript, but is
940 * slightly different in regards to which characters get escaped. This
941 * escapes the characters specified in the URIbaselineExluded set (see class
942 * static members). This is needed for this class to work properly.
943 *
944 * <p>If a different set of characters need to be used for the escaping,
945 * you may use fastEscapeChars() and specify a custom URIEncodingBitmap
946 * that contains the characters your application needs escaped.</p>
947 *
948 * <p>Never pass a full URI to this function. A URI can only be properly
949 * escaped/unescaped when split into its component parts (see RFC 3986
950 * section 2.4). This is due to the fact that the URI component separators
951 * could be characters that would normally need to be escaped.</p>
952 *
953 * @param unescaped character string to be escaped.
954 *
955 * @return escaped character string
956 *
957 * @see encodeURIComponent
958 * @see fastEscapeChars
959 */
960 static public function escapeChars(unescaped:String) : String
961 {
962 // This uses the excluded set by default.
963 return fastEscapeChars(unescaped, URI.URIbaselineExcludedBitmap);
964 }
965
966
967 /**
968 * Unescape any URI escaped characters in the given character
969 * string.
970 *
971 * <p>Never pass a full URI to this function. A URI can only be properly
972 * escaped/unescaped when split into its component parts (see RFC 3986
973 * section 2.4). This is due to the fact that the URI component separators
974 * could be characters that would normally need to be escaped.</p>
975 *
976 * @param escaped the escaped string to be unescaped.
977 *
978 * @return unescaped string.
979 */
980 static public function unescapeChars(escaped:String /*, onlyHighASCII:Boolean = false*/) : String
981 {
982 // We can just use the default AS function. It seems to
983 // decode everything correctly
984 var unescaped:String;
985 unescaped = decodeURIComponent(escaped);
986 return unescaped;
987 }
988
989 /**
990 * Performance focused function that escapes the given character
991 * string using the given URIEncodingBitmap as the rule for what
992 * characters need to be escaped. This function is used by this
993 * class and can be used externally to this class to perform
994 * escaping on custom character sets.
995 *
996 * <p>Never pass a full URI to this function. A URI can only be properly
997 * escaped/unescaped when split into its component parts (see RFC 3986
998 * section 2.4). This is due to the fact that the URI component separators
999 * could be characters that would normally need to be escaped.</p>
1000 *
1001 * @param unescaped the unescaped string to be escaped
1002 * @param bitmap the set of characters that need to be escaped
1003 *
1004 * @return the escaped string.
1005 */
1006 static public function fastEscapeChars(unescaped:String, bitmap:URIEncodingBitmap) : String
1007 {
1008 var escaped:String = "";
1009 var c:String;
1010 var x:int, i:int;
1011
1012 for (i = 0; i < unescaped.length; i++)
1013 {
1014 c = unescaped.charAt(i);
1015
1016 x = bitmap.ShouldEscape(c);
1017 if (x)
1018 {
1019 c = x.toString(16);
1020 if (c.length == 1)
1021 c = "0" + c;
1022
1023 c = "%" + c;
1024 c = c.toUpperCase();
1025 }
1026
1027 escaped += c;
1028 }
1029
1030 return escaped;
1031 }
1032
1033
1034 /**
1035 * Is this URI of a particular scheme type? For example,
1036 * passing "http" to a URI object that represents the URI
1037 * "http://site.com/" would return true.
1038 *
1039 * @param scheme scheme to check for
1040 *
1041 * @return true if this URI object is of the given type, false
1042 * otherwise.
1043 */
1044 public function isOfType(scheme:String) : Boolean
1045 {
1046 // Schemes are never case sensitive. Ignore case.
1047 scheme = scheme.toLowerCase();
1048 return (this._scheme == scheme);
1049 }
1050
1051
1052 /**
1053 * Get the value for the specified named in the query part. This
1054 * assumes the query part of the URI is in the common
1055 * "name1=value1&name2=value2" syntax. Do not call this function
1056 * if you are using a custom query syntax.
1057 *
1058 * @param name name of the query value to get.
1059 *
1060 * @return the value of the query name, empty string if the
1061 * query name does not exist.
1062 */
1063 public function getQueryValue(name:String) : String
1064 {
1065 var map:Object;
1066 var item:String;
1067 var value:String;
1068
1069 map = getQueryByMap();
1070
1071 for (item in map)
1072 {
1073 if (item == name)
1074 {
1075 value = map[item];
1076 return value;
1077 }
1078 }
1079
1080 // Didn't find the specified key
1081 return new String("");
1082 }
1083
1084
1085 /**
1086 * Set the given value on the given query name. If the given name
1087 * does not exist, it will automatically add this name/value pair
1088 * to the query. If null is passed as the value, it will remove
1089 * the given item from the query.
1090 *
1091 * <p>This automatically escapes any characters that may conflict with
1092 * the query syntax so that they are "safe" within the query. The
1093 * strings passed are assumed to be literal unescaped name and value.</p>
1094 *
1095 * @param name name of the query value to set
1096 * @param value value of the query item to set. If null, this will
1097 * force the removal of this item from the query.
1098 */
1099 public function setQueryValue(name:String, value:String) : void
1100 {
1101 var map:Object;
1102
1103 map = getQueryByMap();
1104
1105 // If the key doesn't exist yet, this will create a new pair in
1106 // the map. If it does exist, this will overwrite the previous
1107 // value, which is what we want.
1108 map[name] = value;
1109
1110 setQueryByMap(map);
1111 }
1112
1113
1114 /**
1115 * Get the query of the URI in an Object class that allows for easy
1116 * access to the query data via Object accessors. For example:
1117 *
1118 * <listing>
1119 * var query:Object = uri.getQueryByMap();
1120 * var value:String = query["param"]; // get a value
1121 * query["param2"] = "foo"; // set a new value
1122 * </listing>
1123 *
1124 * @return Object that contains the name/value pairs of the query.
1125 *
1126 * @see #setQueryByMap
1127 * @see #getQueryValue
1128 * @see #setQueryValue
1129 */
1130 public function getQueryByMap() : Object
1131 {
1132 var queryStr:String;
1133 var pair:String;
1134 var pairs:Array;
1135 var item:Array;
1136 var name:String, value:String;
1137 var index:int;
1138 var map:Object = new Object();
1139
1140
1141 // We need the raw query string, no unescaping.
1142 queryStr = this._query;
1143
1144 pairs = queryStr.split('&');
1145 for each (pair in pairs)
1146 {
1147 if (pair.length == 0)
1148 continue;
1149
1150 item = pair.split('=');
1151
1152 if (item.length > 0)
1153 name = item[0];
1154 else
1155 continue; // empty array
1156
1157 if (item.length > 1)
1158 value = item[1];
1159 else
1160 value = "";
1161
1162 name = queryPartUnescape(name);
1163 value = queryPartUnescape(value);
1164
1165 map[name] = value;
1166 }
1167
1168 return map;
1169 }
1170
1171
1172 /**
1173 * Set the query part of this URI using the given object as the
1174 * content source. Any member of the object that has a value of
1175 * null will not be in the resulting query.
1176 *
1177 * @param map object that contains the name/value pairs as
1178 * members of that object.
1179 *
1180 * @see #getQueryByMap
1181 * @see #getQueryValue
1182 * @see #setQueryValue
1183 */
1184 public function setQueryByMap(map:Object) : void
1185 {
1186 var item:String;
1187 var name:String, value:String;
1188 var queryStr:String = "";
1189 var tmpPair:String;
1190 var foo:String;
1191
1192 for (item in map)
1193 {
1194 name = item;
1195 value = map[item];
1196
1197 if (value == null)
1198 value = "";
1199
1200 // Need to escape the name/value pair so that they
1201 // don't conflict with the query syntax (specifically
1202 // '=', '&', and <whitespace>).
1203 name = queryPartEscape(name);
1204 value = queryPartEscape(value);
1205
1206 tmpPair = name;
1207
1208 if (value.length > 0)
1209 {
1210 tmpPair += "=";
1211 tmpPair += value;
1212 }
1213
1214 if (queryStr.length != 0)
1215 queryStr += '&'; // Add the separator
1216
1217 queryStr += tmpPair;
1218 }
1219
1220 // We don't want to escape. We already escaped the
1221 // individual name/value pairs. If we escaped the
1222 // query string again by assigning it to "query",
1223 // we would have double escaping.
1224 _query = queryStr;
1225 }
1226
1227
1228 /**
1229 * Similar to Escape(), except this also escapes characters that
1230 * would conflict with the name/value pair query syntax. This is
1231 * intended to be called on each individual "name" and "value"
1232 * in the query making sure that nothing in the name or value
1233 * strings contain characters that would conflict with the query
1234 * syntax (e.g. '=' and '&').
1235 *
1236 * @param unescaped unescaped string that is to be escaped.
1237 *
1238 * @return escaped string.
1239 *
1240 * @see #queryUnescape
1241 */
1242 static public function queryPartEscape(unescaped:String) : String
1243 {
1244 var escaped:String = unescaped;
1245 escaped = URI.fastEscapeChars(unescaped, URI.URIqueryPartExcludedBitmap);
1246 return escaped;
1247 }
1248
1249
1250 /**
1251 * Unescape the individual name/value string pairs.
1252 *
1253 * @param escaped escaped string to be unescaped
1254 *
1255 * @return unescaped string
1256 *
1257 * @see #queryEscape
1258 */
1259 static public function queryPartUnescape(escaped:String) : String
1260 {
1261 var unescaped:String = escaped;
1262 unescaped = unescapeChars(unescaped);
1263 return unescaped;
1264 }
1265
1266 /**
1267 * Output this URI as a string. The resulting string is properly
1268 * escaped and well formed for machine processing.
1269 */
1270 public function toString() : String
1271 {
1272 if (this == null)
1273 return "";
1274 else
1275 return toStringInternal(false);
1276 }
1277
1278 /**
1279 * Output the URI as a string that is easily readable by a human.
1280 * This outputs the URI with all escape sequences unescaped to
1281 * their character representation. This makes the URI easier for
1282 * a human to read, but the URI could be completely invalid
1283 * because some unescaped characters may now cause ambiguous parsing.
1284 * This function should only be used if you want to display a URI to
1285 * a user. This function should never be used outside that specific
1286 * case.
1287 *
1288 * @return the URI in string format with all escape sequences
1289 * unescaped.
1290 *
1291 * @see #toString
1292 */
1293 public function toDisplayString() : String
1294 {
1295 return toStringInternal(true);
1296 }
1297
1298
1299 /**
1300 * @private
1301 *
1302 * The guts of toString()
1303 */
1304 protected function toStringInternal(forDisplay:Boolean) : String
1305 {
1306 var uri:String = "";
1307 var part:String = "";
1308
1309 if (isHierarchical() == false)
1310 {
1311 // non-hierarchical URI
1312
1313 uri += (forDisplay ? this.scheme : _scheme);
1314 uri += ":";
1315 uri += (forDisplay ? this.nonHierarchical : _nonHierarchical);
1316 }
1317 else
1318 {
1319 // Hierarchical URI
1320
1321 if (isRelative() == false)
1322 {
1323 // If it is not a relative URI, then we want the scheme and
1324 // authority parts in the string. If it is relative, we
1325 // do NOT want this stuff.
1326
1327 if (_scheme.length != 0)
1328 {
1329 part = (forDisplay ? this.scheme : _scheme);
1330 uri += part + ":";
1331 }
1332
1333 if (_authority.length != 0 || isOfType("file"))
1334 {
1335 uri += "//";
1336
1337 // Add on any username/password associated with this
1338 // authority
1339 if (_username.length != 0)
1340 {
1341 part = (forDisplay ? this.username : _username);
1342 uri += part;
1343
1344 if (_password.length != 0)
1345 {
1346 part = (forDisplay ? this.password : _password);
1347 uri += ":" + part;
1348 }
1349
1350 uri += "@";
1351 }
1352
1353 // add the authority
1354 part = (forDisplay ? this.authority : _authority);
1355 uri += part;
1356
1357 // Tack on the port number, if any
1358 if (port.length != 0)
1359 uri += ":" + port;
1360 }
1361 }
1362
1363 // Tack on the path
1364 part = (forDisplay ? this.path : _path);
1365 uri += part;
1366
1367 } // end hierarchical part
1368
1369 // Both non-hier and hierarchical have query and fragment parts
1370
1371 // Add on the query and fragment parts
1372 if (_query.length != 0)
1373 {
1374 part = (forDisplay ? this.query : _query);
1375 uri += "?" + part;
1376 }
1377
1378 if (fragment.length != 0)
1379 {
1380 part = (forDisplay ? this.fragment : _fragment);
1381 uri += "#" + part;
1382 }
1383
1384 return uri;
1385 }
1386
1387 /**
1388 * Forcefully ensure that this URI is properly escaped.
1389 *
1390 * <p>Sometimes URI's are constructed by hand using strings outside
1391 * this class. In those cases, it is unlikely the URI has been
1392 * properly escaped. This function forcefully escapes this URI
1393 * by unescaping each part and then re-escaping it. If the URI
1394 * did not have any escaping, the first unescape will do nothing
1395 * and then the re-escape will properly escape everything. If
1396 * the URI was already escaped, the unescape and re-escape will
1397 * essentally be a no-op. This provides a safe way to make sure
1398 * a URI is in the proper escaped form.</p>
1399 */
1400 public function forceEscape() : void
1401 {
1402 // The accessors for each of the members will unescape
1403 // and then re-escape as we get and assign them.
1404
1405 // Handle the parts that are common for both hierarchical
1406 // and non-hierarchical URI's
1407 this.scheme = this.scheme;
1408 this.setQueryByMap(this.getQueryByMap());
1409 this.fragment = this.fragment;
1410
1411 if (isHierarchical())
1412 {
1413 this.authority = this.authority;
1414 this.path = this.path;
1415 this.port = this.port;
1416 this.username = this.username;
1417 this.password = this.password;
1418 }
1419 else
1420 {
1421 this.nonHierarchical = this.nonHierarchical;
1422 }
1423 }
1424
1425
1426 /**
1427 * Does this URI point to a resource of the given file type?
1428 * Given a file extension (or just a file name, this will strip the
1429 * extension), check to see if this URI points to a file of that
1430 * type.
1431 *
1432 * @param extension string that contains a file extension with or
1433 * without a dot ("html" and ".html" are both valid), or a file
1434 * name with an extension (e.g. "index.html").
1435 *
1436 * @return true if this URI points to a resource with the same file
1437 * file extension as the extension provided, false otherwise.
1438 */
1439 public function isOfFileType(extension:String) : Boolean
1440 {
1441 var thisExtension:String;
1442 var index:int;
1443
1444 index = extension.lastIndexOf(".");
1445 if (index != -1)
1446 {
1447 // Strip the extension
1448 extension = extension.substr(index + 1);
1449 }
1450 else
1451 {
1452 // The caller passed something without a dot in it. We
1453 // will assume that it is just a plain extension (e.g. "html").
1454 // What they passed is exactly what we want
1455 }
1456
1457 thisExtension = getExtension(true);
1458
1459 if (thisExtension == "")
1460 return false;
1461
1462 // Compare the extensions ignoring case
1463 if (compareStr(thisExtension, extension, false) == 0)
1464 return true;
1465 else
1466 return false;
1467 }
1468
1469
1470 /**
1471 * Get the ".xyz" file extension from the filename in the URI.
1472 * For example, if we have the following URI:
1473 *
1474 * <listing>http://something.com/path/to/my/page.html?form=yes&name=bob#anchor</listing>
1475 *
1476 * <p>This will return ".html".</p>
1477 *
1478 * @param minusDot If true, this will strip the dot from the extension.
1479 * If true, the above example would have returned "html".
1480 *
1481 * @return the file extension
1482 */
1483 public function getExtension(minusDot:Boolean = false) : String
1484 {
1485 var filename:String = getFilename();
1486 var extension:String;
1487 var index:int;
1488
1489 if (filename == "")
1490 return String("");
1491
1492 index = filename.lastIndexOf(".");
1493
1494 // If it doesn't have an extension, or if it is a "hidden" file,
1495 // it doesn't have an extension. Hidden files on unix start with
1496 // a dot (e.g. ".login").
1497 if (index == -1 || index == 0)
1498 return String("");
1499
1500 extension = filename.substr(index);
1501
1502 // If the caller does not want the dot, remove it.
1503 if (minusDot && extension.charAt(0) == ".")
1504 extension = extension.substr(1);
1505
1506 return extension;
1507 }
1508
1509 /**
1510 * Quick function to retrieve the file name off the end of a URI.
1511 *
1512 * <p>For example, if the URI is:</p>
1513 * <listing>http://something.com/some/path/to/my/file.html</listing>
1514 * <p>this function will return "file.html".</p>
1515 *
1516 * @param minusExtension true if the file extension should be stripped
1517 *
1518 * @return the file name. If this URI is a directory, the return
1519 * value will be empty string.
1520 */
1521 public function getFilename(minusExtension:Boolean = false) : String
1522 {
1523 if (isDirectory())
1524 return String("");
1525
1526 var pathStr:String = this.path;
1527 var filename:String;
1528 var index:int;
1529
1530 // Find the last path separator.
1531 index = pathStr.lastIndexOf("/");
1532
1533 if (index != -1)
1534 filename = pathStr.substr(index + 1);
1535 else
1536 filename = pathStr;
1537
1538 if (minusExtension)
1539 {
1540 // The caller has requested that the extension be removed
1541 index = filename.lastIndexOf(".");
1542
1543 if (index != -1)
1544 filename = filename.substr(0, index);
1545 }
1546
1547 return filename;
1548 }
1549
1550
1551 /**
1552 * @private
1553 * Helper function to compare strings.
1554 *
1555 * @return true if the two strings are identical, false otherwise.
1556 */
1557 static protected function compareStr(str1:String, str2:String,
1558 sensitive:Boolean = true) : Boolean
1559 {
1560 if (sensitive == false)
1561 {
1562 str1 = str1.toLowerCase();
1563 str2 = str2.toLowerCase();
1564 }
1565
1566 return (str1 == str2)
1567 }
1568
1569 /**
1570 * Based on the type of this URI (http, ftp, etc.) get
1571 * the default port used for that protocol. This is
1572 * just intended to be a helper function for the most
1573 * common cases.
1574 */
1575 public function getDefaultPort() : String
1576 {
1577 if (_scheme == "http")
1578 return String("80");
1579 else if (_scheme == "ftp")
1580 return String("21");
1581 else if (_scheme == "file")
1582 return String("");
1583 else if (_scheme == "sftp")
1584 return String("22"); // ssh standard port
1585 else
1586 {
1587 // Don't know the port for this URI type
1588 return String("");
1589 }
1590 }
1591
1592 /**
1593 * @private
1594 *
1595 * This resolves the given URI if the application has a
1596 * resolver interface defined. This function does not
1597 * modify the passed in URI and returns a new URI.
1598 */
1599 static protected function resolve(uri:URI) : URI
1600 {
1601 var copy:URI = new URI();
1602 copy.copyURI(uri);
1603
1604 if (_resolver != null)
1605 {
1606 // A resolver class has been registered. Call it.
1607 return _resolver.resolve(copy);
1608 }
1609 else
1610 {
1611 // No resolver. Nothing to do, but we don't
1612 // want to reuse the one passed in.
1613 return copy;
1614 }
1615 }
1616
1617 /**
1618 * Accessor to set and get the resolver object used by all URI
1619 * objects to dynamically resolve URI's before comparison.
1620 */
1621 static public function set resolver(resolver:IURIResolver) : void
1622 {
1623 _resolver = resolver;
1624 }
1625 static public function get resolver() : IURIResolver
1626 {
1627 return _resolver;
1628 }
1629
1630 /**
1631 * Given another URI, return this URI object's relation to the one given.
1632 * URI's can have 1 of 4 possible relationships. They can be unrelated,
1633 * equal, parent, or a child of the given URI.
1634 *
1635 * @param uri URI to compare this URI object to.
1636 * @param caseSensitive true if the URI comparison should be done
1637 * taking case into account, false if the comparison should be
1638 * performed case insensitive.
1639 *
1640 * @return URI.NOT_RELATED, URI.CHILD, URI.PARENT, or URI.EQUAL
1641 */
1642 public function getRelation(uri:URI, caseSensitive:Boolean = true) : int
1643 {
1644 // Give the app a chance to resolve these URI's before we compare them.
1645 var thisURI:URI = URI.resolve(this);
1646 var thatURI:URI = URI.resolve(uri);
1647
1648 if (thisURI.isRelative() || thatURI.isRelative())
1649 {
1650 // You cannot compare relative URI's due to their lack of context.
1651 // You could have two relative URI's that look like:
1652 // ../../images/
1653 // ../../images/marketing/logo.gif
1654 // These may appear related, but you have no overall context
1655 // from which to make the comparison. The first URI could be
1656 // from one site and the other URI could be from another site.
1657 return URI.NOT_RELATED;
1658 }
1659 else if (thisURI.isHierarchical() == false || thatURI.isHierarchical() == false)
1660 {
1661 // One or both of the URI's are non-hierarchical.
1662 if (((thisURI.isHierarchical() == false) && (thatURI.isHierarchical() == true)) ||
1663 ((thisURI.isHierarchical() == true) && (thatURI.isHierarchical() == false)))
1664 {
1665 // XOR. One is hierarchical and the other is
1666 // non-hierarchical. They cannot be compared.
1667 return URI.NOT_RELATED;
1668 }
1669 else
1670 {
1671 // They are both non-hierarchical
1672 if (thisURI.scheme != thatURI.scheme)
1673 return URI.NOT_RELATED;
1674
1675 if (thisURI.nonHierarchical != thatURI.nonHierarchical)
1676 return URI.NOT_RELATED;
1677
1678 // The two non-hierarcical URI's are equal.
1679 return URI.EQUAL;
1680 }
1681 }
1682
1683 // Ok, this URI and the one we are being compared to are both
1684 // absolute hierarchical URI's.
1685
1686 if (thisURI.scheme != thatURI.scheme)
1687 return URI.NOT_RELATED;
1688
1689 if (thisURI.authority != thatURI.authority)
1690 return URI.NOT_RELATED;
1691
1692 var thisPort:String = thisURI.port;
1693 var thatPort:String = thatURI.port;
1694
1695 // Different ports are considered completely different servers.
1696 if (thisPort == "")
1697 thisPort = thisURI.getDefaultPort();
1698 if (thatPort == "")
1699 thatPort = thatURI.getDefaultPort();
1700
1701 // Check to see if the port is the default port.
1702 if (thisPort != thatPort)
1703 return URI.NOT_RELATED;
1704
1705 if (compareStr(thisURI.path, thatURI.path, caseSensitive))
1706 return URI.EQUAL;
1707
1708 // Special case check. If we are here, the scheme, authority,
1709 // and port match, and it is not a relative path, but the
1710 // paths did not match. There is a special case where we
1711 // could have:
1712 // http://something.com/
1713 // http://something.com
1714 // Technically, these are equal. So lets, check for this case.
1715 var thisPath:String = thisURI.path;
1716 var thatPath:String = thatURI.path;
1717
1718 if ( (thisPath == "/" || thatPath == "/") &&
1719 (thisPath == "" || thatPath == "") )
1720 {
1721 // We hit the special case. These two are equal.
1722 return URI.EQUAL;
1723 }
1724
1725 // Ok, the paths do not match, but one path may be a parent/child
1726 // of the other. For example, we may have:
1727 // http://something.com/path/to/homepage/
1728 // http://something.com/path/to/homepage/images/logo.gif
1729 // In this case, the first is a parent of the second (or the second
1730 // is a child of the first, depending on which you compare to the
1731 // other). To make this comparison, we must split the path into
1732 // its component parts (split the string on the '/' path delimiter).
1733 // We then compare the
1734 var thisParts:Array, thatParts:Array;
1735 var thisPart:String, thatPart:String;
1736 var i:int;
1737
1738 thisParts = thisPath.split("/");
1739 thatParts = thatPath.split("/");
1740
1741 if (thisParts.length > thatParts.length)
1742 {
1743 thatPart = thatParts[thatParts.length - 1];
1744 if (thatPart.length > 0)
1745 {
1746 // if the last part is not empty, the passed URI is
1747 // not a directory. There is no way the passed URI
1748 // can be a parent.
1749 return URI.NOT_RELATED;
1750 }
1751 else
1752 {
1753 // Remove the empty trailing part
1754 thatParts.pop();
1755 }
1756
1757 // This may be a child of the one passed in
1758 for (i = 0; i < thatParts.length; i++)
1759 {
1760 thisPart = thisParts[i];
1761 thatPart = thatParts[i];
1762
1763 if (compareStr(thisPart, thatPart, caseSensitive) == false)
1764 return URI.NOT_RELATED;
1765 }
1766
1767 return URI.CHILD;
1768 }
1769 else if (thisParts.length < thatParts.length)
1770 {
1771 thisPart = thisParts[thisParts.length - 1];
1772 if (thisPart.length > 0)
1773 {
1774 // if the last part is not empty, this URI is not a
1775 // directory. There is no way this object can be
1776 // a parent.
1777 return URI.NOT_RELATED;
1778 }
1779 else
1780 {
1781 // Remove the empty trailing part
1782 thisParts.pop();
1783 }
1784
1785 // This may be the parent of the one passed in
1786 for (i = 0; i < thisParts.length; i++)
1787 {
1788 thisPart = thisParts[i];
1789 thatPart = thatParts[i];
1790
1791 if (compareStr(thisPart, thatPart, caseSensitive) == false)
1792 return URI.NOT_RELATED;
1793 }
1794
1795 return URI.PARENT;
1796 }
1797 else
1798 {
1799 // Both URI's have the same number of path components, but
1800 // it failed the equivelence check above. This means that
1801 // the two URI's are not related.
1802 return URI.NOT_RELATED;
1803 }
1804
1805 // If we got here, the scheme and authority are the same,
1806 // but the paths pointed to two different locations that
1807 // were in different parts of the file system tree
1808 return URI.NOT_RELATED;
1809 }
1810
1811 /**
1812 * Given another URI, return the common parent between this one
1813 * and the provided URI.
1814 *
1815 * @param uri the other URI from which to find a common parent
1816 * @para caseSensitive true if this operation should be done
1817 * with case sensitive comparisons.
1818 *
1819 * @return the parent URI if successful, null otherwise.
1820 */
1821 public function getCommonParent(uri:URI, caseSensitive:Boolean = true) : URI
1822 {
1823 var thisURI:URI = URI.resolve(this);
1824 var thatURI:URI = URI.resolve(uri);
1825
1826 if(!thisURI.isAbsolute() || !thatURI.isAbsolute() ||
1827 thisURI.isHierarchical() == false ||
1828 thatURI.isHierarchical() == false)
1829 {
1830 // Both URI's must be absolute hierarchical for this to
1831 // make sense.
1832 return null;
1833 }
1834
1835 var relation:int = thisURI.getRelation(thatURI);
1836 if (relation == URI.NOT_RELATED)
1837 {
1838 // The given URI is not related to this one. No
1839 // common parent.
1840 return null;
1841 }
1842
1843 thisURI.chdir(".");
1844 thatURI.chdir(".");
1845
1846 var strBefore:String, strAfter:String;
1847 do
1848 {
1849 relation = thisURI.getRelation(thatURI, caseSensitive);
1850 if(relation == URI.EQUAL || relation == URI.PARENT)
1851 break;
1852
1853 // If strBefore and strAfter end up being the same,
1854 // we know we are at the root of the path because
1855 // chdir("..") is doing nothing.
1856 strBefore = thisURI.toString();
1857 thisURI.chdir("..");
1858 strAfter = thisURI.toString();
1859 }
1860 while(strBefore != strAfter);
1861
1862 return thisURI;
1863 }
1864
1865
1866 /**
1867 * This function is used to move around in a URI in a way similar
1868 * to the 'cd' or 'chdir' commands on Unix. These operations are
1869 * completely string based, using the context of the URI to
1870 * determine the position within the path. The heuristics used
1871 * to determine the action are based off Appendix C in RFC 2396.
1872 *
1873 * <p>URI paths that end in '/' are considered paths that point to
1874 * directories, while paths that do not end in '/' are files. For
1875 * example, if you execute chdir("d") on the following URI's:<br/>
1876 * 1. http://something.com/a/b/c/ (directory)<br/>
1877 * 2. http://something.com/a/b/c (not directory)<br/>
1878 * you will get:<br/>
1879 * 1. http://something.com/a/b/c/d<br/>
1880 * 2. http://something.com/a/b/d<br/></p>
1881 *
1882 * <p>See RFC 2396, Appendix C for more info.</p>
1883 *
1884 * @param reference the URI or path to "cd" to.
1885 * @param escape true if the passed reference string should be URI
1886 * escaped before using it.
1887 *
1888 * @return true if the chdir was successful, false otherwise.
1889 */
1890 public function chdir(reference:String, escape:Boolean = false) : Boolean
1891 {
1892 var uriReference:URI;
1893 var ref:String = reference;
1894
1895 if (escape)
1896 ref = URI.escapeChars(reference);
1897
1898 if (ref == "")
1899 {
1900 // NOOP
1901 return true;
1902 }
1903 else if (ref.substr(0, 2) == "//")
1904 {
1905 // Special case. This is an absolute URI but without the scheme.
1906 // Take the scheme from this URI and tack it on. This is
1907 // intended to make working with chdir() a little more
1908 // tolerant.
1909 var final:String = this.scheme + ":" + ref;
1910
1911 return constructURI(final);
1912 }
1913 else if (ref.charAt(0) == "?")
1914 {
1915 // A relative URI that is just a query part is essentially
1916 // a "./?query". We tack on the "./" here to make the rest
1917 // of our logic work.
1918 ref = "./" + ref;
1919 }
1920
1921 // Parse the reference passed in as a URI. This way we
1922 // get any query and fragments parsed out as well.
1923 uriReference = new URI(ref);
1924
1925 if (uriReference.isAbsolute() ||
1926 uriReference.isHierarchical() == false)
1927 {
1928 // If the URI given is a full URI, it replaces this one.
1929 copyURI(uriReference);
1930 return true;
1931 }
1932
1933
1934 var thisPath:String, thatPath:String;
1935 var thisParts:Array, thatParts:Array;
1936 var thisIsDir:Boolean = false, thatIsDir:Boolean = false;
1937 var thisIsAbs:Boolean = false, thatIsAbs:Boolean = false;
1938 var lastIsDotOperation:Boolean = false;
1939 var curDir:String;
1940 var i:int;
1941
1942 thisPath = this.path;
1943 thatPath = uriReference.path;
1944
1945 if (thisPath.length > 0)
1946 thisParts = thisPath.split("/");
1947 else
1948 thisParts = new Array();
1949
1950 if (thatPath.length > 0)
1951 thatParts = thatPath.split("/");
1952 else
1953 thatParts = new Array();
1954
1955 if (thisParts.length > 0 && thisParts[0] == "")
1956 {
1957 thisIsAbs = true;
1958 thisParts.shift(); // pop the first one off the array
1959 }
1960 if (thisParts.length > 0 && thisParts[thisParts.length - 1] == "")
1961 {
1962 thisIsDir = true;
1963 thisParts.pop(); // pop the last one off the array
1964 }
1965
1966 if (thatParts.length > 0 && thatParts[0] == "")
1967 {
1968 thatIsAbs = true;
1969 thatParts.shift(); // pop the first one off the array
1970 }
1971 if (thatParts.length > 0 && thatParts[thatParts.length - 1] == "")
1972 {
1973 thatIsDir = true;
1974 thatParts.pop(); // pop the last one off the array
1975 }
1976
1977 if (thatIsAbs)
1978 {
1979 // The reference is an absolute path (starts with a slash).
1980 // It replaces this path wholesale.
1981 this.path = uriReference.path;
1982
1983 // And it inherits the query and fragment
1984 this.queryRaw = uriReference.queryRaw;
1985 this.fragment = uriReference.fragment;
1986
1987 return true;
1988 }
1989 else if (thatParts.length == 0 && uriReference.query == "")
1990 {
1991 // The reference must have only been a fragment. Fragments just
1992 // get appended to whatever the current path is. We don't want
1993 // to overwrite any query that may already exist, so this case
1994 // only takes on the new fragment.
1995 this.fragment = uriReference.fragment;
1996 return true;
1997 }
1998 else if (thisIsDir == false && thisParts.length > 0)
1999 {
2000 // This path ends in a file. It goes away no matter what.
2001 thisParts.pop();
2002 }
2003
2004 // By default, this assumes the query and fragment of the reference
2005 this.queryRaw = uriReference.queryRaw;
2006 this.fragment = uriReference.fragment;
2007
2008 // Append the parts of the path from the passed in reference
2009 // to this object's path.
2010 thisParts = thisParts.concat(thatParts);
2011
2012 for(i = 0; i < thisParts.length; i++)
2013 {
2014 curDir = thisParts[i];
2015 lastIsDotOperation = false;
2016
2017 if (curDir == ".")
2018 {
2019 thisParts.splice(i, 1);
2020 i = i - 1; // account for removing this item
2021 lastIsDotOperation = true;
2022 }
2023 else if (curDir == "..")
2024 {
2025 if (i >= 1)
2026 {
2027 if (thisParts[i - 1] == "..")
2028 {
2029 // If the previous is a "..", we must have skipped
2030 // it due to this URI being relative. We can't
2031 // collapse leading ".."s in a relative URI, so
2032 // do nothing.
2033 }
2034 else
2035 {
2036 thisParts.splice(i - 1, 2);
2037 i = i - 2; // move back to account for the 2 we removed
2038 }
2039 }
2040 else
2041 {
2042 // This is the first thing in the path.
2043
2044 if (isRelative())
2045 {
2046 // We can't collapse leading ".."s in a relative
2047 // path. Do noting.
2048 }
2049 else
2050 {
2051 // This is an abnormal case. We have dot-dotted up
2052 // past the base of our "file system". This is a
2053 // case where we had a /path/like/this.htm and were
2054 // given a path to chdir to like this:
2055 // ../../../../../../mydir
2056 // Obviously, it has too many ".." and will take us
2057 // up beyond the top of the URI. However, according
2058 // RFC 2396 Appendix C.2, we should try to handle
2059 // these abnormal cases appropriately. In this case,
2060 // we will do what UNIX command lines do if you are
2061 // at the root (/) of the filesystem and execute:
2062 // # cd ../../../../../bin
2063 // Which will put you in /bin. Essentially, the extra
2064 // ".."'s will just get eaten.
2065
2066 thisParts.splice(i, 1);
2067 i = i - 1; // account for the ".." we just removed
2068 }
2069 }
2070
2071 lastIsDotOperation = true;
2072 }
2073 }
2074
2075 var finalPath:String = "";
2076
2077 // If the last thing in the path was a "." or "..", then this thing is a
2078 // directory. If the last thing isn't a dot-op, then we don't want to
2079 // blow away any information about the directory (hence the "|=" binary
2080 // assignment).
2081 thatIsDir = thatIsDir || lastIsDotOperation;
2082
2083 // Reconstruct the path with the abs/dir info we have
2084 finalPath = joinPath(thisParts, thisIsAbs, thatIsDir);
2085
2086 // Set the path (automatically escaping it)
2087 this.path = finalPath;
2088
2089 return true;
2090 }
2091
2092 /**
2093 * @private
2094 * Join an array of path parts back into a URI style path string.
2095 * This is used by the various path logic functions to recombine
2096 * a path. This is different than the standard Array.join()
2097 * function because we need to take into account the starting and
2098 * ending path delimiters if this is an absolute path or a
2099 * directory.
2100 *
2101 * @param parts the Array that contains strings of each path part.
2102 * @param isAbs true if the given path is absolute
2103 * @param isDir true if the given path is a directory
2104 *
2105 * @return the combined path string.
2106 */
2107 protected function joinPath(parts:Array, isAbs:Boolean, isDir:Boolean) : String
2108 {
2109 var pathStr:String = "";
2110 var i:int;
2111
2112 for (i = 0; i < parts.length; i++)
2113 {
2114 if (pathStr.length > 0)
2115 pathStr += "/";
2116
2117 pathStr += parts[i];
2118 }
2119
2120 // If this path is a directory, tack on the directory delimiter,
2121 // but only if the path contains something. Adding this to an
2122 // empty path would make it "/", which is an absolute path that
2123 // starts at the root.
2124 if (isDir && pathStr.length > 0)
2125 pathStr += "/";
2126
2127 if (isAbs)
2128 pathStr = "/" + pathStr;
2129
2130 return pathStr;
2131 }
2132
2133 /**
2134 * Given an absolute URI, make this relative URI absolute using
2135 * the given URI as a base. This URI instance must be relative
2136 * and the base_uri must be absolute.
2137 *
2138 * @param base_uri URI to use as the base from which to make
2139 * this relative URI into an absolute URI.
2140 *
2141 * @return true if successful, false otherwise.
2142 */
2143 public function makeAbsoluteURI(base_uri:URI) : Boolean
2144 {
2145 if (isAbsolute() || base_uri.isRelative())
2146 {
2147 // This URI needs to be relative, and the base needs to be
2148 // absolute otherwise we won't know what to do!
2149 return false;
2150 }
2151
2152 // Make a copy of the base URI. We don't want to modify
2153 // the passed URI.
2154 var base:URI = new URI();
2155 base.copyURI(base_uri);
2156
2157 // ChDir on the base URI. This will preserve any query
2158 // and fragment we have.
2159 if (base.chdir(toString()) == false)
2160 return false;
2161
2162 // It worked, so copy the base into this one
2163 copyURI(base);
2164
2165 return true;
2166 }
2167
2168
2169 /**
2170 * Given a URI to use as a base from which this object should be
2171 * relative to, convert this object into a relative URI. For example,
2172 * if you have:
2173 *
2174 * <listing>
2175 * var uri1:URI = new URI("http://something.com/path/to/some/file.html");
2176 * var uri2:URI = new URI("http://something.com/path/to/another/file.html");
2177 *
2178 * uri1.MakeRelativePath(uri2);</listing>
2179 *
2180 * <p>uri1 will have a final value of "../some/file.html"</p>
2181 *
2182 * <p>Note! This function is brute force. If you have two URI's
2183 * that are completely unrelated, this will still attempt to make
2184 * the relative URI. In that case, you will most likely get a
2185 * relative path that looks something like:</p>
2186 *
2187 * <p>../../../../../../some/path/to/my/file.html</p>
2188 *
2189 * @param base_uri the URI from which to make this URI relative
2190 *
2191 * @return true if successful, false if the base_uri and this URI
2192 * are not related, of if error.
2193 */
2194 public function makeRelativeURI(base_uri:URI, caseSensitive:Boolean = true) : Boolean
2195 {
2196 var base:URI = new URI();
2197 base.copyURI(base_uri);
2198
2199 var thisParts:Array, thatParts:Array;
2200 var finalParts:Array = new Array();
2201 var thisPart:String, thatPart:String, finalPath:String;
2202 var pathStr:String = this.path;
2203 var queryStr:String = this.queryRaw;
2204 var fragmentStr:String = this.fragment;
2205 var i:int;
2206 var diff:Boolean = false;
2207 var isDir:Boolean = false;
2208
2209 if (isRelative())
2210 {
2211 // We're already relative.
2212 return true;
2213 }
2214
2215 if (base.isRelative())
2216 {
2217 // The base is relative. A relative base doesn't make sense.
2218 return false;
2219 }
2220
2221
2222 if ( (isOfType(base_uri.scheme) == false) ||
2223 (this.authority != base_uri.authority) )
2224 {
2225 // The schemes and/or authorities are different. We can't
2226 // make a relative path to something that is completely
2227 // unrelated.
2228 return false;
2229 }
2230
2231 // Record the state of this URI
2232 isDir = isDirectory();
2233
2234 // We are based of the directory of the given URI. We need to
2235 // make sure the URI is pointing to a directory. Changing
2236 // directory to "." will remove any file name if the base is
2237 // not a directory.
2238 base.chdir(".");
2239
2240 thisParts = pathStr.split("/");
2241 thatParts = base.path.split("/");
2242
2243 if (thisParts.length > 0 && thisParts[0] == "")
2244 thisParts.shift();
2245
2246 if (thisParts.length > 0 && thisParts[thisParts.length - 1] == "")
2247 {
2248 isDir = true;
2249 thisParts.pop();
2250 }
2251
2252 if (thatParts.length > 0 && thatParts[0] == "")
2253 thatParts.shift();
2254 if (thatParts.length > 0 && thatParts[thatParts.length - 1] == "")
2255 thatParts.pop();
2256
2257
2258 // Now that we have the paths split into an array of directories,
2259 // we can compare the two paths. We start from the left of side
2260 // of the path and start comparing. When we either run out of
2261 // directories (one path is longer than the other), or we find
2262 // a directory that is different, we stop. The remaining parts
2263 // of each path is then used to determine the relative path. For
2264 // example, lets say we have:
2265 // path we want to make relative: /a/b/c/d/e.txt
2266 // path to use as base for relative: /a/b/f/
2267 //
2268 // This loop will start at the left, and remove directories
2269 // until we get a mismatch or run off the end of one of them.
2270 // In this example, the result will be:
2271 // c/d/e.txt
2272 // f
2273 //
2274 // For every part left over in the base path, we prepend a ".."
2275 // to the relative to get the final path:
2276 // ../c/d/e.txt
2277 while(thatParts.length > 0)
2278 {
2279 if (thisParts.length == 0)
2280 {
2281 // we matched all there is to match, we are done.
2282 // This is the case where "this" object is a parent
2283 // path of the given URI. eg:
2284 // this.path = /a/b/ (thisParts)
2285 // base.path = /a/b/c/d/e/ (thatParts)
2286 break;
2287 }
2288
2289 thisPart = thisParts[0];
2290 thatPart = thatParts[0];
2291
2292 if (compareStr(thisPart, thatPart, caseSensitive))
2293 {
2294 thisParts.shift();
2295 thatParts.shift();
2296 }
2297 else
2298 break;
2299 }
2300
2301 // If there are any path info left from the base URI, that means
2302 // **this** object is above the given URI in the file tree. For
2303 // each part left over in the given URI, we need to move up one
2304 // directory to get where we are.
2305 var dotdot:String = "..";
2306 for (i = 0; i < thatParts.length; i++)
2307 {
2308 finalParts.push(dotdot);
2309 }
2310
2311 // Append the parts of this URI to any dot-dot's we have
2312 finalParts = finalParts.concat(thisParts);
2313
2314 // Join the parts back into a path
2315 finalPath = joinPath(finalParts, false /* not absolute */, isDir);
2316
2317 if (finalPath.length == 0)
2318 {
2319 // The two URI's are exactly the same. The proper relative
2320 // path is:
2321 finalPath = "./";
2322 }
2323
2324 // Set the parts of the URI, preserving the original query and
2325 // fragment parts.
2326 setParts("", "", "", finalPath, queryStr, fragmentStr);
2327
2328 return true;
2329 }
2330
2331 /**
2332 * Given a string, convert it to a URI. The string could be a
2333 * full URI that is improperly escaped, a malformed URI (e.g.
2334 * missing a protocol like "www.something.com"), a relative URI,
2335 * or any variation there of.
2336 *
2337 * <p>The intention of this function is to take anything that a
2338 * user might manually enter as a URI/URL and try to determine what
2339 * they mean. This function differs from the URI constructor in
2340 * that it makes some assumptions to make it easy to import user
2341 * entered URI data.</p>
2342 *
2343 * <p>This function is intended to be a helper function.
2344 * It is not all-knowning and will probably make mistakes
2345 * when attempting to parse a string of unknown origin. If
2346 * your applicaiton is receiving input from the user, your
2347 * application should already have a good idea what the user
2348 * should be entering, and your application should be
2349 * pre-processing the user's input to make sure it is well formed
2350 * before passing it to this function.</p>
2351 *
2352 * <p>It is assumed that the string given to this function is
2353 * something the user may have manually entered. Given this,
2354 * the URI string is probably unescaped or improperly escaped.
2355 * This function will attempt to properly escape the URI by
2356 * using forceEscape(). The result is that a toString() call
2357 * on a URI that was created from unknownToURI() may not match
2358 * the input string due to the difference in escaping.</p>
2359 *
2360 * @param unknown a potental URI string that should be parsed
2361 * and loaded into this object.
2362 * @param defaultScheme if it is determined that the passed string
2363 * looks like a URI, but it is missing the scheme part, this
2364 * string will be used as the missing scheme.
2365 *
2366 * @return true if the given string was successfully parsed into
2367 * a valid URI object, false otherwise.
2368 */
2369 public function unknownToURI(unknown:String, defaultScheme:String = "http") : Boolean
2370 {
2371 var temp:String;
2372
2373 if (unknown.length == 0)
2374 {
2375 this.initialize();
2376 return false;
2377 }
2378
2379 // Some users love the backslash key. Fix it.
2380 unknown = unknown.replace(/\\/g, "/");
2381
2382 // Check for any obviously missing scheme.
2383 if (unknown.length >= 2)
2384 {
2385 temp = unknown.substr(0, 2);
2386 if (temp == "//")
2387 unknown = defaultScheme + ":" + unknown;
2388 }
2389
2390 if (unknown.length >= 3)
2391 {
2392 temp = unknown.substr(0, 3);
2393 if (temp == "://")
2394 unknown = defaultScheme + unknown;
2395 }
2396
2397 // Try parsing it as a normal URI
2398 var uri:URI = new URI(unknown);
2399
2400 if (uri.isHierarchical() == false)
2401 {
2402 if (uri.scheme == UNKNOWN_SCHEME)
2403 {
2404 this.initialize();
2405 return false;
2406 }
2407
2408 // It's a non-hierarchical URI
2409 copyURI(uri);
2410 forceEscape();
2411 return true;
2412 }
2413 else if ((uri.scheme != UNKNOWN_SCHEME) &&
2414 (uri.scheme.length > 0))
2415 {
2416 if ( (uri.authority.length > 0) ||
2417 (uri.scheme == "file") )
2418 {
2419 // file://... URI
2420 copyURI(uri);
2421 forceEscape(); // ensure proper escaping
2422 return true;
2423 }
2424 else if (uri.authority.length == 0 && uri.path.length == 0)
2425 {
2426 // It's is an incomplete URI (eg "http://")
2427
2428 setParts(uri.scheme, "", "", "", "", "");
2429 return false;
2430 }
2431 }
2432 else
2433 {
2434 // Possible relative URI. We can only detect relative URI's
2435 // that start with "." or "..". If it starts with something
2436 // else, the parsing is ambiguous.
2437 var path:String = uri.path;
2438
2439 if (path == ".." || path == "." ||
2440 (path.length >= 3 && path.substr(0, 3) == "../") ||
2441 (path.length >= 2 && path.substr(0, 2) == "./") )
2442 {
2443 // This is a relative URI.
2444 copyURI(uri);
2445 forceEscape();
2446 return true;
2447 }
2448 }
2449
2450 // Ok, it looks like we are just a normal URI missing the scheme. Tack
2451 // on the scheme.
2452 uri = new URI(defaultScheme + "://" + unknown);
2453
2454 // Check to see if we are good now
2455 if (uri.scheme.length > 0 && uri.authority.length > 0)
2456 {
2457 // It was just missing the scheme.
2458 copyURI(uri);
2459 forceEscape(); // Make sure we are properly encoded.
2460 return true;
2461 }
2462
2463 // don't know what this is
2464 this.initialize();
2465 return false;
2466 }
2467
2468 } // end URI class
2469} // end package
Note: See TracBrowser for help on using the repository browser.