pax_global_header00006660000000000000000000000064142044644640014522gustar00rootroot0000000000000052 comment=ad23357ad5fd9a6b011d049466e9ecff723e52b8 url-parse-1.5.9/000077500000000000000000000000001420446446400134505ustar00rootroot00000000000000url-parse-1.5.9/.github/000077500000000000000000000000001420446446400150105ustar00rootroot00000000000000url-parse-1.5.9/.github/workflows/000077500000000000000000000000001420446446400170455ustar00rootroot00000000000000url-parse-1.5.9/.github/workflows/ci.yml000066400000000000000000000015011420446446400201600ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: runs-on: ubuntu-latest strategy: matrix: node: - 12 - 14 - 16 - 17 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - run: npm install - run: npm test - uses: coverallsapp/github-action@1.1.3 if: matrix.node == 12 with: github-token: ${{ secrets.GITHUB_TOKEN }} test-browser: runs-on: ubuntu-latest env: SAUCE_USERNAME: url-parse SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 12 - run: npm install - run: npm run test-browser url-parse-1.5.9/.gitignore000066400000000000000000000000511420446446400154340ustar00rootroot00000000000000node_modules/ coverage/ dist/ .tern-port url-parse-1.5.9/.npmrc000066400000000000000000000000231420446446400145630ustar00rootroot00000000000000package-lock=false url-parse-1.5.9/LICENSE000066400000000000000000000021331420446446400144540ustar00rootroot00000000000000The 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.5.9/README.md000066400000000000000000000141761420446446400147400ustar00rootroot00000000000000# url-parse [![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/github/workflow/status/unshiftio/url-parse/CI/master?label=CI&style=flat-square)](https://github.com/unshiftio/url-parse/actions?query=workflow%3ACI+branch%3Amaster)[![Coverage Status](https://img.shields.io/coveralls/unshiftio/url-parse/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/url-parse?branch=master) [![Sauce Test Status](https://saucelabs.com/browser-matrix/url-parse.svg)](https://saucelabs.com/u/url-parse) **`url-parse` was created in 2014 when the WHATWG URL API was not available in Node.js and the `URL` interface was supported only in some browsers. Today this is no longer true. The `URL` interface is available in all supported Node.js release lines and basically all browsers. Consider using it for better security and accuracy.** 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. The hostname might be invalid. - `hostname`: Host name without port number. This might be an invalid hostname. - `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.5.9/SECURITY.md000066400000000000000000000100161420446446400152370ustar00rootroot00000000000000# Security Guidelines Please contact us directly at **security@3rd-Eden.com** for any bug that might impact the security of this project. Please prefix the subject of your email with `[security]` in lowercase and square brackets. Our email filters will automatically prevent these messages from being moved to our spam box. All emails that do not include security vulnerabilities will be removed and blocked instantly. In addition to a dedicated email address to receive security related reports, we also have a [Hacker1 account][hacker1] that can be used for communicating security related issues. You will receive an acknowledgement of your report within **24 hours** of notification. ## Exceptions If you do not receive an acknowledgement within the said time frame please give us the benefit of the doubt as it's possible that we haven't seen it yet. In this case please send us a message **without details** using one of the following methods: - Give a poke on Twitter [@3rdEden](https://twitter.com/3rdEden) - Contact the lead developers of this project on their personal e-mails. You can find the e-mails in the git logs, for example using the following command: `git --no-pager show -s --format='%an <%ae>' ` where `` is the SHA1 of their latest commit in the project. Once we have acknowledged receipt of your report and confirmed the bug ourselves we will work with you to fix the vulnerability and publicly acknowledge your responsible disclosure, if you wish. ## History > A URL with a specified but empty port can be used to bypass authorization > checks. - **Reporter credits** - Rohan Sharma - GitHub: [@r0hansh](https://github.com/r0hansh) - Huntr report: https://www.huntr.dev/bounties/55fd06cd-9054-4d80-83be-eb5a454be78c/ - Fixed in: 1.5.8 --- > A specially crafted URL with empty userinfo and no host can be used to bypass > authorization checks. - **Reporter credits** - Haxatron - GitHub: [@haxatron](https://github.com/haxatron) - Twitter: [@haxatron1](https://twitter.com/haxatron1) - Huntr report: https://www.huntr.dev/bounties/83a6bc9a-b542-4a38-82cd-d995a1481155/ - Fixed in: 1.5.7 --- > Incorrect handling of username and password can lead to authorization bypass. - **Reporter credits** - ranjit-git - GitHub: [@ranjit-git](https://github.com/ranjit-git) - Huntr report: https://www.huntr.dev/bounties/6d1bc51f-1876-4f5b-a2c2-734e09e8e05b/ - Fixed in: 1.5.6 --- > url-parse mishandles certain uses of a single (back) slash such as https:\ & > https:/ and interprets the URI as a relative path. Browsers accept a single > backslash after the protocol, and treat it as a normal slash, while url-parse > sees it as a relative path. - **Reporter credits** - ready-research - GitHub: [@ready-research](https://github.com/ready-research) - Huntr report: https://www.huntr.dev/bounties/1625557993985-unshiftio/url-parse/ - Fixed in: 1.5.2 --- > Using backslash in the protocol is valid in the browser, while url-parse > thinks it’s a relative path. An application that validates a url using > url-parse might pass a malicious link. - **Reporter credits** - CxSCA AppSec team at Checkmarx. - Twitter: [Yaniv Nizry](https://twitter.com/ynizry) - Fixed in: 1.5.0 --- > The `extractProtocol` method does not return the correct protocol when > provided with unsanitized content which could lead to false positives. - **Reporter credits** - Reported through our security email & Twitter interaction. - Twitter: [@ronperris](https://twitter.com/ronperris) - Fixed in: 1.4.5 --- > url-parse returns wrong hostname which leads to multiple vulnerabilities such > as SSRF, Open Redirect, Bypass Authentication Protocol. - **Reporter credits** - Hacker1: [lolwaleet](https://hackerone.com/lolwalee) - Twitter: [@ahm3dsec](https://twitter.com/ahm3dsec) - Blog: [0xahmed.ninja](https://0xahmed.ninja) - Hacker1 report: https://hackerone.com/reports/384029 - Triaged by [Liran Tal](https://hackerone.com/lirantal) - Fixed in: 1.4.3 --- [twitter]: https://twitter.com/3rdEden [hacker1]: https://hackerone.com/3rdeden url-parse-1.5.9/index.js000066400000000000000000000377361420446446400151350ustar00rootroot00000000000000'use strict'; var required = require('requires-port') , qs = require('querystringify') , CRHTLF = /[\n\r\t]/g , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\// , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i , windowsDriveLetter = /^[a-zA-Z]:/ , whitespace = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/; /** * Trim a given string. * * @param {String} str String to trim. * @public */ function trimLeft(str) { return (str ? str : '').toString().replace(whitespace, ''); } /** * 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. function sanitize(address, url) { // Sanitize what is left of the address return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address; }, ['/', '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. * @public */ function lolcation(loc) { var globalVar; if (typeof window !== 'undefined') globalVar = window; else if (typeof global !== 'undefined') globalVar = global; else if (typeof self !== 'undefined') globalVar = self; else globalVar = {}; var location = globalVar.location || {}; loc = loc || 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; } /** * Check whether a protocol scheme is special. * * @param {String} The protocol scheme of the URL * @return {Boolean} `true` if the protocol scheme is special, else `false` * @private */ function isSpecial(scheme) { return ( scheme === 'file:' || scheme === 'ftp:' || scheme === 'http:' || scheme === 'https:' || scheme === 'ws:' || scheme === 'wss:' ); } /** * @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. * @param {Object} location * @return {ProtocolExtract} Extracted information. * @private */ function extractProtocol(address, location) { address = trimLeft(address); address = address.replace(CRHTLF, ''); location = location || {}; var match = protocolre.exec(address); var protocol = match[1] ? match[1].toLowerCase() : ''; var forwardSlashes = !!match[2]; var otherSlashes = !!match[3]; var slashesCount = 0; var rest; if (forwardSlashes) { if (otherSlashes) { rest = match[2] + match[3] + match[4]; slashesCount = match[2].length + match[3].length; } else { rest = match[2] + match[4]; slashesCount = match[2].length; } } else { if (otherSlashes) { rest = match[3] + match[4]; slashesCount = match[3].length; } else { rest = match[4] } } if (protocol === 'file:') { if (slashesCount >= 2) { rest = rest.slice(2); } } else if (isSpecial(protocol)) { rest = match[4]; } else if (protocol) { if (forwardSlashes) { rest = rest.slice(2); } } else if (slashesCount >= 2 && isSpecial(location.protocol)) { rest = match[4]; } return { protocol: protocol, slashes: forwardSlashes || isSpecial(protocol), slashesCount: slashesCount, rest: rest }; } /** * 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. * @private */ function resolve(relative, base) { if (relative === '') return 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. * * It is worth noting that we should not use `URL` as class name to prevent * clashes with the global URL instance that got introduced in browsers. * * @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. * @private */ function Url(address, location, parser) { address = trimLeft(address); address = address.replace(CRHTLF, ''); 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 || '', location); 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.protocol === 'file:' && ( extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || (!extracted.slashes && (extracted.protocol || extracted.slashesCount < 2 || !isSpecial(url.protocol))) ) { instructions[3] = [/(.*)/, 'pathname']; } for (; i < instructions.length; i++) { instruction = instructions[i]; if (typeof instruction === 'function') { address = instruction(address, url); continue; } parse = instruction[0]; key = instruction[1]; if (parse !== parse) { url[key] = address; } else if ('string' === typeof parse) { index = parse === '@' ? address.lastIndexOf(parse) : address.indexOf(parse); if (~index) { 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); } // // Default to a / for pathname if none exists. This normalizes the URL // to always have a / // if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) { url.pathname = '/' + url.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) { index = url.auth.indexOf(':'); if (~index) { url.username = url.auth.slice(0, index); url.username = encodeURIComponent(decodeURIComponent(url.username)); url.password = url.auth.slice(index + 1); url.password = encodeURIComponent(decodeURIComponent(url.password)) } else { url.username = encodeURIComponent(decodeURIComponent(url.auth)); } url.auth = url.password ? url.username +':'+ url.password : url.username; } url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? 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} URL instance for chaining. * @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; case 'username': case 'password': url[part] = encodeURIComponent(value); break; case 'auth': var index = value.indexOf(':'); if (~index) { url.username = value.slice(0, index); url.username = encodeURIComponent(decodeURIComponent(url.username)); url.password = value.slice(index + 1); url.password = encodeURIComponent(decodeURIComponent(url.password)); } else { url.username = encodeURIComponent(decodeURIComponent(value)); } } for (var i = 0; i < rules.length; i++) { var ins = rules[i]; if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); } url.auth = url.password ? url.username +':'+ url.password : url.username; url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host ? 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} Compiled version of the URL. * @public */ function toString(stringify) { if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; var query , url = this , host = url.host , protocol = url.protocol; if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; var result = protocol + ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : ''); if (url.username) { result += url.username; if (url.password) result += ':'+ url.password; result += '@'; } else if (url.password) { result += ':'+ url.password; result += '@'; } else if ( url.protocol !== 'file:' && isSpecial(url.protocol) && !host && url.pathname !== '/' ) { // // Add back the empty userinfo, otherwise the original invalid URL // might be transformed into a valid one with `url.pathname` as host. // result += '@'; } // // Trailing colon is removed from `url.host` when it is parsed. If it still // ends with a colon, then add back the trailing colon that was removed. This // prevents an invalid URL from being transformed into a valid one. // if (host[host.length - 1] === ':') host += ':'; result += 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.trimLeft = trimLeft; Url.qs = qs; module.exports = Url; url-parse-1.5.9/package.json000066400000000000000000000023661420446446400157450ustar00rootroot00000000000000{ "name": "url-parse", "version": "1.5.9", "description": "Small footprint URL parser that works seamlessly across Node.js and browser environments", "main": "index.js", "scripts": { "browserify": "rm -rf dist && mkdir -p dist && browserify index.js -s URLParse -o dist/url-parse.js", "minify": "uglifyjs dist/url-parse.js --source-map -cm -o dist/url-parse.min.js", "test": "c8 --reporter=lcov --reporter=text mocha test/test.js", "test-browser": "node test/browser.js", "prepublishOnly": "npm run browserify && npm run minify", "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": "^2.1.1", "requires-port": "^1.0.0" }, "devDependencies": { "assume": "^2.2.0", "browserify": "^17.0.0", "c8": "^7.3.1", "mocha": "^9.0.3", "pre-commit": "^1.2.2", "sauce-browsers": "^2.0.0", "sauce-test": "^1.3.3", "uglify-js": "^3.5.7" } } url-parse-1.5.9/test/000077500000000000000000000000001420446446400144275ustar00rootroot00000000000000url-parse-1.5.9/test/browser.js000066400000000000000000000023571420446446400164570ustar00rootroot00000000000000'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: 'safari', version: ['oldest', 'latest'] }, { name: 'microsoftedge', version: ['oldest', 'latest'] } ]).then((platforms) => { return platforms.map((platform) => { const ret = { browserName: platform.api_name, version: platform.short_version, platform: platform.os }; if (ret.browserName === 'android') ret.deviceName = platform.long_name; return ret; }); }); run(path.join(__dirname, 'test.js'), 'saucelabs', { jobInfo: { name: pkg.name, build: process.env.GITHUB_RUN_ID }, html: path.join(__dirname, 'index.html'), accessKey: process.env.SAUCE_ACCESS_KEY, username: process.env.SAUCE_USERNAME, browserify: true, disableSSL: true, parallel: 5, platforms }).done((results) => { if (!results.passed) process.exit(1); }); url-parse-1.5.9/test/fuzzy.js000066400000000000000000000047271420446446400161660ustar00rootroot00000000000000'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.slashes = true; 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.5.9/test/index.html000066400000000000000000000013711420446446400164260ustar00rootroot00000000000000
{{scripts}} url-parse-1.5.9/test/test.js000066400000000000000000001274011420446446400157510ustar00rootroot00000000000000describe('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'); }); it('works when the global variable is not defined', function () { var globalVar = global; global = undefined; var url = parse('http://google.com/?foo=bar', true); 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'); global = globalVar; }); describe('trimLeft', function () { it('is a function', function () { assume(parse.trimLeft).is.a('function'); }); it('removes control characters on the left', function () { var i = 0; var prefix = '' for (; i < 33; i++) { prefix = String.fromCharCode(i); assume(parse.trimLeft(prefix + prefix +'lol')).equals('lol'); } }); it('calls toString on a given value', function () { // // When users pass in `window.location` it's not an actual string // so you can't replace on it. So it needs to be cast to a string. // var fake = { toString: function () { return 'wat'; } }; assume(parse.trimLeft(fake)).equals('wat'); }); }); describe('extractProtocol', function () { it('extracts the protocol data', function () { assume(parse.extractProtocol('http://example.com')).eql({ slashes: true, protocol: 'http:', rest: 'example.com', slashesCount: 2 }); }); it('extracts the protocol data for nothing', function () { assume(parse.extractProtocol('')).eql({ slashes: false, protocol: '', rest: '', slashesCount: 0 }); }); it('correctly resolves paths', function () { assume(parse.extractProtocol('/foo')).eql({ slashes: false, protocol: '', rest: '/foo', slashesCount: 1 }); assume(parse.extractProtocol('//foo/bar')).eql({ slashes: true, protocol: '', rest: '//foo/bar', slashesCount: 2 }); }); it('does not truncate the input string', function () { var input = 'foo\x0bbar\x0cbaz\u2028qux\u2029'; assume(parse.extractProtocol(input)).eql({ slashes: false, protocol: '', rest: input, slashesCount: 0 }); }); it('trimsLeft', function () { assume(parse.extractProtocol('\x0b\x0c javascript://foo')).eql({ slashes: true, protocol: 'javascript:', rest: 'foo', slashesCount: 2 }); }); it('removes CR, HT, and LF', function () { assume(parse.extractProtocol('jav\n\rasc\nript\r:/\t/fo\no')).eql({ slashes: true, protocol: 'javascript:', rest: 'foo', slashesCount: 2 }); }); }); 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.pathname).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('correctly parses pathnames for relative paths', function () { var url = '/dataApi/PROD/ws' , parsed = parse(url, 'http://localhost:3000/PROD/trends'); assume(parsed.pathname).equals('/dataApi/PROD/ws'); url = '/sections/?project=default' parsed = parse(url, 'http://example.com/foo/bar'); assume(parsed.pathname).equals('/sections/'); assume(parsed.hostname).equals('example.com'); assume(parsed.href).equals('http://example.com/sections/?project=default'); }); 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'); }); it('ignores \\ in pathnames', function () { var url = 'http://google.com:80\\@yahoo.com/#what\\is going on' , parsed = parse(url); assume(parsed.port).equals(''); assume(parsed.username).equals(''); assume(parsed.password).equals(''); assume(parsed.hostname).equals('google.com'); assume(parsed.hash).equals('#what\\is going on'); parsed = parse('http://yolo.com\\what-is-up.com'); assume(parsed.pathname).equals('/what-is-up.com'); }); it('correctly ignores multiple slashes //', function () { var url = '////what-is-up.com' , parsed = parse(url, parse('http://google.com')); assume(parsed.host).equals('what-is-up.com'); assume(parsed.href).equals('http://what-is-up.com/'); url = '\\\\\\\\what-is-up.com' parsed = parse(url, parse('http://google.com')); assume(parsed.host).equals('what-is-up.com'); assume(parsed.href).equals('http://what-is-up.com/'); }); it('ignores slashes after the protocol for special URLs', function () { var url = 'https:\\/github.com/foo/bar' , parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.hostname).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); url = 'https:/\\/\\/\\github.com/foo/bar'; parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.hostname).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); url = 'https:/github.com/foo/bar'; parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); url = 'https:\\github.com/foo/bar'; parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); url = 'https:github.com/foo/bar'; parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); url = 'https:github.com/foo/bar'; parsed = parse(url); assume(parsed.host).equals('github.com'); assume(parsed.pathname).equals('/foo/bar'); assume(parsed.slashes).is.true(); assume(parsed.href).equals('https://github.com/foo/bar'); }); it('handles slashes after the protocol for non special URLs', function () { var url = 'foo:example.com' , parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('example.com'); assume(parsed.href).equals('foo:example.com'); assume(parsed.slashes).is.false(); url = 'foo:/example.com'; parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('/example.com'); assume(parsed.href).equals('foo:/example.com'); assume(parsed.slashes).is.false(); url = 'foo:\\example.com'; parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('\\example.com'); assume(parsed.href).equals('foo:\\example.com'); assume(parsed.slashes).is.false(); url = 'foo://example.com'; parsed = parse(url); assume(parsed.hostname).equals('example.com'); assume(parsed.pathname).equals(''); assume(parsed.href).equals('foo://example.com'); assume(parsed.slashes).is.true(); url = 'foo:\\\\example.com'; parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('\\\\example.com'); assume(parsed.href).equals('foo:\\\\example.com'); assume(parsed.slashes).is.false(); url = 'foo:///example.com'; parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('/example.com'); assume(parsed.href).equals('foo:///example.com'); assume(parsed.slashes).is.true(); url = 'foo:\\\\\\example.com'; parsed = parse(url); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('\\\\\\example.com'); assume(parsed.href).equals('foo:\\\\\\example.com'); assume(parsed.slashes).is.false(); url = '\\\\example.com/foo/bar'; parsed = parse(url, 'foo://bar.com'); assume(parsed.hostname).equals('bar.com'); assume(parsed.pathname).equals('/\\\\example.com/foo/bar'); assume(parsed.href).equals('foo://bar.com/\\\\example.com/foo/bar'); assume(parsed.slashes).is.true(); }); it('does not readd slashes to href if there is no protocol', function() { var parsed = parse('//example.com', {}); assume(parsed.pathname).equals('//example.com'); assume(parsed.href).equals('//example.com'); }); it('removes CR, HT, and LF', function () { var parsed = parse( 'ht\ntp://a\rb:\tcd@exam\rple.com:80\t80/pat\thname?fo\no=b\rar#ba\tz' ); assume(parsed.protocol).equals('http:'); assume(parsed.username).equals('ab'); assume(parsed.password).equals('cd'); assume(parsed.host).equals('example.com:8080'); assume(parsed.hostname).equals('example.com'); assume(parsed.port).equals('8080'); assume(parsed.pathname).equals('/pathname'); assume(parsed.query).equals('?foo=bar'); assume(parsed.hash).equals('#baz'); assume(parsed.href).equals( 'http://ab:cd@example.com:8080/pathname?foo=bar#baz' ); parsed = parse('s\nip:al\rice@atl\tanta.com'); assume(parsed.protocol).equals('sip:'); assume(parsed.pathname).equals('alice@atlanta.com'); assume(parsed.href).equals('sip:alice@atlanta.com'); }); it('handles the case where the port is specified but empty', function () { var parsed = parse('http://example.com:'); assume(parsed.protocol).equals('http:'); assume(parsed.port).equals(''); assume(parsed.host).equals('example.com'); assume(parsed.hostname).equals('example.com'); assume(parsed.pathname).equals('/'); assume(parsed.origin).equals('http://example.com'); assume(parsed.href).equals('http://example.com/'); parsed = parse('http://example.com::'); assume(parsed.protocol).equals('http:'); assume(parsed.port).equals(''); assume(parsed.host).equals('example.com:'); assume(parsed.hostname).equals('example.com:'); assume(parsed.pathname).equals('/'); assume(parsed.origin).equals('http://example.com:'); assume(parsed.href).equals('http://example.com::/'); }); 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('is null for non special URLs', function () { var o = parse('foo://example.com/pathname'); assume(o.hostname).equals('example.com'); assume(o.pathname).equals('/pathname'); assume(o.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'); }); it('maintains the port number for non-default port numbers', function () { var parsed = parse('http://google.com:8080/pathname'); assume(parsed.host).equals('google.com:8080'); assume(parsed.href).equals('http://google.com:8080/pathname'); }); }); describe('protocol', function () { it('extracts the right protocol from a url', function () { var testData = [ { href: 'http://example.com/', protocol: 'http:', pathname: '/', slashes: true }, { href: 'ws://example.com/', protocol: 'ws:', pathname: '/', slashes: true }, { href: 'wss://example.com/', protocol: 'wss:', pathname: '/', slashes: true }, { href: 'mailto:test@example.com', pathname: 'test@example.com', protocol: 'mailto:', slashes: false }, { href: 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', pathname: 'text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', protocol: 'data:', slashes: false, }, { href: 'sip:alice@atlanta.com', pathname: 'alice@atlanta.com', protocol: 'sip:', slashes: false, } ]; var data, test; for (var i = 0, len = testData.length; i < len; ++i) { test = testData[i]; data = parse(test.href); assume(data.protocol).equals(test.protocol); assume(data.pathname).equals(test.pathname); assume(data.slashes).equals(test.slashes); assume(data.href).equals(test.href); } }); 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'); }); it('handles the file: protocol', function () { var slashes = ['', '/', '//', '///']; var data; var url; for (var i = 0; i < slashes.length; i++) { data = parse('file:' + slashes[i]); assume(data.protocol).equals('file:'); assume(data.pathname).equals('/'); assume(data.href).equals('file:///'); } url = 'file:////'; data = parse(url); assume(data.protocol).equals('file:'); assume(data.pathname).equals('//'); assume(data.href).equals(url); url = 'file://///'; data = parse(url); assume(data.protocol).equals('file:'); assume(data.pathname).equals('///'); assume(data.href).equals(url); url = 'file:///Users/foo/BAR/baz.pdf'; data = parse(url); assume(data.protocol).equals('file:'); assume(data.pathname).equals('/Users/foo/BAR/baz.pdf'); assume(data.href).equals(url); url = 'file:///foo/bar?baz=qux#hash'; data = parse(url); assume(data.protocol).equals('file:'); assume(data.hash).equals('#hash'); assume(data.query).equals('?baz=qux'); assume(data.pathname).equals('/foo/bar'); assume(data.href).equals(url); data = parse('file://c:\\foo\\bar\\'); assume(data.protocol).equals('file:'); assume(data.pathname).equals('/c:/foo/bar/'); assume(data.href).equals('file:///c:/foo/bar/'); data = parse('file://host/file'); assume(data.protocol).equals('file:'); assume(data.host).equals('host'); assume(data.hostname).equals('host'); assume(data.pathname).equals('/file'); assume(data.href).equals('file://host/file'); data = parse('foo/bar', 'file:///baz'); assume(data.protocol).equals('file:'); assume(data.pathname).equals('/foo/bar'); assume(data.href).equals('file:///foo/bar'); data = parse('foo/bar', 'file:///baz/'); assume(data.protocol).equals('file:'); assume(data.pathname).equals('/baz/foo/bar'); assume(data.href).equals('file:///baz/foo/bar'); }); }); 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.pathname).equals('/'); 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('handles @ in username', function () { var url = 'http://user@@www.example.com/' , parsed = parse(url); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals('user%40'); assume(parsed.username).equals('user%40'); assume(parsed.password).equals(''); assume(parsed.hostname).equals('www.example.com'); assume(parsed.pathname).equals('/'); assume(parsed.href).equals('http://user%40@www.example.com/'); url = 'http://user%40@www.example.com/'; parsed = parse(url); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals('user%40'); assume(parsed.username).equals('user%40'); assume(parsed.password).equals(''); assume(parsed.hostname).equals('www.example.com'); assume(parsed.pathname).equals('/'); assume(parsed.href).equals('http://user%40@www.example.com/'); }); it('handles @ in password', function () { var url = 'http://user@:pas:s@@www.example.com/' , parsed = parse(url); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals('user%40:pas%3As%40'); assume(parsed.username).equals('user%40'); assume(parsed.password).equals('pas%3As%40'); assume(parsed.hostname).equals('www.example.com'); assume(parsed.pathname).equals('/'); assume(parsed.href).equals('http://user%40:pas%3As%40@www.example.com/'); url = 'http://user%40:pas%3As%40@www.example.com/' parsed = parse(url); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals('user%40:pas%3As%40'); assume(parsed.username).equals('user%40'); assume(parsed.password).equals('pas%3As%40'); assume(parsed.hostname).equals('www.example.com'); assume(parsed.pathname).equals('/'); assume(parsed.href).equals('http://user%40:pas%3As%40@www.example.com/'); }); it('adds @ to href if auth and host are empty', function () { var parsed, i = 0; var urls = [ 'http:@/127.0.0.1', 'http::@/127.0.0.1', 'http:/@/127.0.0.1', 'http:/:@/127.0.0.1', 'http://@/127.0.0.1', 'http://:@/127.0.0.1', 'http:///@/127.0.0.1', 'http:///:@/127.0.0.1' ]; for (; i < urls.length; i++) { parsed = parse(urls[i]); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals(''); assume(parsed.username).equals(''); assume(parsed.password).equals(''); assume(parsed.host).equals(''); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('/127.0.0.1'); assume(parsed.origin).equals('null'); assume(parsed.href).equals('http://@/127.0.0.1'); assume(parsed.toString()).equals('http://@/127.0.0.1'); } urls = [ 'http:@/', 'http:@', 'http::@/', 'http::@', 'http:/@/', 'http:/@', 'http:/:@/', 'http:/:@', 'http://@/', 'http://@', 'http://:@/', 'http://:@' ]; for (i = 0; i < urls.length; i++) { parsed = parse(urls[i]); assume(parsed.protocol).equals('http:'); assume(parsed.auth).equals(''); assume(parsed.username).equals(''); assume(parsed.password).equals(''); assume(parsed.host).equals(''); assume(parsed.hostname).equals(''); assume(parsed.pathname).equals('/'); assume(parsed.origin).equals('null'); assume(parsed.href).equals('http:///'); assume(parsed.toString()).equals('http:///'); } }); }); 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 lolcation = parse('http://sub.example.com:808/') , data = parse('//foo.com/foo', lolcation); 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/', '/'], ['', 'http://foo.com/a', '/a'], ['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 auth when updating username', function() { var data = parse('https://example.com'); assume(data.set('username', 'foo')).equals(data); assume(data.username).equals('foo'); assume(data.auth).equals('foo') assume(data.href).equals('https://foo@example.com/'); data.set('username', ''); assume(data.username).equals(''); assume(data.auth).equals('') assume(data.href).equals('https://example.com/'); data.set('username', 'foo:'); assume(data.username).equals('foo%3A'); assume(data.auth).equals('foo%3A') assume(data.href).equals('https://foo%3A@example.com/'); data = parse('https://foo:bar@example.com') data.set('username', 'baz'); assume(data.username).equals('baz'); assume(data.auth).equals('baz:bar') assume(data.href).equals('https://baz:bar@example.com/'); }); it('updates auth when updating password', function() { var data = parse('https://example.com'); assume(data.set('password', 'foo')).equals(data); assume(data.password).equals('foo'); assume(data.auth).equals(':foo') assume(data.href).equals('https://:foo@example.com/'); data.set('password', ''); assume(data.password).equals(''); assume(data.auth).equals('') assume(data.href).equals('https://example.com/'); data.set('password', ':foo@'); assume(data.password).equals('%3Afoo%40'); assume(data.auth).equals(':%3Afoo%40') assume(data.href).equals('https://:%3Afoo%40@example.com/'); data = parse('https://foo:bar@example.com') data.set('password', 'baz'); assume(data.password).equals('baz'); assume(data.auth).equals('foo:baz') assume(data.href).equals('https://foo:baz@example.com/'); }); it('updates username and password when updating auth', function() { var data = parse('https://example.com'); assume(data.set('auth', 'foo:bar')).equals(data); assume(data.username).equals('foo'); assume(data.password).equals('bar'); assume(data.href).equals('https://foo:bar@example.com/'); assume(data.set('auth', 'baz:')).equals(data); assume(data.username).equals('baz'); assume(data.password).equals(''); assume(data.href).equals('https://baz@example.com/'); assume(data.set('auth', 'qux')).equals(data); assume(data.username).equals('qux'); assume(data.password).equals(''); assume(data.href).equals('https://qux@example.com/'); assume(data.set('auth', ':quux')).equals(data); assume(data.username).equals(''); assume(data.password).equals('quux'); assume(data.href).equals('https://:quux@example.com/'); assume(data.set('auth', 'user@:pass@')).equals(data); assume(data.username).equals('user%40'); assume(data.password).equals('pass%40'); assume(data.href).equals('https://user%40:pass%40@example.com/'); assume(data.set('auth', 'user%40:pass%40')).equals(data); assume(data.username).equals('user%40'); assume(data.password).equals('pass%40'); assume(data.href).equals('https://user%40:pass%40@example.com/'); assume(data.set('auth', 'user:pass:word')).equals(data); assume(data.username).equals('user'); assume(data.password).equals('pass%3Aword'); assume(data.href).equals('https://user:pass%3Aword@example.com/'); assume(data.set('auth', 'user:pass%3Aword')).equals(data); assume(data.username).equals('user'); assume(data.password).equals('pass%3Aword'); assume(data.href).equals('https://user:pass%3Aword@example.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()); } }); });