//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.utils { import mx.messaging.config.LoaderConfig; /** * The URLUtil class is a static class with methods for working with * full and relative URLs within Flex. * * @see mx.managers.BrowserManager * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class URLUtil { //-------------------------------------------------------------------------- // // Private Static Constants // //-------------------------------------------------------------------------- /** * @private */ private static const SQUARE_BRACKET_LEFT:String = "]"; private static const SQUARE_BRACKET_RIGHT:String = "["; private static const SQUARE_BRACKET_LEFT_ENCODED:String = encodeURIComponent(SQUARE_BRACKET_LEFT); private static const SQUARE_BRACKET_RIGHT_ENCODED:String = encodeURIComponent(SQUARE_BRACKET_RIGHT); //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * @private */ public function URLUtil() { super(); } //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * Returns the domain and port information from the specified URL. * * @param url The URL to analyze. * @return The server name and port of the specified URL. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getServerNameWithPort(url:String):String { // Find first slash; second is +1, start 1 after. var start:int = url.indexOf("/") + 2; var length:int = url.indexOf("/", start); return length == -1 ? url.substring(start) : url.substring(start, length); } /** * Returns the server name from the specified URL. * * @param url The URL to analyze. * @return The server name of the specified URL. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getServerName(url:String):String { var sp:String = getServerNameWithPort(url); // If IPv6 is in use, start looking after the square bracket. var delim:int = URLUtil.indexOfLeftSquareBracket(sp); delim = (delim > -1)? sp.indexOf(":", delim) : sp.indexOf(":"); if (delim > 0) sp = sp.substring(0, delim); return sp; } /** * Returns the port number from the specified URL. * * @param url The URL to analyze. * @return The port number of the specified URL. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getPort(url:String):uint { var sp:String = getServerNameWithPort(url); // If IPv6 is in use, start looking after the square bracket. var delim:int = URLUtil.indexOfLeftSquareBracket(sp); delim = (delim > -1)? sp.indexOf(":", delim) : sp.indexOf(":"); var port:uint = 0; if (delim > 0) { var p:Number = Number(sp.substring(delim + 1)); if (!isNaN(p)) port = int(p); } return port; } /** * Converts a potentially relative URL to a fully-qualified URL. * If the URL is not relative, it is returned as is. * If the URL starts with a slash, the host and port * from the root URL are prepended. * Otherwise, the host, port, and path are prepended. * * @param rootURL URL used to resolve the URL specified by the url parameter, if url is relative. * @param url URL to convert. * * @return Fully-qualified URL. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getFullURL(rootURL:String, url:String):String { if (url != null && !URLUtil.isHttpURL(url)) { if (url.indexOf("./") == 0) { url = url.substring(2); } if (URLUtil.isHttpURL(rootURL)) { var slashPos:Number; if (url.charAt(0) == '/') { // non-relative path, "/dev/foo.bar". slashPos = rootURL.indexOf("/", 8); if (slashPos == -1) slashPos = rootURL.length; } else { // relative path, "dev/foo.bar". slashPos = rootURL.lastIndexOf("/") + 1; if (slashPos <= 8) { rootURL += "/"; slashPos = rootURL.length; } } if (slashPos > 0) url = rootURL.substring(0, slashPos) + url; } } return url; } // Note: The following code was copied from Flash Remoting's // NetServices client components. // It is reproduced here to keep the services APIs // independent of the deprecated NetServices code. // Note that it capitalizes any use of URL in method or class names. /** * Determines if the URL uses the HTTP, HTTPS, or RTMP protocol. * * @param url The URL to analyze. * * @return true if the URL starts with "http://", "https://", or "rtmp://". * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function isHttpURL(url:String):Boolean { return url != null && (url.indexOf("http://") == 0 || url.indexOf("https://") == 0); } /** * Determines if the URL uses the secure HTTPS protocol. * * @param url The URL to analyze. * * @return true if the URL starts with "https://". * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function isHttpsURL(url:String):Boolean { return url != null && url.indexOf("https://") == 0; } /** * Returns the protocol section of the specified URL. * The following examples show what is returned based on different URLs: * *
         *  getProtocol("https://localhost:2700/") returns "https"
         *  getProtocol("rtmp://www.myCompany.com/myMainDirectory/groupChatApp/HelpDesk") returns "rtmp"
         *  getProtocol("rtmpt:/sharedWhiteboardApp/June2002") returns "rtmpt"
         *  getProtocol("rtmp::1234/chatApp/room_name") returns "rtmp"
         *  
* * @param url String containing the URL to parse. * * @return The protocol or an empty String if no protocol is specified. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getProtocol(url:String):String { var slash:int = url.indexOf("/"); var indx:int = url.indexOf(":/"); if (indx > -1 && indx < slash) { return url.substring(0, indx); } else { indx = url.indexOf("::"); if (indx > -1 && indx < slash) return url.substring(0, indx); } return ""; } /** * Replaces the protocol of the * specified URI with the given protocol. * * @param uri String containing the URI in which the protocol * needs to be replaced. * * @param newProtocol String containing the new protocol to use. * * @return The URI with the protocol replaced, * or an empty String if the URI does not contain a protocol. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function replaceProtocol(uri:String, newProtocol:String):String { return uri.replace(getProtocol(uri), newProtocol); } /** * Returns a new String with the port replaced with the specified port. * If there is no port in the specified URI, the port is inserted. * This method expects that a protocol has been specified within the URI. * * @param uri String containing the URI in which the port is replaced. * @param newPort uint containing the new port to subsitute. * * @return The URI with the new port. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function replacePort(uri:String, newPort:uint):String { var result:String = ""; // First, determine if IPv6 is in use by looking for square bracket var indx:int = uri.indexOf("]"); // If IPv6 is not in use, reset indx to the first colon if (indx == -1) indx = uri.indexOf(":"); var portStart:int = uri.indexOf(":", indx+1); var portEnd:int; // If we have a port if (portStart > -1) { portStart++; // move past the ":" portEnd = uri.indexOf("/", portStart); //@TODO: need to throw an invalid uri here if no slash was found result = uri.substring(0, portStart) + newPort.toString() + uri.substring(portEnd, uri.length); } else { // Insert the specified port portEnd = uri.indexOf("/", indx); if (portEnd > -1) { // Look to see if we have protocol://host:port/ // if not then we must have protocol:/relative-path if (uri.charAt(portEnd+1) == "/") portEnd = uri.indexOf("/", portEnd + 2); if (portEnd > 0) { result = uri.substring(0, portEnd) + ":"+ newPort.toString() + uri.substring(portEnd, uri.length); } else { result = uri + ":" + newPort.toString(); } } else { result = uri + ":"+ newPort.toString(); } } return result; } /** * Returns a new String with the port and server tokens replaced with * the port and server from the currently running application. * * @param url String containing the SERVER_NAME_TOKEN and/or SERVER_NAME_PORT * which should be replaced by the port and server from the application. * * @return The URI with the port and server replaced. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function replaceTokens(url:String):String { var loaderURL:String = LoaderConfig.url == null ? "" : LoaderConfig.url; // if the LoaderConfig.url hasn't been configured yet we need to // throw, informing the user that this value must be setup first // TODO: add this back in after each new player build //if (LoaderConfig.url == null) // trace("WARNING: LoaderConfig.url hasn't been initialized."); // Replace {server.name} if (url.indexOf(SERVER_NAME_TOKEN) > 0) { loaderURL = URLUtil.replaceEncodedSquareBrackets(loaderURL); var loaderProtocol:String = URLUtil.getProtocol(loaderURL); var loaderServerName:String = "localhost"; if (loaderProtocol.toLowerCase() != "file") loaderServerName = URLUtil.getServerName(loaderURL); url = url.replace(SERVER_NAME_REGEX, loaderServerName); } // Replace {server.port} either with the loader's port, or // remove it and the proceeding token if a port is not // specified for the SWF Loader. var portToken:int = url.indexOf(SERVER_PORT_TOKEN); if (portToken > 0) { var loaderPort:uint = URLUtil.getPort(loaderURL); if (loaderPort > 0) { url = url.replace(SERVER_PORT_REGEX, loaderPort); } else { if (url.charAt(portToken - 1) == ":") url = url.substring(0, portToken - 1) + url.substring(portToken); url = url.replace(SERVER_PORT_REGEX, ""); } } return url; } /** * Tests whether two URI Strings are equivalent, ignoring case and * differences in trailing slashes. * * @param uri1 The first URI to compare. * @param uri2 The second URI to compare. * * @return true if the URIs are equal. Otherwise, false. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function urisEqual(uri1:String, uri2:String):Boolean { if (uri1 != null && uri2 != null) { uri1 = StringUtil.trim(uri1).toLowerCase(); uri2 = StringUtil.trim(uri2).toLowerCase(); if (uri1.charAt(uri1.length - 1) != "/") uri1 = uri1 + "/"; if (uri2.charAt(uri2.length - 1) != "/") uri2 = uri2 + "/"; } return uri1 == uri2; } /** * Given a url, determines whether the url contains the server.name and * server.port tokens. * * @param url A url string. * * @return true if the url contains server.name and server.port tokens. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public static function hasTokens(url:String):Boolean { if (url == null || url == "") return false; if (url.indexOf(SERVER_NAME_TOKEN) > 0) return true; if (url.indexOf(SERVER_PORT_TOKEN) > 0) return true; return false; } /** * If the LoaderConfig.url property is not available, the replaceTokens() method will not * replace the server name and port properties properly. * * @return true if the LoaderConfig.url property is not available. Otherwise, false. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function hasUnresolvableTokens():Boolean { return LoaderConfig.url != null; } /** * The pattern in the String that is passed to the replaceTokens() method that * is replaced by the application's server name. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static const SERVER_NAME_TOKEN:String = "{server.name}"; /** * The pattern in the String that is passed to the replaceTokens() method that * is replaced by the application's port. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static const SERVER_PORT_TOKEN:String = "{server.port}"; /** * Enumerates an object's dynamic properties (by using a for..in loop) * and returns a String. You typically use this method to convert an ActionScript object to a String that you then append to the end of a URL. * By default, invalid URL characters are URL-encoded (converted to the %XX format). * *

For example: *

         *  var o:Object = { name: "Alex", age: 21 };
         *  var s:String = URLUtil.objectToString(o,";",true);
         *  trace(s);
         *  
* Prints "name=Alex;age=21" to the trace log. *

* * @param object The object to convert to a String. * @param separator The character that separates each of the object's property:value pair in the String. * @param encodeURL Whether or not to URL-encode the String. * * @return The object that was passed to the method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function objectToString(object:Object, separator:String=';', encodeURL:Boolean = true):String { var s:String = internalObjectToString(object, separator, null, encodeURL); return s; } private static function indexOfLeftSquareBracket(value:String):int { var delim:int = value.indexOf(SQUARE_BRACKET_LEFT); if (delim == -1) delim = value.indexOf(SQUARE_BRACKET_LEFT_ENCODED); return delim; } private static function internalObjectToString(object:Object, separator:String, prefix:String, encodeURL:Boolean):String { var s:String = ""; var first:Boolean = true; for (var p:String in object) { if (first) { first = false; } else s += separator; var value:Object = object[p]; var name:String = prefix ? prefix + "." + p : p; if (encodeURL) name = encodeURIComponent(name); if (value is String) { s += name + '=' + (encodeURL ? encodeURIComponent(value as String) : value); } else if (value is Number) { value = value.toString(); if (encodeURL) value = encodeURIComponent(value as String); s += name + '=' + value; } else if (value is Boolean) { s += name + '=' + (value ? "true" : "false"); } else { if (value is Array) { s += internalArrayToString(value as Array, separator, name, encodeURL); } else // object { s += internalObjectToString(value, separator, name, encodeURL); } } } return s; } private static function replaceEncodedSquareBrackets(value:String):String { var rightIndex:int = value.indexOf(SQUARE_BRACKET_RIGHT_ENCODED); if (rightIndex > -1) { value = value.replace(SQUARE_BRACKET_RIGHT_ENCODED, SQUARE_BRACKET_RIGHT); var leftIndex:int = value.indexOf(SQUARE_BRACKET_LEFT_ENCODED); if (leftIndex > -1) value = value.replace(SQUARE_BRACKET_LEFT_ENCODED, SQUARE_BRACKET_LEFT); } return value; } private static function internalArrayToString(array:Array, separator:String, prefix:String, encodeURL:Boolean):String { var s:String = ""; var first:Boolean = true; var n:int = array.length; for (var i:int = 0; i < n; i++) { if (first) { first = false; } else s += separator; var value:Object = array[i]; var name:String = prefix + "." + i; if (encodeURL) name = encodeURIComponent(name); if (value is String) { s += name + '=' + (encodeURL ? encodeURIComponent(value as String) : value); } else if (value is Number) { value = value.toString(); if (encodeURL) value = encodeURIComponent(value as String); s += name + '=' + value; } else if (value is Boolean) { s += name + '=' + (value ? "true" : "false"); } else { if (value is Array) { s += internalArrayToString(value as Array, separator, name, encodeURL); } else // object { s += internalObjectToString(value, separator, name, encodeURL); } } } return s; } /** * Returns an object from a String. The String contains name=value pairs, which become dynamic properties * of the returned object. These property pairs are separated by the specified separator. * This method converts Numbers and Booleans, Arrays (defined by "[]"), * and sub-objects (defined by "{}"). By default, URL patterns of the format %XX are converted * to the appropriate String character. * *

For example: *

         *  var s:String = "name=Alex;age=21";
         *  var o:Object = URLUtil.stringToObject(s, ";", true);
         *  
* * Returns the object: { name: "Alex", age: 21 }. *

* * @param string The String to convert to an object. * @param separator The character that separates name=value pairs in the String. * @param decodeURL Whether or not to decode URL-encoded characters in the String. * * @return The object containing properties and values extracted from the String passed to this method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function stringToObject(string:String, separator:String = ";", decodeURL:Boolean = true):Object { var o:Object = {}; var arr:Array = string.split(separator); // if someone has a name or value that contains the separator // this will not work correctly, nor will it work well if there are // '=' or '.' in the name or value var n:int = arr.length; for (var i:int = 0; i < n; i++) { var pieces:Array = arr[i].split('='); var name:String = pieces[0]; if (decodeURL) name = decodeURIComponent(name); var value:Object = pieces[1]; if (decodeURL) value = decodeURIComponent(value as String); if (value == "true") value = true; else if (value == "false") value = false; else { var temp:Object = int(value); if (temp.toString() == value) value = temp; else { temp = Number(value) if (temp.toString() == value) value = temp; } } var obj:Object = o; pieces = name.split('.'); var m:int = pieces.length; for (var j:int = 0; j < m - 1; j++) { var prop:String = pieces[j]; if (obj[prop] == null && j < m - 1) { var subProp:String = pieces[j + 1]; var idx:Object = int(subProp); if (idx.toString() == subProp) obj[prop] = []; else obj[prop] = {}; } obj = obj[prop]; } obj[pieces[j]] = value; } return o; } // Reusable reg-exp for token replacement. The . means any char, so this means // we should handle server.name and server-name, etc... private static const SERVER_NAME_REGEX:RegExp = new RegExp("\\{server.name\\}", "g"); private static const SERVER_PORT_REGEX:RegExp = new RegExp("\\{server.port\\}", "g"); } }