mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
651 lines
16 KiB
JavaScript
651 lines
16 KiB
JavaScript
// Based on the regex in RFC2396 Appendix B.
|
|
var URI_RE = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
|
|
|
var CFURLsForCachedUIDs,
|
|
CFURLCachingEnableCount = 0;
|
|
|
|
function enableCFURLCaching()
|
|
{
|
|
++CFURLCachingEnableCount;
|
|
CFURLsForCachedUIDs = { };
|
|
}
|
|
|
|
function disableCFURLCaching()
|
|
{
|
|
CFURLCachingEnableCount = MAX(CFURLCachingEnableCount - 1, 0);
|
|
|
|
if (!CFURLCachingEnableCount === 0)
|
|
delete CFURLsForCachedUIDs;
|
|
}
|
|
|
|
GLOBAL(CFURL) = function CFURL(/*CFURL|String*/ aURL, /*CFURL*/ aBaseURL)
|
|
{
|
|
if (aURL instanceof CFURL)
|
|
if (!aBaseURL)
|
|
return aURL
|
|
else
|
|
{
|
|
var existingBaseURL = aURL.baseURL();
|
|
|
|
if (existingBaseURL)
|
|
aBaseURL = new CFURL(existingBaseURL, aBaseURL);
|
|
|
|
return new CFURL(aURL.string(), aBaseURL);
|
|
}
|
|
|
|
// Use the cache if it's enabled.
|
|
if (CFURLsForCachedUIDs)
|
|
{
|
|
var cacheUID = aURL + " " + (aBaseURL && aBaseURL.UID() || "");
|
|
|
|
if (hasOwnProperty.call(CFURLsForCachedUIDs, cacheUID))
|
|
return CFURLsForCachedUIDs[cacheUID];
|
|
|
|
CFURLsForCachedUIDs[cacheUID] = this;
|
|
}
|
|
|
|
this._UID = objj_generateObjectUID();
|
|
this._string = aURL;
|
|
|
|
var result = (aURL || "").match(URI_RE);
|
|
|
|
this._baseURL = aBaseURL;
|
|
this._resourcePropertiesForKeys = new CFMutableDictionary();
|
|
|
|
this._scheme = result[2];
|
|
this._authority = result[4];
|
|
this._path = result[5] || "";
|
|
this._queryString = result[7] || NULL;
|
|
this._fragment = result[9] || NULL;
|
|
}
|
|
|
|
var URLMap = { };
|
|
|
|
CFURL.prototype.UID = function()
|
|
{
|
|
return this._UID;
|
|
}
|
|
|
|
CFURL.prototype.mappedURL = function()
|
|
{
|
|
return URLMap[this.absoluteString()] || this;
|
|
}
|
|
|
|
CFURL.setMappedURLForURL = function(/*CFURL*/ fromURL, /*CFURL*/ toURL)
|
|
{
|
|
URLMap[fromURL.absoluteString()] = toURL;
|
|
}
|
|
|
|
CFURL.prototype.schemeAndAuthority = function()
|
|
{
|
|
var string = "",
|
|
scheme = this.scheme();
|
|
|
|
if (scheme)
|
|
string += scheme + ":";
|
|
|
|
var authority = this.authority();
|
|
|
|
if (authority)
|
|
string += "//" + authority;
|
|
|
|
return string;
|
|
}
|
|
|
|
CFURL.prototype.absoluteString = function()
|
|
{
|
|
return this.absoluteURL().string();
|
|
}
|
|
|
|
CFURL.prototype.toString = function()
|
|
{
|
|
return this.absoluteString();
|
|
}
|
|
|
|
CFURL.prototype.absoluteURL = function()
|
|
{
|
|
if (this._absoluteURL === undefined)
|
|
{
|
|
var baseURL = this._baseURL;
|
|
|
|
this._absoluteURL = baseURL ? new CFURL(resolve(baseURL.absoluteString(), this.string())) : this;
|
|
}
|
|
|
|
return this._absoluteURL;
|
|
}
|
|
|
|
CFURL.prototype.string = function()
|
|
{
|
|
return this._string;
|
|
}
|
|
|
|
CFURL.prototype.authority = function()
|
|
{
|
|
var authority = this._authority;
|
|
|
|
if (authority === undefined)
|
|
{
|
|
var baseURL = this.baseURL();
|
|
|
|
authority = baseURL && baseURL.authority() || NULL;
|
|
this._authority = authority;
|
|
}
|
|
|
|
return authority;
|
|
}
|
|
|
|
CFURL.prototype.hasDirectoryPath = function()
|
|
{
|
|
var path = this.path();
|
|
|
|
if (!path)
|
|
return NO;
|
|
|
|
return path.charAt(path.length - 1) === "/";
|
|
}
|
|
|
|
CFURL.prototype.hostName = function()
|
|
{
|
|
return this._authority;
|
|
}
|
|
|
|
CFURL.prototype.fragment = function()
|
|
{
|
|
return this._fragment;
|
|
}
|
|
|
|
CFURL.prototype.lastPathComponent = function()
|
|
{
|
|
var path = this.path();
|
|
|
|
if (!path)
|
|
return "";
|
|
|
|
// Root is a special case.
|
|
if (path === "/")
|
|
return "/";
|
|
|
|
// Remove the trailing slash, since "path/directory/" and "path/directory" should both return a.
|
|
var components = path.replace(/\/$/g, "").split("/");
|
|
|
|
return components[components.length - 1];
|
|
}
|
|
|
|
CFURL.prototype.path = function()
|
|
{
|
|
return this._path;
|
|
}
|
|
|
|
CFURL.prototype.pathComponents = function()
|
|
{
|
|
if (!this._pathComponents)
|
|
{
|
|
var path = this.path();
|
|
|
|
if (!path)
|
|
this._pathComponents = [];
|
|
else
|
|
{
|
|
var components = path.split("/"),
|
|
result = [],
|
|
index = 0,
|
|
count = components.length;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
var component = components[index];
|
|
|
|
if (component)
|
|
result.push(component);
|
|
|
|
else if (index === 0)
|
|
result.push("/");
|
|
}
|
|
|
|
this._pathComponents = result;
|
|
}
|
|
}
|
|
|
|
return this._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);
|
|
}
|
|
|
|
CFURL.prototype.queryString = function()
|
|
{
|
|
return this._queryString;
|
|
}
|
|
|
|
CFURL.prototype.scheme = function()
|
|
{
|
|
var scheme = this._scheme;
|
|
|
|
if (scheme === undefined)
|
|
{
|
|
var baseURL = this.baseURL();
|
|
|
|
scheme = baseURL && baseURL.scheme() || NULL;
|
|
this._scheme = scheme;
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
CFURL.prototype.baseURL = function()
|
|
{
|
|
return this._baseURL;
|
|
}
|
|
|
|
CFURL.prototype.asDirectoryPathURL = function()
|
|
{
|
|
if (this.hasDirectoryPath())
|
|
return this;
|
|
|
|
return new CFURL(this.lastPathComponent() + "/", this);
|
|
}
|
|
|
|
CFURL.prototype.resourcePropertyForKey = function(/*String*/ aKey)
|
|
{
|
|
return this._resourcePropertiesForKeys.objectForKey(aKey);
|
|
}
|
|
|
|
CFURL.prototype.setResourcePropertyForKey = function(/*String*/ aKey, /*id*/ aValue)
|
|
{
|
|
this._resourcePropertiesForKeys.setObjectForKey(aKey, aValue);
|
|
}
|
|
|
|
CFURL.prototype.staticResourceData = function()
|
|
{
|
|
var data = new CFMutableData();
|
|
|
|
data.setRawString(StaticResource.resourceAtURL(this).contents());
|
|
|
|
return data;
|
|
}
|
|
/*
|
|
function parseURL(aURL)
|
|
{
|
|
var parts = expression.exec(aURL.string()),
|
|
index = 0,
|
|
count = parts.length;
|
|
|
|
for (; index < count; ++index)
|
|
aURL[expressionKeys[index]] = parts[index] || "";
|
|
|
|
aURL._root = (aURL._root || aURL._authorityRoot) ? "/" : "";
|
|
|
|
var components = aPath.split("/"),
|
|
results = [],
|
|
index = 0,
|
|
count = components.length,
|
|
isRoot = aPath.charAt(0) === "/";
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
var component = components[index];
|
|
|
|
// These simply remain in the current directory.
|
|
if (component === "" || component === ".")
|
|
continue;
|
|
|
|
if (component !== "..")
|
|
{
|
|
results.push(component);
|
|
continue;
|
|
}
|
|
|
|
var resultsCount = results.length;
|
|
|
|
// If we have a valid previous component, "climb" it.
|
|
if (resultsCount > 0 && results[resultsCount - 1] !== "..")
|
|
results.pop();
|
|
|
|
// If this isn't a root listing, and we are preceded by only ..'s, or
|
|
// nothing at all, then add it since it makes sense for relative paths.
|
|
else if (!isRoot && resultsCount === 0 || results[resultsCount - 1] === "..")
|
|
results.push(component);
|
|
}
|
|
|
|
return (isRoot ? "/" : "") + results.join("/");
|
|
|
|
items.direcotries = that;
|
|
items.domains = items.domain.split(".");
|
|
};
|
|
}
|
|
*/
|
|
// from Chiron's HTTP module:
|
|
|
|
/**** keys
|
|
members of a parsed URI object.
|
|
*/
|
|
var keys = [
|
|
"url",
|
|
"protocol",
|
|
"authorityRoot",
|
|
"authority",
|
|
"userInfo",
|
|
"user",
|
|
"password",
|
|
"domain",
|
|
"domains",
|
|
"port",
|
|
"path",
|
|
"root",
|
|
"directory",
|
|
"directories",
|
|
"file",
|
|
"query",
|
|
"anchor"
|
|
];
|
|
|
|
/**** expressionKeys
|
|
members of a parsed URI object that you get
|
|
from evaluting the strict regular expression.
|
|
*/
|
|
var expressionKeys = [
|
|
"url",
|
|
"protocol",
|
|
"authorityRoot",
|
|
"authority",
|
|
"userInfo",
|
|
"user",
|
|
"password",
|
|
"domain",
|
|
"port",
|
|
"path",
|
|
"root",
|
|
"directory",
|
|
"file",
|
|
"query",
|
|
"anchor"
|
|
];
|
|
|
|
/**** strictExpression
|
|
*/
|
|
var strictExpression = new RegExp( /* url */
|
|
"^" +
|
|
"(?:" +
|
|
"([^:/?#]+):" + /* protocol */
|
|
")?" +
|
|
"(?:" +
|
|
"(//)" + /* authorityRoot */
|
|
"(" + /* authority */
|
|
"(?:" +
|
|
"(" + /* userInfo */
|
|
"([^:@]*)" + /* user */
|
|
":?" +
|
|
"([^:@]*)" + /* password */
|
|
")?" +
|
|
"@" +
|
|
")?" +
|
|
"([^:/?#]*)" + /* domain */
|
|
"(?::(\\d*))?" + /* port */
|
|
")" +
|
|
")?" +
|
|
"(" + /* path */
|
|
"(/?)" + /* root */
|
|
"((?:[^?#/]*/)*)" +
|
|
"([^?#]*)" + /* file */
|
|
")" +
|
|
"(?:\\?([^#]*))?" + /* query */
|
|
"(?:#(.*))?" /*anchor */
|
|
);
|
|
|
|
/**** Parser
|
|
returns a URI parser function given
|
|
a regular expression that renders
|
|
`expressionKeys` and returns an `Object`
|
|
mapping all `keys` to values.
|
|
*/
|
|
var Parser = function (expression) {
|
|
return function p_p(url) {
|
|
if (typeof url == "undefined")
|
|
throw new Error("HttpError: URL is undefined");
|
|
if (typeof url != "string") return new Object(url);
|
|
|
|
var items = {};
|
|
var parts = expression.exec(url);
|
|
|
|
for (var i = 0; i < parts.length; i++) {
|
|
items[expressionKeys[i]] = parts[i] ? parts[i] : "";
|
|
}
|
|
|
|
items.root = (items.root || items.authorityRoot) ? '/' : '';
|
|
|
|
items.directories = items.directory.split("/");
|
|
if (items.directories[items.directories.length - 1] == "") {
|
|
items.directories.pop();
|
|
}
|
|
|
|
/* normalize */
|
|
var directories = [];
|
|
for (var i = 0; i < items.directories.length; i++) {
|
|
var directory = items.directories[i];
|
|
if (directory == '.') {
|
|
} else if (directory == '..') {
|
|
if (directories.length && directories[directories.length - 1] != '..')
|
|
directories.pop();
|
|
else
|
|
directories.push('..');
|
|
} else {
|
|
directories.push(directory);
|
|
}
|
|
}
|
|
items.directories = directories;
|
|
|
|
items.domains = items.domain.split(".");
|
|
|
|
return items;
|
|
};
|
|
};
|
|
|
|
/**** parse
|
|
a strict URI parser.
|
|
*/
|
|
var parse = Parser(strictExpression);
|
|
|
|
/**** format
|
|
accepts a parsed URI object and returns
|
|
the corresponding string.
|
|
*/
|
|
var format = function (object) {
|
|
if (typeof(object) == 'undefined')
|
|
throw new Error("UrlError: URL undefined for urls#format");
|
|
if (object instanceof String || typeof(object) == 'string')
|
|
return object;
|
|
var domain =
|
|
object.domains ?
|
|
object.domains.join(".") :
|
|
object.domain;
|
|
var userInfo = (
|
|
object.user ||
|
|
object.password
|
|
) ?
|
|
(
|
|
(object.user || "") +
|
|
(object.password ? ":" + object.password : "")
|
|
) :
|
|
object.userInfo;
|
|
var authority = (
|
|
userInfo ||
|
|
domain ||
|
|
object.port
|
|
) ? (
|
|
(userInfo ? userInfo + "@" : "") +
|
|
(domain || "") +
|
|
(object.port ? ":" + object.port : "")
|
|
) :
|
|
object.authority;
|
|
var directory =
|
|
object.directories ?
|
|
object.directories.join("/") :
|
|
object.directory;
|
|
var path =
|
|
directory || object.file ?
|
|
(
|
|
(directory ? directory + "/" : "") +
|
|
(object.file || "")
|
|
) :
|
|
object.path;
|
|
return (
|
|
(object.protocol ? object.protocol + ":" : "") +
|
|
(authority ? "//" + authority : "") +
|
|
(object.root || (authority && path) ? "/" : "") +
|
|
(path ? path : "") +
|
|
(object.query ? "?" + object.query : "") +
|
|
(object.anchor ? "#" + object.anchor : "")
|
|
) || object.url || "";
|
|
};
|
|
|
|
/**** resolveObject
|
|
returns an object representing a URL resolved from
|
|
a relative location and a source location.
|
|
*/
|
|
var resolveObject = function (source, relative) {
|
|
if (!source)
|
|
return relative;
|
|
|
|
source = parse(source);
|
|
relative = parse(relative);
|
|
|
|
if (relative.url == "")
|
|
return source;
|
|
|
|
delete source.url;
|
|
delete source.authority;
|
|
delete source.domain;
|
|
delete source.userInfo;
|
|
delete source.path;
|
|
delete source.directory;
|
|
|
|
if (
|
|
relative.protocol && relative.protocol != source.protocol ||
|
|
relative.authority && relative.authority != source.authority
|
|
) {
|
|
source = relative;
|
|
} else {
|
|
if (relative.root || relative.protocol) {
|
|
source.directories = relative.directories;
|
|
} else {
|
|
|
|
var directories = relative.directories;
|
|
for (var i = 0; i < directories.length; i++) {
|
|
var directory = directories[i];
|
|
if (directory == ".") {
|
|
} else if (directory == "..") {
|
|
if (source.directories.length) {
|
|
source.directories.pop();
|
|
} else {
|
|
source.directories.push('..');
|
|
}
|
|
} else {
|
|
source.directories.push(directory);
|
|
}
|
|
}
|
|
|
|
if (relative.file == ".") {
|
|
relative.file = "";
|
|
} else if (relative.file == "..") {
|
|
source.directories.pop();
|
|
relative.file = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (relative.root || relative.protocol)
|
|
source.root = relative.root;
|
|
if (relative.protocol)
|
|
source.protocol = relative.protocol;
|
|
if (!(!relative.path && relative.anchor))
|
|
source.file = relative.file;
|
|
source.query = relative.query;
|
|
source.anchor = relative.anchor;
|
|
|
|
return source;
|
|
};
|
|
|
|
/**** relativeObject
|
|
returns an object representing a relative URL to
|
|
a given target URL from a source URL.
|
|
*/
|
|
var relativeObject = function (source, target) {
|
|
target = parse(target);
|
|
source = parse(source);
|
|
|
|
delete target.url;
|
|
|
|
if (
|
|
target.protocol == source.protocol &&
|
|
target.authority == source.authority
|
|
) {
|
|
delete target.protocol;
|
|
delete target.authority;
|
|
delete target.userInfo;
|
|
delete target.user;
|
|
delete target.password;
|
|
delete target.domain;
|
|
delete target.domains;
|
|
delete target.port;
|
|
if (
|
|
!!target.root == !!source.root && !(
|
|
target.root &&
|
|
target.directories[0] != source.directories[0]
|
|
)
|
|
) {
|
|
delete target.path;
|
|
delete target.root;
|
|
delete target.directory;
|
|
while (
|
|
source.directories.length &&
|
|
target.directories.length &&
|
|
target.directories[0] == source.directories[0]
|
|
) {
|
|
target.directories.shift();
|
|
source.directories.shift();
|
|
}
|
|
while (source.directories.length) {
|
|
source.directories.shift();
|
|
target.directories.unshift('..');
|
|
}
|
|
|
|
if (!target.root && !target.directories.length && !target.file && source.file)
|
|
target.directories.push('.');
|
|
|
|
if (source.file == target.file)
|
|
delete target.file;
|
|
if (source.query == target.query)
|
|
delete target.query;
|
|
if (source.anchor == target.anchor)
|
|
delete target.anchor;
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**** resolve
|
|
returns a URL resovled to a relative URL from a source URL.
|
|
*/
|
|
var resolve = function (source, relative) {
|
|
return format(resolveObject(source, relative));
|
|
};
|
|
|
|
/**** relative
|
|
returns a relative URL to a target from a source.
|
|
*/
|
|
var relative = function (source, target) {
|
|
return format(relativeObject(source, target));
|
|
};
|