////////////////////////////////////////////////////////////////////////////////
//
// 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");
}
}