UNPKG

objj-runtime

Version:

JavaScript (ECMAScript) and Objective-J runtime

729 lines (536 loc) 17.4 kB
/* * 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);