pax_global_header00006660000000000000000000000064131766221450014521gustar00rootroot0000000000000052 comment=45e05571478e3901e30716f3ee5fcfb83fe5b5aa url-parse-1.2.0/000077500000000000000000000000001317662214500134335ustar00rootroot00000000000000url-parse-1.2.0/.gitignore000066400000000000000000000001041317662214500154160ustar00rootroot00000000000000node_modules/ .nyc_output/ coverage/ dist/ npm-debug.log .tern-port url-parse-1.2.0/.travis.yml000066400000000000000000000013511317662214500155440ustar00rootroot00000000000000sudo: false language: node_js matrix: fast_finish: true include: - node_js: "8" env: SCRIPT=test - node_js: "6" env: SCRIPT=test - node_js: "4" env: SCRIPT=test - node_js: "8" env: - secure: IF01oyIKSs0C5dARdYRTilKnU1TG4zenjjEPClkQxAWIpUOxl9xcNJWDVEOPxJ/4pVt+pozyT80Rp7efh6ZiREJIQI1tUboBKSqZzSbnD5uViQNSbQ90PaDP0FIUc0IQ5o07W36rijBB0DTmtU1VofzN9PKkJO7XiSSXevI8RcM= - SAUCE_USERNAME=url-parse - SCRIPT=test-browser script: - "npm run ${SCRIPT}" after_script: - 'if [ "${SCRIPT}" == "test" ]; then npm i coveralls@3 && cat coverage/lcov.info | coveralls; fi' notifications: irc: channels: - "irc.freenode.org#unshift" on_success: change on_failure: change url-parse-1.2.0/LICENSE000066400000000000000000000021331317662214500144370ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors. 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. url-parse-1.2.0/README.md000066400000000000000000000140771317662214500147230ustar00rootroot00000000000000# url-parse [![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](https://img.shields.io/npm/v/url-parse.svg?style=flat-square)](https://www.npmjs.com/package/url-parse)[![Build Status](https://img.shields.io/travis/unshiftio/url-parse/master.svg?style=flat-square)](https://travis-ci.org/unshiftio/url-parse)[![Dependencies](https://img.shields.io/david/unshiftio/url-parse.svg?style=flat-square)](https://david-dm.org/unshiftio/url-parse)[![Coverage Status](https://img.shields.io/coveralls/unshiftio/url-parse/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/url-parse?branch=master)[![IRC channel](https://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](https://webchat.freenode.net/?channels=unshift) [![Sauce Test Status](https://saucelabs.com/browser-matrix/url-parse.svg)](https://saucelabs.com/u/url-parse) The `url-parse` method exposes two different API interfaces. The [`url`](https://nodejs.org/api/url.html) interface that you know from Node.js and the new [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) interface that is available in the latest browsers. In version `0.1` we moved from a DOM based parsing solution, using the `` element, to a full Regular Expression solution. The main reason for this was to make the URL parser available in different JavaScript environments as you don't always have access to the DOM. An example of such environment is the [`Worker`](https://developer.mozilla.org/en/docs/Web/API/Worker) interface. The RegExp based solution didn't work well as it required a lot of lookups causing major problems in FireFox. In version `1.0.0` we ditched the RegExp based solution in favor of a pure string parsing solution which chops up the URL into smaller pieces. This module still has a really small footprint as it has been designed to be used on the client side. In addition to URL parsing we also expose the bundled `querystringify` module. ## Installation This module is designed to be used using either browserify or Node.js it's released in the public npm registry and can be installed using: ``` npm install url-parse ``` ## Usage All examples assume that this library is bootstrapped using: ```js 'use strict'; var URL = require('url-parse'); ``` To parse an URL simply call the `URL` method with the URL that needs to be transformed into an object. ```js var url = new URL('https://github.com/foo/bar'); ``` The `new` keyword is optional but it will save you an extra function invocation. The constructor takes the following arguments: - `url` (`String`): A string representing an absolute or relative URL. - `baseURL` (`Object` | `String`): An object or string representing the base URL to use in case `url` is a relative URL. This argument is optional and defaults to [`location`](https://developer.mozilla.org/en-US/docs/Web/API/Location) in the browser. - `parser` (`Boolean` | `Function`): This argument is optional and specifies how to parse the query string. By default it is `false` so the query string is not parsed. If you pass `true` the query string is parsed using the embedded `querystringify` module. If you pass a function the query string will be parsed using this function. As said above we also support the Node.js interface so you can also use the library in this way: ```js 'use strict'; var parse = require('url-parse') , url = parse('https://github.com/foo/bar', true); ``` The returned `url` instance contains the following properties: - `protocol`: The protocol scheme of the URL (e.g. `http:`). - `slashes`: A boolean which indicates whether the `protocol` is followed by two forward slashes (`//`). - `auth`: Authentication information portion (e.g. `username:password`). - `username`: Username of basic authentication. - `password`: Password of basic authentication. - `host`: Host name with port number. - `hostname`: Host name without port number. - `port`: Optional port number. - `pathname`: URL path. - `query`: Parsed object containing query string, unless parsing is set to false. - `hash`: The "fragment" portion of the URL including the pound-sign (`#`). - `href`: The full URL. - `origin`: The origin of the URL. Note that when `url-parse` is used in a browser environment, it will default to using the browser's current window location as the base URL when parsing all inputs. To parse an input independently of the browser's current URL (e.g. for functionality parity with the library in a Node environment), pass an empty location object as the second parameter: ```js var parse = require('url-parse'); parse('hostname', {}); ``` ### URL.set(key, value) A simple helper function to change parts of the URL and propagating it through all properties. When you set a new `host` you want the same value to be applied to `port` if has a different port number, `hostname` so it has a correct name again and `href` so you have a complete URL. ```js var parsed = parse('http://google.com/parse-things'); parsed.set('hostname', 'yahoo.com'); console.log(parsed.href); // http://yahoo.com/parse-things ``` It's aware of default ports so you cannot set a port 80 on an URL which has `http` as protocol. ### URL.toString() The returned `url` object comes with a custom `toString` method which will generate a full URL again when called. The method accepts an extra function which will stringify the query string for you. If you don't supply a function we will use our default method. ```js var location = url.toString(); // http://example.com/whatever/?qs=32 ``` You would rarely need to use this method as the full URL is also available as `href` property. If you are using the `URL.set` method to make changes, this will automatically update. ## Testing The testing of this module is done in 3 different ways: 1. We have unit tests that run under Node.js. You can run these tests with the `npm test` command. 2. Code coverage can be run manually using `npm run coverage`. 3. For browser testing we use Sauce Labs and `zuul`. You can run browser tests using the `npm run test-browser` command. ## License [MIT](LICENSE) url-parse-1.2.0/index.js000066400000000000000000000263751317662214500151150ustar00rootroot00000000000000'use strict'; var required = require('requires-port') , qs = require('querystringify') , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\S\s]*)/i , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//; /** * These are the parse rules for the URL parser, it informs the parser * about: * * 0. The char it Needs to parse, if it's a string it should be done using * indexOf, RegExp using exec and NaN means set as current value. * 1. The property we should set when parsing this value. * 2. Indication if it's backwards or forward parsing, when set as number it's * the value of extra chars that should be split off. * 3. Inherit from location if non existing in the parser. * 4. `toLowerCase` the resulting value. */ var rules = [ ['#', 'hash'], // Extract from the back. ['?', 'query'], // Extract from the back. ['/', 'pathname'], // Extract from the back. ['@', 'auth', 1], // Extract from the front. [NaN, 'host', undefined, 1, 1], // Set left over value. [/:(\d+)$/, 'port', undefined, 1], // RegExp the back. [NaN, 'hostname', undefined, 1, 1] // Set left over. ]; /** * These properties should not be copied or inherited from. This is only needed * for all non blob URL's as a blob URL does not include a hash, only the * origin. * * @type {Object} * @private */ var ignore = { hash: 1, query: 1 }; /** * The location object differs when your code is loaded through a normal page, * Worker or through a worker using a blob. And with the blobble begins the * trouble as the location object will contain the URL of the blob, not the * location of the page where our code is loaded in. The actual origin is * encoded in the `pathname` so we can thankfully generate a good "default" * location from it so we can generate proper relative URL's again. * * @param {Object|String} loc Optional default location object. * @returns {Object} lolcation object. * @api public */ function lolcation(loc) { loc = loc || global.location || {}; var finaldestination = {} , type = typeof loc , key; if ('blob:' === loc.protocol) { finaldestination = new URL(unescape(loc.pathname), {}); } else if ('string' === type) { finaldestination = new URL(loc, {}); for (key in ignore) delete finaldestination[key]; } else if ('object' === type) { for (key in loc) { if (key in ignore) continue; finaldestination[key] = loc[key]; } if (finaldestination.slashes === undefined) { finaldestination.slashes = slashes.test(loc.href); } } return finaldestination; } /** * @typedef ProtocolExtract * @type Object * @property {String} protocol Protocol matched in the URL, in lowercase. * @property {Boolean} slashes `true` if protocol is followed by "//", else `false`. * @property {String} rest Rest of the URL that is not part of the protocol. */ /** * Extract protocol information from a URL with/without double slash ("//"). * * @param {String} address URL we want to extract from. * @return {ProtocolExtract} Extracted information. * @api private */ function extractProtocol(address) { var match = protocolre.exec(address); return { protocol: match[1] ? match[1].toLowerCase() : '', slashes: !!match[2], rest: match[3] }; } /** * Resolve a relative URL pathname against a base URL pathname. * * @param {String} relative Pathname of the relative URL. * @param {String} base Pathname of the base URL. * @return {String} Resolved pathname. * @api private */ function resolve(relative, base) { var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) , i = path.length , last = path[i - 1] , unshift = false , up = 0; while (i--) { if (path[i] === '.') { path.splice(i, 1); } else if (path[i] === '..') { path.splice(i, 1); up++; } else if (up) { if (i === 0) unshift = true; path.splice(i, 1); up--; } } if (unshift) path.unshift(''); if (last === '.' || last === '..') path.push(''); return path.join('/'); } /** * The actual URL instance. Instead of returning an object we've opted-in to * create an actual constructor as it's much more memory efficient and * faster and it pleases my OCD. * * @constructor * @param {String} address URL we want to parse. * @param {Object|String} location Location defaults for relative paths. * @param {Boolean|Function} parser Parser for the query string. * @api public */ function URL(address, location, parser) { if (!(this instanceof URL)) { return new URL(address, location, parser); } var relative, extracted, parse, instruction, index, key , instructions = rules.slice() , type = typeof location , url = this , i = 0; // // The following if statements allows this module two have compatibility with // 2 different API: // // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments // where the boolean indicates that the query string should also be parsed. // // 2. The `URL` interface of the browser which accepts a URL, object as // arguments. The supplied object will be used as default values / fall-back // for relative paths. // if ('object' !== type && 'string' !== type) { parser = location; location = null; } if (parser && 'function' !== typeof parser) parser = qs.parse; location = lolcation(location); // // Extract protocol information before running the instructions. // extracted = extractProtocol(address || ''); relative = !extracted.protocol && !extracted.slashes; url.slashes = extracted.slashes || relative && location.slashes; url.protocol = extracted.protocol || location.protocol || ''; address = extracted.rest; // // When the authority component is absent the URL starts with a path // component. // if (!extracted.slashes) instructions[2] = [/(.*)/, 'pathname']; for (; i < instructions.length; i++) { instruction = instructions[i]; parse = instruction[0]; key = instruction[1]; if (parse !== parse) { url[key] = address; } else if ('string' === typeof parse) { if (~(index = address.indexOf(parse))) { if ('number' === typeof instruction[2]) { url[key] = address.slice(0, index); address = address.slice(index + instruction[2]); } else { url[key] = address.slice(index); address = address.slice(0, index); } } } else if ((index = parse.exec(address))) { url[key] = index[1]; address = address.slice(0, index.index); } url[key] = url[key] || ( relative && instruction[3] ? location[key] || '' : '' ); // // Hostname, host and protocol should be lowercased so they can be used to // create a proper `origin`. // if (instruction[4]) url[key] = url[key].toLowerCase(); } // // Also parse the supplied query string in to an object. If we're supplied // with a custom parser as function use that instead of the default build-in // parser. // if (parser) url.query = parser(url.query); // // If the URL is relative, resolve the pathname against the base URL. // if ( relative && location.slashes && url.pathname.charAt(0) !== '/' && (url.pathname !== '' || location.pathname !== '') ) { url.pathname = resolve(url.pathname, location.pathname); } // // We should not add port numbers if they are already the default port number // for a given protocol. As the host also contains the port number we're going // override it with the hostname which contains no port number. // if (!required(url.port, url.protocol)) { url.host = url.hostname; url.port = ''; } // // Parse down the `auth` for the username and password. // url.username = url.password = ''; if (url.auth) { instruction = url.auth.split(':'); url.username = instruction[0] || ''; url.password = instruction[1] || ''; } url.origin = url.protocol && url.host && url.protocol !== 'file:' ? url.protocol +'//'+ url.host : 'null'; // // The href is just the compiled result. // url.href = url.toString(); } /** * This is convenience method for changing properties in the URL instance to * insure that they all propagate correctly. * * @param {String} part Property we need to adjust. * @param {Mixed} value The newly assigned value. * @param {Boolean|Function} fn When setting the query, it will be the function * used to parse the query. * When setting the protocol, double slash will be * removed from the final url if it is true. * @returns {URL} * @api public */ function set(part, value, fn) { var url = this; switch (part) { case 'query': if ('string' === typeof value && value.length) { value = (fn || qs.parse)(value); } url[part] = value; break; case 'port': url[part] = value; if (!required(value, url.protocol)) { url.host = url.hostname; url[part] = ''; } else if (value) { url.host = url.hostname +':'+ value; } break; case 'hostname': url[part] = value; if (url.port) value += ':'+ url.port; url.host = value; break; case 'host': url[part] = value; if (/:\d+$/.test(value)) { value = value.split(':'); url.port = value.pop(); url.hostname = value.join(':'); } else { url.hostname = value; url.port = ''; } break; case 'protocol': url.protocol = value.toLowerCase(); url.slashes = !fn; break; case 'pathname': case 'hash': if (value) { var char = part === 'pathname' ? '/' : '#'; url[part] = value.charAt(0) !== char ? char + value : value; } else { url[part] = value; } break; default: url[part] = value; } for (var i = 0; i < rules.length; i++) { var ins = rules[i]; if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); } url.origin = url.protocol && url.host && url.protocol !== 'file:' ? url.protocol +'//'+ url.host : 'null'; url.href = url.toString(); return url; } /** * Transform the properties back in to a valid and full URL string. * * @param {Function} stringify Optional query stringify function. * @returns {String} * @api public */ function toString(stringify) { if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; var query , url = this , protocol = url.protocol; if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; var result = protocol + (url.slashes ? '//' : ''); if (url.username) { result += url.username; if (url.password) result += ':'+ url.password; result += '@'; } result += url.host + url.pathname; query = 'object' === typeof url.query ? stringify(url.query) : url.query; if (query) result += '?' !== query.charAt(0) ? '?'+ query : query; if (url.hash) result += url.hash; return result; } URL.prototype = { set: set, toString: toString }; // // Expose the URL parser and some additional properties that might be useful for // others or testing. // URL.extractProtocol = extractProtocol; URL.location = lolcation; URL.qs = qs; module.exports = URL; url-parse-1.2.0/package.json000066400000000000000000000022251317662214500157220ustar00rootroot00000000000000{ "name": "url-parse", "version": "1.2.0", "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments", "main": "index.js", "scripts": { "browserify": "mkdir -p dist && browserify index.js -s URLParse | uglifyjs -cm -o dist/url-parse.min.js", "test": "nyc --reporter=html --reporter=text mocha test/test.js", "test-browser": "node test/browser.js", "prepublishOnly": "npm run browserify", "watch": "mocha --watch test/test.js" }, "files": [ "index.js", "dist" ], "repository": { "type": "git", "url": "https://github.com/unshiftio/url-parse.git" }, "keywords": [ "URL", "parser", "uri", "url", "parse", "query", "string", "querystring", "stringify" ], "author": "Arnout Kazemier", "license": "MIT", "dependencies": { "querystringify": "~1.0.0", "requires-port": "~1.0.0" }, "devDependencies": { "assume": "~1.5.0", "browserify": "~14.5.0", "mocha": "~4.0.0", "nyc": "~11.3.0", "pre-commit": "~1.2.0", "sauce-browsers": "~1.0.0", "sauce-test": "~1.3.3", "uglify-js": "~3.1.0" } } url-parse-1.2.0/test/000077500000000000000000000000001317662214500144125ustar00rootroot00000000000000url-parse-1.2.0/test/browser.js000066400000000000000000000022051317662214500164320ustar00rootroot00000000000000'use strict'; const sauceBrowsers = require('sauce-browsers'); const run = require('sauce-test'); const path = require('path'); const pkg = require('../package'); const platforms = sauceBrowsers([ { name: 'android', version: ['oldest', 'latest'] }, { name: 'chrome', version: ['oldest', 'latest'] }, { name: 'firefox', version: ['oldest', 'latest'] }, { name: 'internet explorer', version: 'oldest..latest' }, { name: 'iphone', version: ['oldest', 'latest'] }, { name: 'opera', version: 'oldest..latest' }, { name: 'safari', version: 'oldest..latest' }, { name: 'microsoftedge', version: 'oldest..latest' } ]).then((platforms) => { return platforms.map((platform) => { return { browserName: platform.api_name, version: platform.short_version, platform: platform.os }; }); }); run(path.join(__dirname, 'test.js'), 'saucelabs', { html: path.join(__dirname, 'index.html'), accessKey: process.env.SAUCE_ACCESS_KEY, username: process.env.SAUCE_USERNAME, browserify: true, disableSSL: true, name: pkg.name, parallel: 5, platforms }).done((results) => { if (!results.passed) process.exit(1); }); url-parse-1.2.0/test/fuzzy.js000066400000000000000000000046751317662214500161530ustar00rootroot00000000000000'use strict'; var URL = require('../') , url = new URL('//'); /** * A dictionary with all kind of different options that should generate a valid * and parse-able URL. * * @type {Object} * @api private */ var combinations = {}; combinations.protocol = [ 'http:', 'https:', 'ws:', 'wss:'/*, 'blob:', ''*/ ]; combinations.username = ['foo', 'bar']; combinations.password = combinations.username; combinations.hostname = [ 'example.com', 'www.example.com', 'travel.travel', 'sub.sub.sub.domain.nl', 'xn--n3h.com', 'localhost', '127.0.0.1', '255.255.255.255', '[3ffe:6a88:85a3:08d3:1319:8a2e:0370:7344]', '[2001:2353::1428:57ab]', '[2001:2353:0::0:1428:57ab]', '[2001:2353:0:0:0:0:1428:57ab]', '[2001:2353:0000:0000:0000::1428:57ab]', '[2001:2353:0000:0000:0000:0000:1428:57ab]', '[2001:2353:02de::0e13]', '[2001:2353:2de::e13]', '[::2]', '[1::]' ]; combinations.port = ['8080', '844', '3340']; combinations.pathname = [ '/', '/bar', '/bar/', '/foo/bar', '/foo.bar/foo', '/fav.ico', '/@3rd-Eden', '/a/b/c/d/e/f/g/j/1/d/4/' ]; combinations.query = [ 'foo[]=bar&foo[]=foo', 'email=foo@bar.travel', 'foo=bar', 'q=' ]; combinations.hash = [ 'name', 'moo-with-longer-name', '/what/about/slashes?querystring', '?querystring', '!/google/urls', 'use:foo@', 'http://' ]; /** * Get a random item from the given array. * * @param {String} name Name of the array we want to have a random item returned. * @returns {Mixed} * @api private */ function get(name) { var data = combinations[name]; return data[Math.floor(Math.random() * data.length)]; } /** * Return a random boolean. * * @returns {Boolean} * @api private */ function yep() { return !!Math.round(Math.random() * 1); } /** * Generate the actual URL. * * @returns {Object} specification * @api public */ module.exports = function generate() { var spec = {} , key; spec.protocol = get('protocol'); spec.hostname = get('hostname'); spec.pathname = get('pathname'); if (yep()) spec.port = get('port'); if (yep()) spec.query = '?'+ get('query'); if (yep()) spec.hash = '#'+ get('hash'); if (yep()) { spec.username = get('username'); spec.password = get('password'); } spec.host = spec.port ? spec.hostname + ':' + spec.port : spec.hostname; for (key in combinations) { url[key] = ''; } for (key in spec) { url[key] = spec[key]; } spec.href = url.toString(); return spec; }; url-parse-1.2.0/test/index.html000066400000000000000000000013711317662214500164110ustar00rootroot00000000000000
{{scripts}} url-parse-1.2.0/test/test.js000066400000000000000000000601741317662214500157370ustar00rootroot00000000000000describe('url-parse', function () { 'use strict'; var assume = require('assume') , parse = require('../'); it('exposes parse as a function', function () { assume(parse).is.a('function'); }); it('exposes the querystring module', function () { assume(parse.qs).equals(require('querystringify')); }); it('exposes the location function', function () { assume(parse.location).is.a('function'); }); it('exposes the extractProtocol function', function () { assume(parse.extractProtocol).is.a('function'); }); it('defaults to empty address to return valid URL instance', function () { var url = parse(); assume(url).to.be.an('object'); assume(url.pathname).to.be.a('string'); assume(url.host).to.be.a('string'); assume(url.hostname).to.be.a('string'); }); describe('extractProtocol', function () { it('extracts the protocol data', function () { assume(parse.extractProtocol('')).eql({ slashes: false, protocol: '', rest: '' }); }); it('does not truncate the input string', function () { var input = 'foo\nbar\rbaz\u2028qux\u2029'; assume(parse.extractProtocol(input)).eql({ slashes: false, protocol: '', rest: input }); }); }); it('parses the query string into an object', function () { var url = 'http://google.com/?foo=bar' , data = parse(url, true); assume(data.query).is.a('object'); assume(data.query.foo).equals('bar'); url = 'http://google.com/'; data = parse(url, true); assume(data.query).is.a('object'); assume(data.query).is.empty(); }); it('does not add question mark to href if query string is empty', function () { var url = 'http://google.com/' , data = parse(url, true); assume(data.href).equals(url); }); it('allows a custom function as parser', function () { var url = 'http://google.com/?foo=bar' , data = parse(url, function () { return '1337'; }); assume(data.query).equals('1337'); }); it('allows a custom stringify function', function () { var url = 'http://google.com/?foo=bar' , data = parse(url, true) , str; str = data.toString(function () { return 'lolcakes'; }); assume(str).equals('http://google.com/?lolcakes'); }); it('allows a custom location object', function () { var url = '/foo?foo=bar' , data = parse(url, parse('http://google.com')); assume(data.href).equals('http://google.com/foo?foo=bar'); }); it('is blob: location aware', function () { var blob = { 'href': 'blob:https%3A//gist.github.com/3f272586-6dac-4e29-92d0-f674f2dde618', 'pathname': 'https%3A//gist.github.com/3f272586-6dac-4e29-92d0-f674f2dde618', 'origin': 'https://gist.github.com', 'protocol': 'blob:', 'hostname': '', 'search': '', 'hash': '', 'host': '', 'port': '' }; var url = '/unshiftio/url-parse' , data = parse(url, blob); assume(data.href).equals('https://gist.github.com/unshiftio/url-parse'); }); it('can parse complex urls multiple times without errors', function () { var url = 'https://www.mozilla.org/en-US/firefox/34.0/whatsnew/?oldversion=33.1'; for (var i = 0; i < 100; i++) { parse(url); } }); it('converts hostname to lowercase', function () { var url = 'HTTP://fOo.eXaMPle.com'; assume(parse(url).hostname).equals('foo.example.com'); }); it('does not lowercase the path', function () { var url = 'HTTP://X.COM/Y/Z'; assume(parse(url).pathname).equals('/Y/Z'); }); it('removes default port numbers', function () { var url = 'http://example.com:80' , parsed = parse(url); assume(parsed.port).equals(''); assume(parsed.host).equals('example.com'); assume(parsed.hostname).equals('example.com'); assume(parsed.href).equals('http://example.com'); }); it('understands an / as pathname', function () { var url = 'http://example.com:80/' , parsed = parse(url); assume(parsed.port).equals(''); assume(parsed.username).equals(''); assume(parsed.password).equals(''); assume(parsed.pathname).equals('/'); assume(parsed.host).equals('example.com'); assume(parsed.hostname).equals('example.com'); assume(parsed.href).equals('http://example.com/'); }); it('does not care about spaces', function () { var url = 'http://x.com/path?that\'s#all, folks' , parsed = parse(url); assume(parsed.port).equals(''); assume(parsed.username).equals(''); assume(parsed.password).equals(''); assume(parsed.pathname).equals('/path'); assume(parsed.hash).equal('#all, folks'); assume(parsed.query).equal('?that\'s'); assume(parsed.host).equals('x.com'); assume(parsed.hostname).equals('x.com'); }); it('accepts + in the url', function () { var url = 'http://x.y.com+a/b/c' , parsed = parse(url); assume(parsed.protocol).equals('http:'); assume(parsed.host).equals('x.y.com+a'); assume(parsed.hostname).equals('x.y.com+a'); assume(parsed.pathname).equals('/b/c'); }); describe('origin', function () { it('generates an origin property', function () { var url = 'http://google.com:80/pathname' , parsed = parse(url); assume(parsed.origin).equals('http://google.com'); }); it('is lowercased', function () { var url = 'HTTP://gOogle.cOm:80/pathname' , parsed = parse(url); assume(parsed.origin).equals('http://google.com'); }); it('sets null if no hostname is specified', function () { var url = 'http://' , parsed = parse(url, {}); assume(parsed.origin).equals('null'); }); it('removes default ports for http', function () { var o = parse('http://google.com:80/pathname'); assume(o.origin).equals('http://google.com'); o = parse('http://google.com:80'); assume(o.origin).equals('http://google.com'); o = parse('http://google.com'); assume(o.origin).equals('http://google.com'); o = parse('https://google.com:443/pathname'); assume(o.origin).equals('https://google.com'); o = parse('http://google.com:443/pathname'); assume(o.origin).equals('http://google.com:443'); o = parse('https://google.com:80/pathname'); assume(o.origin).equals('https://google.com:80'); }); it('handles file:// based urls as null', function () { var o = parse('file://google.com/pathname'); assume(o.origin).equals('null'); }); it('removes default ports for ws', function () { var o = parse('ws://google.com:80/pathname'); assume(o.origin).equals('ws://google.com'); o = parse('wss://google.com:443/pathname'); assume(o.origin).equals('wss://google.com'); o = parse('ws://google.com:443/pathname'); assume(o.origin).equals('ws://google.com:443'); o = parse('wss://google.com:80/pathname'); assume(o.origin).equals('wss://google.com:80'); }); }); describe('protocol', function () { it('extracts the right protocol from a url', function () { var testData = [ { href: 'http://example.com', protocol: 'http:', pathname: '' }, { href: 'mailto:test@example.com', pathname: 'test@example.com', protocol: 'mailto:' }, { href: 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', pathname: 'text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', protocol: 'data:' }, { href: 'sip:alice@atlanta.com', pathname: 'alice@atlanta.com', protocol: 'sip:' } ]; var data; for (var i = 0, len = testData.length; i < len; ++i) { data = parse(testData[i].href); assume(data.protocol).equals(testData[i].protocol); assume(data.pathname).equals(testData[i].pathname); } }); it('converts protocol to lowercase', function () { var url = 'HTTP://example.com'; assume(parse(url).protocol).equals('http:'); }); it('correctly adds ":" to protocol in final url string', function () { var data = parse('google.com/foo', {}); data.set('protocol', 'https'); assume(data.href).equals('https://google.com/foo'); data = parse('https://google.com/foo'); data.protocol = 'http'; assume(data.toString()).equals('http://google.com/foo'); data = parse('http://google.com/foo'); data.set('protocol', 'https:'); assume(data.href).equals('https://google.com/foo'); }); }); describe('ip', function () { it('parses ipv6', function () { var url = 'http://[1080:0:0:0:8:800:200C:417A]:61616/foo/bar?q=z' , parsed = parse(url); assume(parsed.port).equals('61616'); assume(parsed.query).equals('?q=z'); assume(parsed.protocol).equals('http:'); assume(parsed.hostname).equals('[1080:0:0:0:8:800:200c:417a]'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.href).equals('http://[1080:0:0:0:8:800:200c:417a]:61616/foo/bar?q=z'); }); it('parses ipv6 with auth', function () { var url = 'http://user:password@[3ffe:2a00:100:7031::1]:8080' , parsed = parse(url); assume(parsed.username).equals('user'); assume(parsed.password).equals('password'); assume(parsed.host).equals('[3ffe:2a00:100:7031::1]:8080'); assume(parsed.hostname).equals('[3ffe:2a00:100:7031::1]'); assume(parsed.href).equals(url); }); it('parses ipv4', function () { var url = 'http://222.148.142.13:61616/foo/bar?q=z' , parsed = parse(url); assume(parsed.port).equals('61616'); assume(parsed.query).equals('?q=z'); assume(parsed.protocol).equals('http:'); assume(parsed.hostname).equals('222.148.142.13'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.href).equals(url); }); }); describe('auth', function () { it('does not lowercase the USER:PASS', function () { var url = 'HTTP://USER:PASS@EXAMPLE.COM' , parsed = parse(url); assume(parsed.username).equals('USER'); assume(parsed.password).equals('PASS'); assume(parsed.protocol).equals('http:'); assume(parsed.host).equals('example.com'); assume(parsed.hostname).equals('example.com'); }); it('accepts @ in pathnames', function () { var url = 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=' , parsed = parse(url); assume(parsed.pathname).equals('/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s='); assume(parsed.username).equals(''); assume(parsed.password).equals(''); }); it('does not require passwords for auth', function () { var url = 'http://user@www.example.com/' , parsed = parse(url); assume(parsed.password).equals(''); assume(parsed.pathname).equals('/'); assume(parsed.username).equals('user'); assume(parsed.protocol).equals('http:'); assume(parsed.hostname).equals('www.example.com'); assume(parsed.href).equals(url); }); }); it('accepts multiple ???', function () { var url = 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s='; assume(parse(url).query).equals('???&hl=en&src=api&x=2&y=2&z=3&s='); }); it('accepts a string as source argument', function () { var data = parse('/foo', 'http://sub.example.com/bar?foo=bar#hash'); assume(data.port).equals(''); assume(data.host).equals('sub.example.com'); assume(data.href).equals('http://sub.example.com/foo'); }); describe('inheritance', function () { it('does not inherit port numbers for non relative urls', function () { var data = parse('http://localhost', parse('http://sub.example.com:808/')); assume(data.port).equals(''); assume(data.host).equals('localhost'); assume(data.href).equals('http://localhost'); }); it('inherits port numbers for relative urls', function () { var data = parse('/foo', parse('http://sub.example.com:808/')); assume(data.port).equals('808'); assume(data.hostname).equals('sub.example.com'); assume(data.host).equals('sub.example.com:808'); assume(data.href).equals('http://sub.example.com:808/foo'); }); it('inherits slashes for relative urls', function () { var data = parse('/foo', { hash: '', host: 'example.com', hostname: 'example.com', href: 'http://example.com/', origin: 'http://example.com', password: '', pathname: '/', port: '', protocol: 'http:', search: '' }); assume(data.slashes).equals(true); assume(data.href).equals('http://example.com/foo'); data = parse('/foo', { auth: null, hash: null, host: 'example.com', hostname: 'example.com', href: 'http://example.com/', path: '/', pathname: '/', port: null, protocol: 'http:', query: null, search: null, slashes: true }); assume(data.slashes).equals(true); assume(data.href).equals('http://example.com/foo'); }); it('inherits protocol for relative protocols', function () { var data = parse('//foo.com/foo', parse('http://sub.example.com:808/')); assume(data.port).equals(''); assume(data.host).equals('foo.com'); assume(data.protocol).equals('http:'); assume(data.href).equals('http://foo.com/foo'); }); it('does not inherit pathname for non relative urls', function () { var data = parse('http://localhost', parse('http://foo:bar@sub.example.com/bar?foo=bar#hash')); assume(data.port).equals(''); assume(data.host).equals('localhost'); assume(data.href).equals('http://localhost'); }); it('resolves pathname for relative urls', function () { var data, i = 0; var tests = [ ['', 'http://foo.com', ''], ['', 'http://foo.com/', '/'], ['a', 'http://foo.com', '/a'], ['a/', 'http://foo.com', '/a/'], ['b/c', 'http://foo.com/a', '/b/c'], ['b/c', 'http://foo.com/a/', '/a/b/c'], ['.', 'http://foo.com', '/'], ['./', 'http://foo.com', '/'], ['./.', 'http://foo.com', '/'], ['.', 'http://foo.com/a', '/'], ['.', 'http://foo.com/a/', '/a/'], ['./', 'http://foo.com/a/', '/a/'], ['./.', 'http://foo.com/a/', '/a/'], ['./b', 'http://foo.com/a/', '/a/b'], ['..', 'http://foo.com', '/'], ['../', 'http://foo.com', '/'], ['../..', 'http://foo.com', '/'], ['..', 'http://foo.com/a/b', '/'], ['..', 'http://foo.com/a/b/', '/a/'], ['../..', 'http://foo.com/a/b', '/'], ['../..', 'http://foo.com/a/b/', '/'], ['../../../../c', 'http://foo.com/a/b/', '/c'], ['./../d', 'http://foo.com/a/b/c', '/a/d'], ['d/e/f/./../../g', 'http://foo.com/a/b/c', '/a/b/d/g'] ]; for (; i < tests.length; i++) { data = parse(tests[i][0], tests[i][1]); assume(data.pathname).equals(tests[i][2]); } }); it('does not inherit hashes and query strings from source object', function () { var data = parse('/foo', parse('http://sub.example.com/bar?foo=bar#hash')); assume(data.port).equals(''); assume(data.host).equals('sub.example.com'); assume(data.href).equals('http://sub.example.com/foo'); }); it('does not inherit auth from source object', function () { var base = parse('http://foo:bar@sub.example.com') , data = parse('/foo', base); assume(data.port).equals(''); assume(data.username).equals(''); assume(data.password).equals(''); assume(data.host).equals('sub.example.com'); assume(data.href).equals('http://sub.example.com/foo'); }); }); describe('#set', function () { it('correctly updates the host when setting port', function () { var data = parse('http://google.com/foo'); assume(data.set('port', 8080)).equals(data); assume(data.host).equals('google.com:8080'); assume(data.href).equals('http://google.com:8080/foo'); }); it('correctly updates the host when setting port (IPv6)', function () { var data = parse('http://[7886:3423::1233]/foo'); assume(data.set('port', 8080)).equals(data); assume(data.host).equals('[7886:3423::1233]:8080'); assume(data.href).equals('http://[7886:3423::1233]:8080/foo'); }); it('removes querystring and hash', function () { var data = parse('https://thisanurl.com/?swag=yolo#representing'); data.set('query', ''); data.set('hash', ''); assume(data.href).equals('https://thisanurl.com/'); }); it('only sets port when its not default', function () { var data = parse('http://google.com/foo'); assume(data.set('port', 80)).equals(data); assume(data.host).equals('google.com'); assume(data.href).equals('http://google.com/foo'); assume(data.set('port', 443)).equals(data); assume(data.host).equals('google.com:443'); assume(data.href).equals('http://google.com:443/foo'); }); it('only sets port when its not default (IPv6)', function () { var data = parse('http://[7886:3423::1233]/foo'); assume(data.set('port', 80)).equals(data); assume(data.host).equals('[7886:3423::1233]'); assume(data.href).equals('http://[7886:3423::1233]/foo'); assume(data.set('port', 443)).equals(data); assume(data.host).equals('[7886:3423::1233]:443'); assume(data.href).equals('http://[7886:3423::1233]:443/foo'); }); it('prepends / to pathname', function () { var url = parse(); url .set('protocol', 'http') .set('host', 'example.com:80') .set('pathname', 'will/get/slash/prepended'); assume(url.pathname).equals('/will/get/slash/prepended'); assume(url.href).equals('http://example.com:80/will/get/slash/prepended'); url.set('pathname', ''); assume(url.pathname).equals(''); assume(url.href).equals('http://example.com:80'); url.set('pathname', '/has/slash'); assume(url.pathname).equals('/has/slash'); assume(url.href).equals('http://example.com:80/has/slash'); }); it('updates query with object', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('query', { bar: 'foo' })).equals(data); assume(data.query.foo).equals(undefined); assume(data.query.bar).equals('foo'); assume(data.href).equals('http://google.com/?bar=foo'); }); it('updates query with a string', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('query', 'bar=foo')).equals(data); assume(data.query.foo).equals(undefined); assume(data.query.bar).equals('foo'); assume(data.href).equals('http://google.com/?bar=foo'); assume(data.set('query', '?baz=foo')).equals(data); assume(data.query.bar).equals(undefined); assume(data.query.baz).equals('foo'); assume(data.href).equals('http://google.com/?baz=foo'); }); it('allows custom parser when updating query', function() { var data = parse('http://google.com/?foo=bar'); assume(data.set('query', 'bar=foo', function () { return '1337'; })).equals(data); assume(data.query).equals('1337'); assume(data.href).equals('http://google.com/?1337'); }); it('throws error when updating query, if custom parser is not a function', function() { var data = parse('http://google.com/?foo=bar'); assume(function () { data.set('query', 'bar=foo', '1337'); }).throws(Error); // // `data` is unchanged. // assume(data.href).equals('http://google.com/?foo=bar'); }); it('prepends # to hash', function () { var data = parse('http://example.com'); data.set('hash', 'usage'); assume(data.hash).equals('#usage'); assume(data.href).equals('http://example.com#usage'); data.set('hash', '#license'); assume(data.hash).equals('#license'); assume(data.href).equals('http://example.com#license'); }); it('updates the port when updating host', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('host', 'yahoo.com:808')).equals(data); assume(data.hostname).equals('yahoo.com'); assume(data.host).equals('yahoo.com:808'); assume(data.port).equals('808'); assume(data.href).equals('http://yahoo.com:808/?foo=bar'); }); it('updates the port when updating host (IPv6)', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('host', '[56h7::1]:808')).equals(data); assume(data.hostname).equals('[56h7::1]'); assume(data.host).equals('[56h7::1]:808'); assume(data.port).equals('808'); assume(data.href).equals('http://[56h7::1]:808/?foo=bar'); }); it('unsets the port when port is missing (IPv6)', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('host', '[56h7::1]')).equals(data); assume(data.hostname).equals('[56h7::1]'); assume(data.host).equals('[56h7::1]'); assume(data.port).equals(''); assume(data.href).equals('http://[56h7::1]/?foo=bar'); }); it('unsets the port when the port is missing from host', function () { var data = parse('http://google.com:8000/?foo=bar'); assume(data.set('host', 'yahoo.com')).equals(data); assume(data.hostname).equals('yahoo.com'); assume(data.host).equals('yahoo.com'); assume(data.port).equals(''); assume(data.href).equals('http://yahoo.com/?foo=bar'); }); it('updates the host when updating hostname', function () { var data = parse('http://google.com:808/?foo=bar'); assume(data.set('hostname', 'yahoo.com')).equals(data); assume(data.hostname).equals('yahoo.com'); assume(data.host).equals('yahoo.com:808'); assume(data.port).equals('808'); assume(data.href).equals('http://yahoo.com:808/?foo=bar'); }); it('updates slashes when updating protocol', function() { var data = parse('sip:alice@atlanta.com'); assume(data.set('protocol', 'https')).equals(data); assume(data.href).equals('https://alice@atlanta.com'); assume(data.set('protocol', 'mailto', true)).equals(data); assume(data.href).equals('mailto:alice@atlanta.com'); }); it('updates other values', function () { var data = parse('http://google.com/?foo=bar'); assume(data.set('protocol', 'https:')).equals(data); assume(data.protocol).equals('https:'); assume(data.href).equals('https://google.com/?foo=bar'); data.set('username', 'foo'); assume(data.username).equals('foo'); assume(data.href).equals('https://foo@google.com/?foo=bar'); }); it('lowercases the required values', function () { var data = parse('http://google.com/?foo=bar'); data.set('protocol', 'HTTPS:'); assume(data.protocol).equals('https:'); assume(data.href).equals('https://google.com/?foo=bar'); data.set('host', 'GOOGLE.LOL'); assume(data.host).equals('google.lol'); assume(data.href).equals('https://google.lol/?foo=bar'); data.set('hostname', 'YAhOo.COm'); assume(data.hostname).equals('yahoo.com'); assume(data.href).equals('https://yahoo.com/?foo=bar'); }); it('correctly updates the origin when host/protocol/port changes', function () { var data = parse('http://google.com/?foo=bar'); data.set('protocol', 'HTTPS:'); assume(data.protocol).equals('https:'); assume(data.origin).equals('https://google.com'); data.set('port', '1337'); assume(data.port).equals('1337'); assume(data.origin).equals('https://google.com:1337'); data.set('protocol', 'file:'); assume(data.protocol).equals('file:'); assume(data.origin).equals('null'); }); }); describe('fuzzy', function () { var fuzz = require('./fuzzy') , times = 10; for (var i = 0; i < times; i++) { (function (spec) { it('parses: '+ spec.href, function () { var url = parse(spec.href) , prop; for (prop in spec) { assume(url[prop]).equals(spec[prop]); } }); })(fuzz()); } }); });