objj-runtime
Version:
JavaScript (ECMAScript) and Objective-J runtime
729 lines (536 loc) • 17.4 kB
JavaScript
/*
* CFURL.js
* Objective-J
*
* Created by Francisco Tolmasky.
* Copyright 2010, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var CFURLsForCachedUIDs,
CFURLPartsForURLStrings,
CFURLCachingEnableCount = 0;
function enableCFURLCaching()
{
if (++CFURLCachingEnableCount !== 1)
return;
CFURLsForCachedUIDs = { };
CFURLPartsForURLStrings = { };
}
function disableCFURLCaching()
{
CFURLCachingEnableCount = MAX(CFURLCachingEnableCount - 1, 0);
if (CFURLCachingEnableCount !== 0)
return;
delete CFURLsForCachedUIDs;
delete CFURLPartsForURLStrings;
}
var URL_RE = new RegExp( /* url */
"^" +
"(?:" +
"([^:/?#]+):" + /* scheme */
")?" +
"(?:" +
"(//)" + /* authorityRoot */
"(" + /* authority */
"(?:" +
"(" + /* userInfo */
"([^:@]*)" + /* user */
":?" +
"([^:@]*)" + /* password */
")?" +
"@" +
")?" +
"([^:/?#]*)" + /* domain */
"(?::(\\d*))?" + /* port */
")" +
")?" +
"([^?#]*)" + /*path*/
"(?:\\?([^#]*))?" + /* queryString */
"(?:#(.*))?" /*fragment */
);
var URI_KEYS =
[
"url",
"scheme",
"authorityRoot",
"authority",
"userInfo",
"user",
"password",
"domain",
"portNumber",
"path",
"queryString",
"fragment"
];
function CFURLGetParts(/*CFURL*/ aURL)
{
if (aURL._parts)
return aURL._parts;
var URLString = aURL.string(),
isMHTMLURL = URLString.match(/^mhtml:/);
if (isMHTMLURL)
URLString = URLString.substr("mhtml:".length);
if (CFURLCachingEnableCount > 0 && hasOwnProperty.call(CFURLPartsForURLStrings, URLString))
{
aURL._parts = CFURLPartsForURLStrings[URLString];
return aURL._parts;
}
aURL._parts = { };
var parts = aURL._parts,
results = URL_RE.exec(URLString),
index = results.length;
while (index--)
parts[URI_KEYS[index]] = results[index] || NULL;
parts.portNumber = parseInt(parts.portNumber, 10);
if (isNaN(parts.portNumber))
parts.portNumber = -1;
parts.pathComponents = [];
if (parts.path)
{
var split = parts.path.split("/"),
pathComponents = parts.pathComponents,
count = split.length;
for (index = 0; index < count; ++index)
{
var component = split[index];
if (component)
pathComponents.push(component);
else if (index === 0)
pathComponents.push("/");
}
parts.pathComponents = pathComponents;
}
if (isMHTMLURL)
{
parts.url = "mhtml:" + parts.url;
parts.scheme = "mhtml:" + parts.scheme;
}
if (CFURLCachingEnableCount > 0)
CFURLPartsForURLStrings[URLString] = parts;
return parts;
}
#define PARTS(aURL) ((aURL)._parts || CFURLGetParts(aURL))
GLOBAL(CFURL) = function(/*CFURL|String*/ aURL, /*CFURL*/ aBaseURL)
{
aURL = aURL || "";
if (aURL instanceof CFURL)
{
if (!aBaseURL)
return new CFURL(aURL.absoluteString());
var existingBaseURL = aURL.baseURL();
if (existingBaseURL)
aBaseURL = new CFURL(existingBaseURL.absoluteURL(), aBaseURL);
aURL = aURL.string();
}
// Use the cache if it's enabled.
if (CFURLCachingEnableCount > 0)
{
var cacheUID = aURL + " " + (aBaseURL && aBaseURL.UID() || "");
if (hasOwnProperty.call(CFURLsForCachedUIDs, cacheUID))
return CFURLsForCachedUIDs[cacheUID];
CFURLsForCachedUIDs[cacheUID] = this;
}
if (aURL.match(/^data:/))
{
var parts = { },
index = URI_KEYS.length;
while (index--)
parts[URI_KEYS[index]] = "";
parts.url = aURL;
parts.scheme = "data";
parts.pathComponents = [];
this._parts = parts;
this._standardizedURL = this;
this._absoluteURL = this;
}
this._UID = objj_generateObjectUID();
this._string = aURL;
this._baseURL = aBaseURL;
}
DISPLAY_NAME(CFURL);
CFURL.prototype.UID = function()
{
return this._UID;
};
DISPLAY_NAME(CFURL.prototype.UID);
var URLMap = { };
CFURL.prototype.mappedURL = function()
{
return URLMap[this.absoluteString()] || this;
};
DISPLAY_NAME(CFURL.prototype.mappedURL);
CFURL.setMappedURLForURL = function(/*CFURL*/ fromURL, /*CFURL*/ toURL)
{
URLMap[fromURL.absoluteString()] = toURL;
};
DISPLAY_NAME(CFURL.setMappedURLForURL);
CFURL.prototype.schemeAndAuthority = function()
{
var string = "",
scheme = this.scheme();
if (scheme)
string += scheme + ":";
var authority = this.authority();
if (authority)
string += "//" + authority;
return string;
};
DISPLAY_NAME(CFURL.prototype.schemeAndAuthority);
CFURL.prototype.absoluteString = function()
{
if (this._absoluteString === undefined)
this._absoluteString = this.absoluteURL().string();
return this._absoluteString;
};
DISPLAY_NAME(CFURL.prototype.absoluteString);
CFURL.prototype.toString = function()
{
return this.absoluteString();
};
DISPLAY_NAME(CFURL.prototype.toString);
function resolveURL(aURL)
{
aURL = aURL.standardizedURL();
var baseURL = aURL.baseURL();
if (!baseURL)
return aURL;
var parts = PARTS(aURL),
resolvedParts,
absoluteBaseURL = baseURL.absoluteURL(),
baseParts = PARTS(absoluteBaseURL);
if (!parts.scheme && parts.authorityRoot)
{
// Handle "//domain.com/" style links which need to take their scheme from the base URL,
// but nothing else.
resolvedParts = CFURLPartsCreateCopy(parts);
resolvedParts.scheme = baseURL.scheme();
}
else if (parts.scheme || parts.authority)
{
resolvedParts = parts;
}
else
{
resolvedParts = { };
resolvedParts.scheme = baseParts.scheme;
resolvedParts.authority = baseParts.authority;
resolvedParts.userInfo = baseParts.userInfo;
resolvedParts.user = baseParts.user;
resolvedParts.password = baseParts.password;
resolvedParts.domain = baseParts.domain;
resolvedParts.portNumber = baseParts.portNumber;
resolvedParts.queryString = parts.queryString;
resolvedParts.fragment = parts.fragment;
var pathComponents = parts.pathComponents;
if (pathComponents.length && pathComponents[0] === "/")
{
resolvedParts.path = parts.path;
resolvedParts.pathComponents = pathComponents;
}
else
{
var basePathComponents = baseParts.pathComponents,
resolvedPathComponents = basePathComponents.concat(pathComponents);
// If baseURL is a file, then get rid of that file from the path components.
if (!baseURL.hasDirectoryPath() && basePathComponents.length)
resolvedPathComponents.splice(basePathComponents.length - 1, 1);
// If this doesn't start with a "..", then we're simply appending to already standardized paths.
if (pathComponents.length && (pathComponents[0] === ".." || pathComponents[0] === "."))
standardizePathComponents(resolvedPathComponents, YES);
resolvedParts.pathComponents = resolvedPathComponents;
resolvedParts.path = pathFromPathComponents(resolvedPathComponents, pathComponents.length <= 0 || aURL.hasDirectoryPath());
}
}
var resolvedString = URLStringFromParts(resolvedParts),
resolvedURL = new CFURL(resolvedString);
resolvedURL._parts = resolvedParts;
resolvedURL._standardizedURL = resolvedURL;
resolvedURL._standardizedString = resolvedString;
resolvedURL._absoluteURL = resolvedURL;
resolvedURL._absoluteString = resolvedString;
return resolvedURL;
}
function pathFromPathComponents(/*Array*/ pathComponents, /*BOOL*/ isDirectoryPath)
{
var path = pathComponents.join("/");
if (path.length && path.charAt(0) === "/")
path = path.substr(1);
if (isDirectoryPath)
path += "/";
return path;
}
function standardizePathComponents(/*Array*/ pathComponents, /*BOOL*/ inPlace)
{
var index = 0,
resultIndex = 0,
count = pathComponents.length,
result = inPlace ? pathComponents : [],
startsWithPeriod = NO;
for (; index < count; ++index)
{
var component = pathComponents[index];
if (component === "")
continue;
if (component === ".")
{
startsWithPeriod = resultIndex === 0;
continue;
}
if (component !== ".." || resultIndex === 0 || result[resultIndex - 1] === "..")
{
result[resultIndex] = component;
resultIndex++;
continue;
}
if (resultIndex > 0 && result[resultIndex - 1] !== "/")
--resultIndex;
}
if (startsWithPeriod && resultIndex === 0)
result[resultIndex++] = ".";
result.length = resultIndex;
return result;
}
function URLStringFromParts(/*Object*/ parts)
{
var string = "",
scheme = parts.scheme;
if (scheme)
string += scheme + ":";
var authority = parts.authority;
if (authority)
string += "//" + authority;
string += parts.path;
var queryString = parts.queryString;
if (queryString)
string += "?" + queryString;
var fragment = parts.fragment;
if (fragment)
string += "#" + fragment;
return string;
}
CFURL.prototype.absoluteURL = function()
{
if (this._absoluteURL === undefined)
this._absoluteURL = resolveURL(this);
return this._absoluteURL;
};
DISPLAY_NAME(CFURL.prototype.absoluteURL);
CFURL.prototype.standardizedURL = function()
{
if (this._standardizedURL === undefined)
{
var parts = PARTS(this),
pathComponents = parts.pathComponents,
standardizedPathComponents = standardizePathComponents(pathComponents, NO);
var standardizedPath = pathFromPathComponents(standardizedPathComponents, this.hasDirectoryPath());
if (parts.path === standardizedPath)
this._standardizedURL = this;
else
{
var standardizedParts = CFURLPartsCreateCopy(parts);
standardizedParts.pathComponents = standardizedPathComponents;
standardizedParts.path = standardizedPath;
var standardizedURL = new CFURL(URLStringFromParts(standardizedParts), this.baseURL());
standardizedURL._parts = standardizedParts;
standardizedURL._standardizedURL = standardizedURL;
this._standardizedURL = standardizedURL;
}
}
return this._standardizedURL;
};
DISPLAY_NAME(CFURL.prototype.standardizedURL);
function CFURLPartsCreateCopy(parts)
{
var copiedParts = { },
count = URI_KEYS.length;
while (count--)
{
var partName = URI_KEYS[count];
copiedParts[partName] = parts[partName];
}
return copiedParts;
}
CFURL.prototype.string = function()
{
return this._string;
};
DISPLAY_NAME(CFURL.prototype.string);
CFURL.prototype.authority = function()
{
var authority = PARTS(this).authority;
if (authority)
return authority;
var baseURL = this.baseURL();
return baseURL && baseURL.authority() || "";
};
DISPLAY_NAME(CFURL.prototype.authority);
CFURL.prototype.hasDirectoryPath = function()
{
var hasDirectoryPath = this._hasDirectoryPath;
if (hasDirectoryPath === undefined)
{
var path = this.path();
if (!path)
return NO;
if (path.charAt(path.length - 1) === "/")
return YES;
var lastPathComponent = this.lastPathComponent();
hasDirectoryPath = lastPathComponent === "." || lastPathComponent === "..";
this._hasDirectoryPath = hasDirectoryPath;
}
return hasDirectoryPath;
};
DISPLAY_NAME(CFURL.prototype.hasDirectoryPath);
CFURL.prototype.hostName = function()
{
return this.authority();
};
DISPLAY_NAME(CFURL.prototype.hostName);
CFURL.prototype.fragment = function()
{
return PARTS(this).fragment;
};
DISPLAY_NAME(CFURL.prototype.fragment);
CFURL.prototype.lastPathComponent = function()
{
if (this._lastPathComponent === undefined)
{
var pathComponents = this.pathComponents(),
pathComponentCount = pathComponents.length;
if (!pathComponentCount)
this._lastPathComponent = "";
else
this._lastPathComponent = pathComponents[pathComponentCount - 1];
}
return this._lastPathComponent;
};
DISPLAY_NAME(CFURL.prototype.lastPathComponent);
CFURL.prototype.path = function()
{
return PARTS(this).path;
};
DISPLAY_NAME(CFURL.prototype.path);
CFURL.prototype.createCopyDeletingLastPathComponent = function()
{
var parts = PARTS(this),
components = standardizePathComponents(parts.pathComponents, NO);
if (components.length > 0)
if (components.length > 1 || components[0] !== "/")
components.pop();
// pathFromPathComponents() returns an empty path for ["/"]
var isRoot = components.length === 1 && components[0] === "/";
parts.pathComponents = components;
parts.path = isRoot ? "/" : pathFromPathComponents(components, NO);
return new CFURL(URLStringFromParts(parts));
};
DISPLAY_NAME(CFURL.prototype.createCopyDeletingLastPathComponent);
CFURL.prototype.pathComponents = function()
{
return PARTS(this).pathComponents;
};
DISPLAY_NAME(CFURL.prototype.pathComponents);
CFURL.prototype.pathExtension = function()
{
var lastPathComponent = this.lastPathComponent();
if (!lastPathComponent)
return NULL;
lastPathComponent = lastPathComponent.replace(/^\.*/, '');
var index = lastPathComponent.lastIndexOf(".");
return index <= 0 ? "" : lastPathComponent.substring(index + 1);
};
DISPLAY_NAME(CFURL.prototype.pathExtension);
CFURL.prototype.queryString = function()
{
return PARTS(this).queryString;
};
DISPLAY_NAME(CFURL.prototype.queryString);
CFURL.prototype.scheme = function()
{
var scheme = this._scheme;
if (scheme === undefined)
{
scheme = PARTS(this).scheme;
if (!scheme)
{
var baseURL = this.baseURL();
scheme = baseURL && baseURL.scheme();
}
this._scheme = scheme;
}
return scheme;
};
DISPLAY_NAME(CFURL.prototype.scheme);
CFURL.prototype.user = function()
{
return PARTS(this).user;
};
DISPLAY_NAME(CFURL.prototype.user);
CFURL.prototype.password = function()
{
return PARTS(this).password;
};
DISPLAY_NAME(CFURL.prototype.password);
CFURL.prototype.portNumber = function()
{
return PARTS(this).portNumber;
};
DISPLAY_NAME(CFURL.prototype.portNumber);
CFURL.prototype.domain = function()
{
return PARTS(this).domain;
};
DISPLAY_NAME(CFURL.prototype.domain);
CFURL.prototype.baseURL = function()
{
return this._baseURL;
};
DISPLAY_NAME(CFURL.prototype.baseURL);
CFURL.prototype.asDirectoryPathURL = function()
{
if (this.hasDirectoryPath())
return this;
var lastPathComponent = this.lastPathComponent();
// We do this because on Windows the path may start with C: and be
// misinterpreted as a scheme.
if (lastPathComponent !== "/")
lastPathComponent = "./" + lastPathComponent;
return new CFURL(lastPathComponent + "/", this);
};
DISPLAY_NAME(CFURL.prototype.asDirectoryPathURL);
function CFURLGetResourcePropertiesForKeys(/*CFURL*/ aURL)
{
if (!aURL._resourcePropertiesForKeys)
aURL._resourcePropertiesForKeys = new CFMutableDictionary();
return aURL._resourcePropertiesForKeys;
}
CFURL.prototype.resourcePropertyForKey = function(/*String*/ aKey)
{
return CFURLGetResourcePropertiesForKeys(this).valueForKey(aKey);
};
DISPLAY_NAME(CFURL.prototype.resourcePropertyForKey);
CFURL.prototype.setResourcePropertyForKey = function(/*String*/ aKey, /*id*/ aValue)
{
CFURLGetResourcePropertiesForKeys(this).setValueForKey(aKey, aValue);
};
DISPLAY_NAME(CFURL.prototype.setResourcePropertyForKey);
CFURL.prototype.staticResourceData = function()
{
var data = new CFMutableData();
data.setRawString(StaticResource.resourceAtURL(this).contents());
return data;
};
DISPLAY_NAME(CFURL.prototype.staticResourceData);