8889841cREADME.md000066600000014551150514456310006041 0ustar00# relateurl [![NPM Version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][david-image]][david-url] > Minify URLs by converting them from absolute to relative. If you were to use this library on a website like `http://example.com/dir1/dir1-1/`, you would get results such as: | Before | After | | :------------------------------------------ | :----------------------------------- | | `http://example.com/dir1/dir1-2/index.html` | `../dir1-2/` | | `http://example.com/dir2/dir2-1/` | `/dir2/dir2-1/` | | `http://example.com/dir1/dir1-1/` | ` ` | | `https://example.com/dir1/dir1-1/` | `https://example.com/dir1/dir1-1/` | | `http://google.com:80/dir/` | `//google.com/dir/` | | `../../../../../../../../#anchor` | `/#anchor` | **All string parsing.** *No* directory browsing. It is thoroughly tested, very fast and lightweight with zero external dependencies. ## Getting Started This utility requires [Node.js](http://nodejs.org/) `>= 0.10`. To install, type this at the command line: ``` npm install relateurl --save-dev ``` ### Options #### options.defaultPorts Type: `Object` Default value: `{ftp:21, http:80, https:443}` Extend the list with any ports you need. Any URLs containing these default ports will have them removed. Example: `http://example.com:80/` will become `http://example.com/`. #### options.directoryIndexes Type: `Array` Default value: `["index.html"]` Extend the list with any resources you need. Works with [`options.removeDirectoryIndexes`](#options.removeDirectoryIndexes). #### options.ignore_www Type: `Boolean` Default value: `false` This will, for example, consider any domains containing `http://www.example.com/` to be related to any that contain `http://example.com/`. #### options.output Type: constant or `String` Choices: `RelateUrl.ABSOLUTE`,`RelateUrl.PATH_RELATIVE`,`RelateUrl.ROOT_RELATIVE`,`RelateUrl.SHORTEST` Choices: `"absolute"`,`"pathRelative"`,`"rootRelative"`,`"shortest"` Default value: `RelateUrl.SHORTEST` `RelateUrl.ABSOLUTE` will produce an absolute URL. Overrides [`options.schemeRelative`](#options.schemeRelative) with a value of `false`. `RelateUrl.PATH_RELATIVE` will produce something like `../child-of-parent/etc/`. `RelateUrl.ROOT_RELATIVE` will produce something like `/child-of-root/etc/`. `RelateUrl.SHORTEST` will choose whichever is shortest between root- and path-relative. #### options.rejectedSchemes Type: `Array` Default value: `["data","javascript","mailto"]` Extend the list with any additional schemes. Example: `javascript:something` will not be modified. #### options.removeAuth Type: `Boolean` Default value: `false` Remove user authentication information from the output URL. #### options.removeDirectoryIndexes Type: `Boolean` Default value: `true` Remove any resources that match any found in [`options.directoryIndexes`](#options.directoryIndexes). #### options.removeEmptyQueries Type: `Boolean` Default value: `false` Remove empty query variables. Example: `http://domain.com/?var1&var2=&var3=asdf` will become `http://domain.com/?var3=adsf`. This does not apply to unrelated URLs (with other protocols, auths, hosts and/or ports). #### options.removeRootTrailingSlash Type: `Boolean` Default value: `true` Remove trailing slashes from root paths. Example: `http://domain.com/?var` will become `http://domain.com?var` while `http://domain.com/dir/?var` will not be modified. #### options.schemeRelative Type: `Boolean` Default value: `true` Output URLs relative to the scheme. Example: `http://example.com/` will become `//example.com/`. #### options.site Type: `String` Default value: `undefined` An options-based version of the [`from`](#examples) argument. If both are specified, `from` takes priority. #### options.slashesDenoteHost Type: `Boolean` Default value: `true` Passed to Node's [`url.parse`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost). ### Examples This library can be used as a [function for single-use](#single-instance) or as a [class for multiple conversions](#reusable-instances). Upon successful conversion, a `String` will be returned. If an issue is encountered while parsing `from`, an error will be thrown. #### Single Instance ```js var RelateUrl = require("relateurl"); var result = RelateUrl.relate(from, to, options); ``` #### Reusable Instances ```js var RelateUrl = require("relateurl"); var instance = new RelateUrl(from, options); var result1 = instance.relate(to1); var result2 = instance.relate(to2, customOptions); var result3 = instance.relate(to3); ``` ## FAQ 1. **Why bother writing/using this?** To aid in further minifying HTML, mainly for the purpose of faster page loads and SEO. It's been integrated into [HTMLMinifier](https://github.com/kangax/html-minifier). 2. **Why not just use Node's `url.parse`, `url.resolve` and `path.relative`?** `url.parse` *is* used, but `url.resolve` and `path.relative` are both slower and less powerful than this library. ## Release History * 0.2.7 Node v6 support * 0.2.6 minor enhancements * 0.2.5 added `options.removeRootTrailingSlash` * 0.2.4 added `options.site` * 0.2.3 added browserify npm-script * 0.2.2 removed task runner * 0.2.1 shorten resource- and query-relative URLs, test variations list with other site URLs * 0.2.0 code cleanup, `options.removeEmptyQueries=true` only applied to unrelated URLs * 0.1.0 initial release ## Roadmap * 0.2.8 check if queries are the same, regardless of param order * 0.2.8 possible [scheme exclusions](http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml) such as `tel:` * 0.2.8 decipher and return invalid input (special cases) to complete test suite * 0.3.0 test `options.slashesDenoteHost=false`, add something like `options.externalDirectoryIndexes=[]` for external sites [npm-image]: https://img.shields.io/npm/v/relateurl.svg [npm-url]: https://npmjs.org/package/relateurl [travis-image]: https://img.shields.io/travis/stevenvachon/relateurl.svg [travis-url]: https://travis-ci.org/stevenvachon/relateurl [david-image]: https://img.shields.io/david/stevenvachon/relateurl.svg [david-url]: https://david-dm.org/stevenvachon/relateurl license000066600000002127150514456310006123 0ustar00The MIT License (MIT) Copyright (c) Steven Vachon (svachon.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lib/index.js000066600000003406150514456310006772 0ustar00"use strict"; var constants = require("./constants"); var formatUrl = require("./format"); var getOptions = require("./options"); var objUtils = require("./util/object"); var parseUrl = require("./parse"); var relateUrl = require("./relate"); function RelateUrl(from, options) { this.options = getOptions(options, { defaultPorts: {ftp:21, http:80, https:443}, directoryIndexes: ["index.html"], ignore_www: false, output: RelateUrl.SHORTEST, rejectedSchemes: ["data","javascript","mailto"], removeAuth: false, removeDirectoryIndexes: true, removeEmptyQueries: false, removeRootTrailingSlash: true, schemeRelative: true, site: undefined, slashesDenoteHost: true }); this.from = parseUrl.from(from, this.options, null); } /* Usage: instance=new RelateUrl(); instance.relate(); */ RelateUrl.prototype.relate = function(from, to, options) { // relate(to,options) if ( objUtils.isPlainObject(to) ) { options = to; to = from; from = null; } // relate(to) else if (!to) { to = from; from = null; } options = getOptions(options, this.options); from = from || options.site; from = parseUrl.from(from, options, this.from); if (!from || !from.href) { throw new Error("from value not defined."); } else if (from.extra.hrefInfo.minimumPathOnly) { throw new Error("from value supplied is not absolute: "+from.href); } to = parseUrl.to(to, options); if (to.valid===false) return to.href; to = relateUrl(from, to, options); to = formatUrl(to, options); return to; } /* Usage: RelateUrl.relate(); */ RelateUrl.relate = function(from, to, options) { return new RelateUrl().relate(from, to, options); } // Make constants accessible from API objUtils.shallowMerge(RelateUrl, constants); module.exports = RelateUrl; lib/options.js000066600000001515150514456310007355 0ustar00"use strict"; var objUtils = require("./util/object"); function getOptions(options, defaults) { if ( objUtils.isPlainObject(options) ) { var newOptions = {}; for (var i in defaults) { if ( defaults.hasOwnProperty(i) ) { if (options[i] !== undefined) { newOptions[i] = mergeOption(options[i], defaults[i]); } else { newOptions[i] = defaults[i]; } } } return newOptions; } else { return defaults; } } function mergeOption(newValues, defaultValues) { if (defaultValues instanceof Object && newValues instanceof Object) { if (defaultValues instanceof Array && newValues instanceof Array) { return defaultValues.concat(newValues); } else { return objUtils.shallowMerge(newValues, defaultValues); } } return newValues; } module.exports = getOptions; lib/constants.js000066600000000247150514456310007677 0ustar00"use strict"; module.exports = { // Output ABSOLUTE: "absolute", PATH_RELATIVE: "pathRelative", ROOT_RELATIVE: "rootRelative", SHORTEST: "shortest" }; lib/relate/index.js000066600000000437150514456310010247 0ustar00"use strict"; var absolutize = require("./absolutize"); var relativize = require("./relativize"); function relateUrl(siteUrlObj, urlObj, options) { absolutize(urlObj, siteUrlObj, options); relativize(urlObj, siteUrlObj, options); return urlObj; } module.exports = relateUrl; lib/relate/findRelation.js000066600000006735150514456310011565 0ustar00"use strict"; function findRelation_upToPath(urlObj, siteUrlObj, options) { // Path- or root-relative URL var pathOnly = urlObj.extra.hrefInfo.minimumPathOnly; // Matching scheme, scheme-relative or path-only var minimumScheme = (urlObj.scheme===siteUrlObj.scheme || !urlObj.scheme); // Matching auth, ignoring auth or path-only var minimumAuth = minimumScheme && (urlObj.auth===siteUrlObj.auth || options.removeAuth || pathOnly); // Matching host or path-only var www = options.ignore_www ? "stripped" : "full"; var minimumHost = minimumAuth && (urlObj.host[www]===siteUrlObj.host[www] || pathOnly); // Matching port or path-only var minimumPort = minimumHost && (urlObj.port===siteUrlObj.port || pathOnly); urlObj.extra.relation.minimumScheme = minimumScheme; urlObj.extra.relation.minimumAuth = minimumAuth; urlObj.extra.relation.minimumHost = minimumHost; urlObj.extra.relation.minimumPort = minimumPort; urlObj.extra.relation.maximumScheme = !minimumScheme || minimumScheme && !minimumAuth; urlObj.extra.relation.maximumAuth = !minimumScheme || minimumScheme && !minimumHost; urlObj.extra.relation.maximumHost = !minimumScheme || minimumScheme && !minimumPort; } function findRelation_pathOn(urlObj, siteUrlObj, options) { var queryOnly = urlObj.extra.hrefInfo.minimumQueryOnly; var hashOnly = urlObj.extra.hrefInfo.minimumHashOnly; var empty = urlObj.extra.hrefInfo.empty; // not required, but self-documenting // From upToPath() var minimumPort = urlObj.extra.relation.minimumPort; var minimumScheme = urlObj.extra.relation.minimumScheme; // Matching port and path var minimumPath = minimumPort && urlObj.path.absolute.string===siteUrlObj.path.absolute.string; // Matching resource or query/hash-only or empty var matchingResource = (urlObj.resource===siteUrlObj.resource || !urlObj.resource && siteUrlObj.extra.resourceIsIndex) || (options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex && !siteUrlObj.resource); var minimumResource = minimumPath && (matchingResource || queryOnly || hashOnly || empty); // Matching query or hash-only/empty var query = options.removeEmptyQueries ? "stripped" : "full"; var urlQuery = urlObj.query.string[query]; var siteUrlQuery = siteUrlObj.query.string[query]; var minimumQuery = (minimumResource && !!urlQuery && urlQuery===siteUrlQuery) || ((hashOnly || empty) && !urlObj.extra.hrefInfo.separatorOnlyQuery); var minimumHash = minimumQuery && urlObj.hash===siteUrlObj.hash; urlObj.extra.relation.minimumPath = minimumPath; urlObj.extra.relation.minimumResource = minimumResource; urlObj.extra.relation.minimumQuery = minimumQuery; urlObj.extra.relation.minimumHash = minimumHash; urlObj.extra.relation.maximumPort = !minimumScheme || minimumScheme && !minimumPath; urlObj.extra.relation.maximumPath = !minimumScheme || minimumScheme && !minimumResource; urlObj.extra.relation.maximumResource = !minimumScheme || minimumScheme && !minimumQuery; urlObj.extra.relation.maximumQuery = !minimumScheme || minimumScheme && !minimumHash; urlObj.extra.relation.maximumHash = !minimumScheme || minimumScheme && !minimumHash; // there's nothing after hash, so it's the same as maximumQuery // Matching path and/or resource with existing but non-matching site query urlObj.extra.relation.overridesQuery = minimumPath && urlObj.extra.relation.maximumResource && !minimumQuery && !!siteUrlQuery; } module.exports = { pathOn: findRelation_pathOn, upToPath: findRelation_upToPath }; lib/relate/absolutize.js000066600000004366150514456310011326 0ustar00"use strict"; var findRelation = require("./findRelation"); var objUtils = require("../util/object"); var pathUtils = require("../util/path"); function absolutize(urlObj, siteUrlObj, options) { findRelation.upToPath(urlObj, siteUrlObj, options); // Fill in relative URLs if (urlObj.extra.relation.minimumScheme) urlObj.scheme = siteUrlObj.scheme; if (urlObj.extra.relation.minimumAuth) urlObj.auth = siteUrlObj.auth; if (urlObj.extra.relation.minimumHost) urlObj.host = objUtils.clone(siteUrlObj.host); if (urlObj.extra.relation.minimumPort) copyPort(urlObj, siteUrlObj); if (urlObj.extra.relation.minimumScheme) copyPath(urlObj, siteUrlObj); // Check remaining relativeness now that path has been copied and/or resolved findRelation.pathOn(urlObj, siteUrlObj, options); // Fill in relative URLs if (urlObj.extra.relation.minimumResource) copyResource(urlObj, siteUrlObj); if (urlObj.extra.relation.minimumQuery) urlObj.query = objUtils.clone(siteUrlObj.query); if (urlObj.extra.relation.minimumHash) urlObj.hash = siteUrlObj.hash; } /* Get an absolute path that's relative to site url. */ function copyPath(urlObj, siteUrlObj) { if (urlObj.extra.relation.maximumHost || !urlObj.extra.hrefInfo.minimumResourceOnly) { var pathArray = urlObj.path.absolute.array; var pathString = "/"; // If not erroneous URL if (pathArray) { // If is relative path if (urlObj.extra.hrefInfo.minimumPathOnly && urlObj.path.absolute.string.indexOf("/")!==0) { // Append path to site path pathArray = siteUrlObj.path.absolute.array.concat(pathArray); } pathArray = pathUtils.resolveDotSegments(pathArray); pathString += pathUtils.join(pathArray); } else { pathArray = []; } urlObj.path.absolute.array = pathArray; urlObj.path.absolute.string = pathString; } else { // Resource-, query- or hash-only or empty urlObj.path = objUtils.clone(siteUrlObj.path); } } function copyPort(urlObj, siteUrlObj) { urlObj.port = siteUrlObj.port; urlObj.extra.portIsDefault = siteUrlObj.extra.portIsDefault; } function copyResource(urlObj, siteUrlObj) { urlObj.resource = siteUrlObj.resource; urlObj.extra.resourceIsIndex = siteUrlObj.extra.resourceIsIndex; } module.exports = absolutize; lib/relate/relativize.js000066600000002032150514456310011307 0ustar00"use strict"; var pathUtils = require("../util/path"); /* Get a path relative to the site path. */ function relatePath(absolutePath, siteAbsolutePath) { var relativePath = []; // At this point, it's related to the host/port var related = true; var parentIndex = -1; // Find parents siteAbsolutePath.forEach( function(siteAbsoluteDir, i) { if (related) { if (absolutePath[i] !== siteAbsoluteDir) { related = false; } else { parentIndex = i; } } if (!related) { // Up one level relativePath.push(".."); } }); // Form path absolutePath.forEach( function(dir, i) { if (i > parentIndex) { relativePath.push(dir); } }); return relativePath; } function relativize(urlObj, siteUrlObj, options) { if (urlObj.extra.relation.minimumScheme) { var pathArray = relatePath(urlObj.path.absolute.array, siteUrlObj.path.absolute.array); urlObj.path.relative.array = pathArray; urlObj.path.relative.string = pathUtils.join(pathArray); } } module.exports = relativize; lib/util/object.js000066600000001472150514456310010107 0ustar00"use strict"; /* Deep-clone an object. */ function clone(obj) { if (obj instanceof Object) { var clonedObj = (obj instanceof Array) ? [] : {}; for (var i in obj) { if ( obj.hasOwnProperty(i) ) { clonedObj[i] = clone( obj[i] ); } } return clonedObj; } return obj; } /* https://github.com/jonschlinkert/is-plain-object */ function isPlainObject(obj) { return !!obj && typeof obj==="object" && obj.constructor===Object; } /* Shallow-merge two objects. */ function shallowMerge(target, source) { if (target instanceof Object && source instanceof Object) { for (var i in source) { if ( source.hasOwnProperty(i) ) { target[i] = source[i]; } } } return target; } module.exports = { clone: clone, isPlainObject: isPlainObject, shallowMerge: shallowMerge }; lib/util/path.js000066600000001100150514456310007561 0ustar00"use strict"; function joinPath(pathArray) { if (pathArray.length > 0) { return pathArray.join("/") + "/"; } else { return ""; } } function resolveDotSegments(pathArray) { var pathAbsolute = []; pathArray.forEach( function(dir) { if (dir !== "..") { if (dir !== ".") { pathAbsolute.push(dir); } } else { // Remove parent if (pathAbsolute.length > 0) { pathAbsolute.splice(pathAbsolute.length-1, 1); } } }); return pathAbsolute; } module.exports = { join: joinPath, resolveDotSegments: resolveDotSegments }; lib/util/devlog.js000066600000000451150514456310010115 0ustar00"use strict"; var inspect = require("util").inspect; function log(data) { console.log( inspect(data, {depth:null, colors:true}) ); } function logAll(data) { console.log( inspect(data, {depth:null, showHidden:true, colors:true}) ); } module.exports = { log: log, logAll: logAll }; lib/format.js000066600000006551150514456310007157 0ustar00"use strict"; var constants = require("./constants"); function formatAuth(urlObj, options) { if (urlObj.auth && !options.removeAuth && (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE)) { return urlObj.auth + "@"; } return ""; } function formatHash(urlObj, options) { return urlObj.hash ? urlObj.hash : ""; } function formatHost(urlObj, options) { if (urlObj.host.full && (urlObj.extra.relation.maximumAuth || options.output===constants.ABSOLUTE)) { return urlObj.host.full; } return ""; } function formatPath(urlObj, options) { var str = ""; var absolutePath = urlObj.path.absolute.string; var relativePath = urlObj.path.relative.string; var resource = showResource(urlObj, options); if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE) { str = absolutePath; } else if (relativePath.length<=absolutePath.length && options.output===constants.SHORTEST || options.output===constants.PATH_RELATIVE) { str = relativePath; if (str === "") { var query = showQuery(urlObj,options) && !!getQuery(urlObj,options); if (urlObj.extra.relation.maximumPath && !resource) { str = "./"; } else if (urlObj.extra.relation.overridesQuery && !resource && !query) { str = "./"; } } } else { str = absolutePath; } if ( str==="/" && !resource && options.removeRootTrailingSlash && (!urlObj.extra.relation.minimumPort || options.output===constants.ABSOLUTE) ) { str = ""; } return str; } function formatPort(urlObj, options) { if (urlObj.port && !urlObj.extra.portIsDefault && urlObj.extra.relation.maximumHost) { return ":" + urlObj.port; } return ""; } function formatQuery(urlObj, options) { return showQuery(urlObj,options) ? getQuery(urlObj, options) : ""; } function formatResource(urlObj, options) { return showResource(urlObj,options) ? urlObj.resource : ""; } function formatScheme(urlObj, options) { var str = ""; if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE) { if (!urlObj.extra.relation.minimumScheme || !options.schemeRelative || options.output===constants.ABSOLUTE) { str += urlObj.scheme + "://"; } else { str += "//"; } } return str; } function formatUrl(urlObj, options) { var url = ""; url += formatScheme(urlObj, options); url += formatAuth(urlObj, options); url += formatHost(urlObj, options); url += formatPort(urlObj, options); url += formatPath(urlObj, options); url += formatResource(urlObj, options); url += formatQuery(urlObj, options); url += formatHash(urlObj, options); return url; } function getQuery(urlObj, options) { var stripQuery = options.removeEmptyQueries && urlObj.extra.relation.minimumPort; return urlObj.query.string[ stripQuery ? "stripped" : "full" ]; } function showQuery(urlObj, options) { return !urlObj.extra.relation.minimumQuery || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE; } function showResource(urlObj, options) { var removeIndex = options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex; var removeMatchingResource = urlObj.extra.relation.minimumResource && options.output!==constants.ABSOLUTE && options.output!==constants.ROOT_RELATIVE; return !!urlObj.resource && !removeMatchingResource && !removeIndex; } module.exports = formatUrl; lib/parse/index.js000066600000002114150514456310010077 0ustar00"use strict"; var hrefInfo = require("./hrefInfo"); var parseHost = require("./host"); var parsePath = require("./path"); var parsePort = require("./port"); var parseQuery = require("./query"); var parseUrlString = require("./urlstring"); var pathUtils = require("../util/path"); function parseFromUrl(url, options, fallback) { if (url) { var urlObj = parseUrl(url, options); // Because the following occurs in the relate stage for "to" URLs, // such had to be mostly duplicated here var pathArray = pathUtils.resolveDotSegments(urlObj.path.absolute.array); urlObj.path.absolute.array = pathArray; urlObj.path.absolute.string = "/" + pathUtils.join(pathArray); return urlObj; } else { return fallback; } } function parseUrl(url, options) { var urlObj = parseUrlString(url, options); if (urlObj.valid===false) return urlObj; parseHost(urlObj, options); parsePort(urlObj, options); parsePath(urlObj, options); parseQuery(urlObj, options); hrefInfo(urlObj); return urlObj; } module.exports = { from: parseFromUrl, to: parseUrl }; lib/parse/host.js000066600000000556150514456310007755 0ustar00"use strict"; function parseHost(urlObj, options) { // TWEAK :: condition only for speed optimization if (options.ignore_www) { var host = urlObj.host.full; if (host) { var stripped = host; if (host.indexOf("www.") === 0) { stripped = host.substr(4); } urlObj.host.stripped = stripped; } } } module.exports = parseHost; lib/parse/path.js000066600000003142150514456310007726 0ustar00"use strict"; function isDirectoryIndex(resource, options) { var verdict = false; options.directoryIndexes.every( function(index) { if (index === resource) { verdict = true; return false; } return true; }); return verdict; } function parsePath(urlObj, options) { var path = urlObj.path.absolute.string; if (path) { var lastSlash = path.lastIndexOf("/"); if (lastSlash > -1) { if (++lastSlash < path.length) { var resource = path.substr(lastSlash); if (resource!=="." && resource!=="..") { urlObj.resource = resource; path = path.substr(0, lastSlash); } else { path += "/"; } } urlObj.path.absolute.string = path; urlObj.path.absolute.array = splitPath(path); } else if (path==="." || path==="..") { // "..?var", "..#anchor", etc ... not "..index.html" path += "/"; urlObj.path.absolute.string = path; urlObj.path.absolute.array = splitPath(path); } else { // Resource-only urlObj.resource = path; urlObj.path.absolute.string = null; } urlObj.extra.resourceIsIndex = isDirectoryIndex(urlObj.resource, options); } // Else: query/hash-only or empty } function splitPath(path) { // TWEAK :: condition only for speed optimization if (path !== "/") { var cleaned = []; path.split("/").forEach( function(dir) { // Cleanup -- splitting "/dir/" becomes ["","dir",""] if (dir !== "") { cleaned.push(dir); } }); return cleaned; } else { // Faster to skip the above block and just create an array return []; } } module.exports = parsePath; lib/parse/hrefInfo.js000066600000001367150514456310010541 0ustar00"use strict"; function hrefInfo(urlObj) { var minimumPathOnly = (!urlObj.scheme && !urlObj.auth && !urlObj.host.full && !urlObj.port); var minimumResourceOnly = (minimumPathOnly && !urlObj.path.absolute.string); var minimumQueryOnly = (minimumResourceOnly && !urlObj.resource); var minimumHashOnly = (minimumQueryOnly && !urlObj.query.string.full.length); var empty = (minimumHashOnly && !urlObj.hash); urlObj.extra.hrefInfo.minimumPathOnly = minimumPathOnly; urlObj.extra.hrefInfo.minimumResourceOnly = minimumResourceOnly; urlObj.extra.hrefInfo.minimumQueryOnly = minimumQueryOnly; urlObj.extra.hrefInfo.minimumHashOnly = minimumHashOnly; urlObj.extra.hrefInfo.empty = empty; } module.exports = hrefInfo; lib/parse/port.js000066600000001014150514456310007752 0ustar00"use strict"; function parsePort(urlObj, options) { var defaultPort = -1; for (var i in options.defaultPorts) { if ( i===urlObj.scheme && options.defaultPorts.hasOwnProperty(i) ) { defaultPort = options.defaultPorts[i]; break; } } if (defaultPort > -1) { // Force same type as urlObj.port defaultPort = defaultPort.toString(); if (urlObj.port === null) { urlObj.port = defaultPort; } urlObj.extra.portIsDefault = (urlObj.port === defaultPort); } } module.exports = parsePort; lib/parse/urlstring.js000066600000004474150514456310011034 0ustar00"use strict"; var _parseUrl = require("url").parse; /* Customize the URL object that Node generates because: * necessary data for later * urlObj.host is useless * urlObj.hostname is too long * urlObj.path is useless * urlObj.pathname is too long * urlObj.protocol is inaccurate; should be called "scheme" * urlObj.search is mostly useless */ function clean(urlObj) { var scheme = urlObj.protocol; if (scheme) { // Remove ":" suffix if (scheme.indexOf(":") === scheme.length-1) { scheme = scheme.substr(0, scheme.length-1); } } urlObj.host = { // TODO :: unescape(encodeURIComponent(s)) ? ... http://ecmanaut.blogspot.ca/2006/07/encoding-decoding-utf8-in-javascript.html full: urlObj.hostname, stripped: null }; urlObj.path = { absolute: { array: null, string: urlObj.pathname }, relative: { array: null, string: null } }; urlObj.query = { object: urlObj.query, string: { full: null, stripped: null } }; urlObj.extra = { hrefInfo: { minimumPathOnly: null, minimumResourceOnly: null, minimumQueryOnly: null, minimumHashOnly: null, empty: null, separatorOnlyQuery: urlObj.search==="?" }, portIsDefault: null, relation: { maximumScheme: null, maximumAuth: null, maximumHost: null, maximumPort: null, maximumPath: null, maximumResource: null, maximumQuery: null, maximumHash: null, minimumScheme: null, minimumAuth: null, minimumHost: null, minimumPort: null, minimumPath: null, minimumResource: null, minimumQuery: null, minimumHash: null, overridesQuery: null }, resourceIsIndex: null, slashes: urlObj.slashes }; urlObj.resource = null; urlObj.scheme = scheme; delete urlObj.hostname; delete urlObj.pathname; delete urlObj.protocol; delete urlObj.search; delete urlObj.slashes; return urlObj; } function validScheme(url, options) { var valid = true; options.rejectedSchemes.every( function(rejectedScheme) { valid = !(url.indexOf(rejectedScheme+":") === 0); // Break loop return valid; }); return valid; } function parseUrlString(url, options) { if ( validScheme(url,options) ) { return clean( _parseUrl(url, true, options.slashesDenoteHost) ); } else { return {href:url, valid:false}; } } module.exports = parseUrlString; lib/parse/query.js000066600000001556150514456310010146 0ustar00"use strict"; var hasOwnProperty = Object.prototype.hasOwnProperty; function parseQuery(urlObj, options) { urlObj.query.string.full = stringify(urlObj.query.object, false); // TWEAK :: condition only for speed optimization if (options.removeEmptyQueries) { urlObj.query.string.stripped = stringify(urlObj.query.object, true); } } function stringify(queryObj, removeEmptyQueries) { var count = 0; var str = ""; for (var i in queryObj) { if ( i!=="" && hasOwnProperty.call(queryObj, i)===true ) { var value = queryObj[i]; if (value !== "" || !removeEmptyQueries) { str += (++count===1) ? "?" : "&"; i = encodeURIComponent(i); if (value !== "") { str += i +"="+ encodeURIComponent(value).replace(/%20/g,"+"); } else { str += i; } } } } return str; } module.exports = parseQuery; package.json000066600000002030150514456310007035 0ustar00{ "name": "relateurl", "description": "Minify URLs by converting them from absolute to relative.", "version": "0.2.7", "license": "MIT", "homepage": "https://github.com/stevenvachon/relateurl", "author": { "name": "Steven Vachon", "email": "contact@svachon.com", "url": "http://www.svachon.com/" }, "main": "lib", "repository": { "type": "git", "url": "git://github.com/stevenvachon/relateurl.git" }, "bugs": { "url": "https://github.com/stevenvachon/relateurl/issues" }, "devDependencies": { "browserify": "^13.0.1", "chai": "^3.5.0", "mocha": "^2.5.3", "uglify-js": "^2.7.0" }, "engines": { "node": ">= 0.10" }, "scripts": { "browserify": "browserify lib/ --standalone RelateUrl | uglifyjs --compress --mangle -o relateurl-browser.js", "test": "mocha test/ --bail --reporter spec --check-leaks" }, "files": [ "lib", "license" ], "keywords": [ "uri", "url", "minifier", "minify", "lint", "relative", "absolute" ] }