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