├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── node-aught.yml │ ├── node-pretest.yml │ ├── node-tens.yml │ ├── rebase.yml │ └── require-allow-edits.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .nycrc ├── .travis.yml ├── .zuul.yml ├── LICENSE ├── README.md ├── package.json ├── test ├── index.js └── mocha.opts └── url.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "@ljharb", 5 | 6 | "rules": { 7 | "complexity": 0, 8 | "eqeqeq": [2, "allow-null"], 9 | "func-style": 0, 10 | "id-length": 0, 11 | "indent": [2, 2], 12 | "max-depth": 0, 13 | "max-lines": 0, 14 | "max-lines-per-function": 0, 15 | "max-statements": 0, 16 | "max-statements-per-line": [2, { "max": 3 }], 17 | "no-continue": 1, 18 | "no-empty": 1, 19 | "no-magic-numbers": 0, 20 | "no-mixed-operators": 1, 21 | "max-nested-callbacks": 0, 22 | "no-param-reassign": 1, 23 | "no-plusplus": 0, 24 | "no-redeclare": 1, 25 | "no-restricted-syntax": 1, 26 | "no-script-url": 1, 27 | "sort-keys": 0, 28 | }, 29 | 30 | "overrides": [ 31 | { 32 | "files": "test/**/*", 33 | "rules": { 34 | "no-sequences": 1, 35 | }, 36 | }, 37 | ], 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/node-aught.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js < 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '< 10' 10 | type: minors 11 | command: npm run tests-only 12 | skip-ls-check: true # node 4-9 fails this 13 | -------------------------------------------------------------------------------- /.github/workflows/node-pretest.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: pretest/posttest' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/pretest.yml@main 8 | -------------------------------------------------------------------------------- /.github/workflows/node-tens.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js >= 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 10' 10 | type: minors 11 | command: npm run tests-only 12 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | 3 | on: [pull_request_target] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | _: 10 | permissions: 11 | contents: write # for ljharb/rebase to push code to rebase 12 | pull-requests: read # for ljharb/rebase to get info about PR 13 | 14 | name: "Automatic Rebase" 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: ljharb/rebase@master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/require-allow-edits.yml: -------------------------------------------------------------------------------- 1 | name: Require “Allow Edits” 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | name: "Require “Allow Edits”" 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: ljharb/require-allow-edits@main 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | node_modules 3 | .DS_Store 4 | npm-debug.log 5 | 6 | # Only apps should have lockfiles 7 | npm-shrinkwrap.json 8 | package-lock.json 9 | yarn.lock 10 | 11 | coverage/ 12 | .nyc_output/ 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | node_modules 3 | .DS_Store 4 | npm-debug.log 5 | 6 | # Only apps should have lockfiles 7 | npm-shrinkwrap.json 8 | package-lock.json 9 | yarn.lock 10 | 11 | coverage/ 12 | .nyc_output/ 13 | 14 | .github/workflows 15 | .zuul.yml 16 | .travis.yml 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "check-coverage": false, 4 | "reporter": ["text-summary", "text", "html", "json"], 5 | "exclude": [ 6 | "coverage", 7 | "test" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: "npm run zuul" 5 | env: 6 | global: 7 | - secure: OgPRLCzHFh5WbjHEKlghHFW1oOreSF2JVUr3CMaFDi03ngTS2WONSw8mRn8SA6FTldiGGBx1n8orDzUw6cdkB7+tkU3G5B0M0V3vl823NaUFKgxsCM3UGDYfJb3yfAG5cj72rVZoX/ABd1fVuG4vBIlDLxsSlKQFMzUCFoyttr8= 8 | - secure: AiZP8GHbyx83ZBhOvOxxtpNcgNHoP+vo5G1a1OYU78EHCgHg8NRyHKyCdrBnPvw6mV2BI/8frZaXAEicsHMtHMofBYn7nibNlaajBPI8AkHtYfNSc+zO+71Kwv7VOTOKKnkMEIkqhHlc6njFoH3QaBNHsgNlzzplPxaIt8vdUVk= 9 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-tdd 2 | browsers: 3 | - name: chrome 4 | version: latest 5 | - name: firefox 6 | version: 24..latest 7 | - name: safari 8 | version: latest 9 | - name: ie 10 | version: 9..latest 11 | - name: iphone 12 | version: oldest..latest 13 | - name: ipad 14 | version: oldest..latest 15 | - name: android 16 | version: oldest..latest 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2014 Joyent, Inc. and other Node contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-url 2 | 3 | [![Build Status](https://travis-ci.org/defunctzombie/node-url.svg?branch=master)](https://travis-ci.org/defunctzombie/node-url) 4 | 5 | This module has utilities for URL resolution and parsing meant to have feature parity with node.js core [url](http://nodejs.org/api/url.html) module. 6 | 7 | ```js 8 | var url = require('url'); 9 | ``` 10 | 11 | ## api 12 | 13 | Parsed URL objects have some or all of the following fields, depending on 14 | whether or not they exist in the URL string. Any parts that are not in the URL 15 | string will not be in the parsed object. Examples are shown for the URL 16 | 17 | `'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'` 18 | 19 | * `href`: The full URL that was originally parsed. Both the protocol and host are lowercased. 20 | 21 | Example: `'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'` 22 | 23 | * `protocol`: The request protocol, lowercased. 24 | 25 | Example: `'http:'` 26 | 27 | * `host`: The full lowercased host portion of the URL, including port 28 | information. 29 | 30 | Example: `'host.com:8080'` 31 | 32 | * `auth`: The authentication information portion of a URL. 33 | 34 | Example: `'user:pass'` 35 | 36 | * `hostname`: Just the lowercased hostname portion of the host. 37 | 38 | Example: `'host.com'` 39 | 40 | * `port`: The port number portion of the host. 41 | 42 | Example: `'8080'` 43 | 44 | * `pathname`: The path section of the URL, that comes after the host and 45 | before the query, including the initial slash if present. 46 | 47 | Example: `'/p/a/t/h'` 48 | 49 | * `search`: The 'query string' portion of the URL, including the leading 50 | question mark. 51 | 52 | Example: `'?query=string'` 53 | 54 | * `path`: Concatenation of `pathname` and `search`. 55 | 56 | Example: `'/p/a/t/h?query=string'` 57 | 58 | * `query`: Either the 'params' portion of the query string, or a 59 | querystring-parsed object. 60 | 61 | Example: `'query=string'` or `{'query':'string'}` 62 | 63 | * `hash`: The 'fragment' portion of the URL including the pound-sign. 64 | 65 | Example: `'#hash'` 66 | 67 | The following methods are provided by the URL module: 68 | 69 | ### url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) 70 | 71 | Take a URL string, and return an object. 72 | 73 | Pass `true` as the second argument to also parse 74 | the query string using the `querystring` module. 75 | Defaults to `false`. 76 | 77 | Pass `true` as the third argument to treat `//foo/bar` as 78 | `{ host: 'foo', pathname: '/bar' }` rather than 79 | `{ pathname: '//foo/bar' }`. Defaults to `false`. 80 | 81 | ### url.format(urlObj) 82 | 83 | Take a parsed URL object, and return a formatted URL string. 84 | 85 | * `href` will be ignored. 86 | * `protocol` is treated the same with or without the trailing `:` (colon). 87 | * The protocols `http`, `https`, `ftp`, `gopher`, `file` will be 88 | postfixed with `://` (colon-slash-slash). 89 | * All other protocols `mailto`, `xmpp`, `aim`, `sftp`, `foo`, etc will 90 | be postfixed with `:` (colon) 91 | * `auth` will be used if present. 92 | * `hostname` will only be used if `host` is absent. 93 | * `port` will only be used if `host` is absent. 94 | * `host` will be used in place of `hostname` and `port` 95 | * `pathname` is treated the same with or without the leading `/` (slash) 96 | * `search` will be used in place of `query` 97 | * `query` (object; see `querystring`) will only be used if `search` is absent. 98 | * `search` is treated the same with or without the leading `?` (question mark) 99 | * `hash` is treated the same with or without the leading `#` (pound sign, anchor) 100 | 101 | ### url.resolve(from, to) 102 | 103 | Take a base URL, and a href URL, and resolve them as a browser would for 104 | an anchor tag. Examples: 105 | 106 | url.resolve('/one/two/three', 'four') // '/one/two/four' 107 | url.resolve('http://example.com/', '/one') // 'http://example.com/one' 108 | url.resolve('http://example.com/one', '/two') // 'http://example.com/two' 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url", 3 | "description": "The core `url` packaged standalone for use with Browserify.", 4 | "version": "0.11.4", 5 | "author": "defunctzombie", 6 | "dependencies": { 7 | "punycode": "^1.4.1", 8 | "qs": "^6.12.3" 9 | }, 10 | "main": "./url.js", 11 | "keywords": [ 12 | "parsing", 13 | "url", 14 | "analyze" 15 | ], 16 | "devDependencies": { 17 | "@ljharb/eslint-config": "^21.1.1", 18 | "acorn": "^8.12.1", 19 | "aud": "^2.0.4", 20 | "eslint": "=8.8.0", 21 | "mocha": "^3.5.3", 22 | "nyc": "^10.3.2", 23 | "zuul": "^3.12.0" 24 | }, 25 | "scripts": { 26 | "lint": "eslint .", 27 | "pretest": "npm run lint", 28 | "tests-only": "nyc mocha", 29 | "test": "npm run tests-only", 30 | "posttest": "aud --production", 31 | "zuul": "zuul -- test/index.js", 32 | "test-local": "zuul --local -- test/index.js" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/defunctzombie/node-url.git" 37 | }, 38 | "license": "MIT", 39 | "engines": { 40 | "node": ">= 0.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Copyright Joyent, Inc. and other Node contributors. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to permit 11 | * persons to whom the Software is furnished to do so, subject to the 12 | * following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included 15 | * in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 23 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | var test = require('mocha').test; 27 | var assert = require('assert'); 28 | var nodeURL = require('url'); 29 | 30 | var url = require('../url'); 31 | 32 | /* 33 | * URLs to parse, and expected data 34 | * { url : parsed } 35 | */ 36 | var parseTests = { 37 | '//some_path': { 38 | href: '//some_path', 39 | pathname: '//some_path', 40 | path: '//some_path' 41 | }, 42 | 43 | 'http:\\\\evil-phisher\\foo.html#h\\a\\s\\h': { 44 | protocol: 'http:', 45 | slashes: true, 46 | host: 'evil-phisher', 47 | hostname: 'evil-phisher', 48 | pathname: '/foo.html', 49 | path: '/foo.html', 50 | hash: '#h%5Ca%5Cs%5Ch', 51 | href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch' 52 | }, 53 | 54 | 'http:\\\\evil-phisher\\foo.html?json="\\"foo\\""#h\\a\\s\\h': { 55 | protocol: 'http:', 56 | slashes: true, 57 | host: 'evil-phisher', 58 | hostname: 'evil-phisher', 59 | pathname: '/foo.html', 60 | search: '?json=%22%5C%22foo%5C%22%22', 61 | query: 'json=%22%5C%22foo%5C%22%22', 62 | path: '/foo.html?json=%22%5C%22foo%5C%22%22', 63 | hash: '#h%5Ca%5Cs%5Ch', 64 | href: 'http://evil-phisher/foo.html?json=%22%5C%22foo%5C%22%22#h%5Ca%5Cs%5Ch' 65 | }, 66 | 67 | 'http:\\\\evil-phisher\\foo.html#h\\a\\s\\h?blarg': { 68 | protocol: 'http:', 69 | slashes: true, 70 | host: 'evil-phisher', 71 | hostname: 'evil-phisher', 72 | pathname: '/foo.html', 73 | path: '/foo.html', 74 | hash: '#h%5Ca%5Cs%5Ch?blarg', 75 | href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch?blarg' 76 | }, 77 | 78 | 'http:\\\\evil-phisher\\foo.html': { 79 | protocol: 'http:', 80 | slashes: true, 81 | host: 'evil-phisher', 82 | hostname: 'evil-phisher', 83 | pathname: '/foo.html', 84 | path: '/foo.html', 85 | href: 'http://evil-phisher/foo.html' 86 | }, 87 | 88 | 'HTTP://www.example.com/': { 89 | href: 'http://www.example.com/', 90 | protocol: 'http:', 91 | slashes: true, 92 | host: 'www.example.com', 93 | hostname: 'www.example.com', 94 | pathname: '/', 95 | path: '/' 96 | }, 97 | 98 | 'HTTP://www.example.com': { 99 | href: 'http://www.example.com/', 100 | protocol: 'http:', 101 | slashes: true, 102 | host: 'www.example.com', 103 | hostname: 'www.example.com', 104 | pathname: '/', 105 | path: '/' 106 | }, 107 | 108 | 'http://www.ExAmPlE.com/': { 109 | href: 'http://www.example.com/', 110 | protocol: 'http:', 111 | slashes: true, 112 | host: 'www.example.com', 113 | hostname: 'www.example.com', 114 | pathname: '/', 115 | path: '/' 116 | }, 117 | 118 | 'http://user:pw@www.ExAmPlE.com/': { 119 | href: 'http://user:pw@www.example.com/', 120 | protocol: 'http:', 121 | slashes: true, 122 | auth: 'user:pw', 123 | host: 'www.example.com', 124 | hostname: 'www.example.com', 125 | pathname: '/', 126 | path: '/' 127 | }, 128 | 129 | 'http://USER:PW@www.ExAmPlE.com/': { 130 | href: 'http://USER:PW@www.example.com/', 131 | protocol: 'http:', 132 | slashes: true, 133 | auth: 'USER:PW', 134 | host: 'www.example.com', 135 | hostname: 'www.example.com', 136 | pathname: '/', 137 | path: '/' 138 | }, 139 | 140 | 'http://user@www.example.com/': { 141 | href: 'http://user@www.example.com/', 142 | protocol: 'http:', 143 | slashes: true, 144 | auth: 'user', 145 | host: 'www.example.com', 146 | hostname: 'www.example.com', 147 | pathname: '/', 148 | path: '/' 149 | }, 150 | 151 | 'http://user%3Apw@www.example.com/': { 152 | href: 'http://user:pw@www.example.com/', 153 | protocol: 'http:', 154 | slashes: true, 155 | auth: 'user:pw', 156 | host: 'www.example.com', 157 | hostname: 'www.example.com', 158 | pathname: '/', 159 | path: '/' 160 | }, 161 | 162 | 'http://x.com/path?that\'s#all, folks': { 163 | href: 'http://x.com/path?that%27s#all,%20folks', 164 | protocol: 'http:', 165 | slashes: true, 166 | host: 'x.com', 167 | hostname: 'x.com', 168 | search: '?that%27s', 169 | query: 'that%27s', 170 | pathname: '/path', 171 | hash: '#all,%20folks', 172 | path: '/path?that%27s' 173 | }, 174 | 175 | 'HTTP://X.COM/Y': { 176 | href: 'http://x.com/Y', 177 | protocol: 'http:', 178 | slashes: true, 179 | host: 'x.com', 180 | hostname: 'x.com', 181 | pathname: '/Y', 182 | path: '/Y' 183 | }, 184 | 185 | /* 186 | * + not an invalid host character 187 | * per https://url.spec.whatwg.org/#host-parsing 188 | */ 189 | 'http://x.y.com+a/b/c': { 190 | href: 'http://x.y.com+a/b/c', 191 | protocol: 'http:', 192 | slashes: true, 193 | host: 'x.y.com+a', 194 | hostname: 'x.y.com+a', 195 | pathname: '/b/c', 196 | path: '/b/c' 197 | }, 198 | 199 | // an unexpected invalid char in the hostname. 200 | 'HtTp://x.y.cOm;a/b/c?d=e#f gi': { 201 | href: 'http://x.y.com/;a/b/c?d=e#f%20g%3Ch%3Ei', 202 | protocol: 'http:', 203 | slashes: true, 204 | host: 'x.y.com', 205 | hostname: 'x.y.com', 206 | pathname: ';a/b/c', 207 | search: '?d=e', 208 | query: 'd=e', 209 | hash: '#f%20g%3Ch%3Ei', 210 | path: ';a/b/c?d=e' 211 | }, 212 | 213 | // make sure that we don't accidentally lcast the path parts. 214 | 'HtTp://x.y.cOm;A/b/c?d=e#f gi': { 215 | href: 'http://x.y.com/;A/b/c?d=e#f%20g%3Ch%3Ei', 216 | protocol: 'http:', 217 | slashes: true, 218 | host: 'x.y.com', 219 | hostname: 'x.y.com', 220 | pathname: ';A/b/c', 221 | search: '?d=e', 222 | query: 'd=e', 223 | hash: '#f%20g%3Ch%3Ei', 224 | path: ';A/b/c?d=e' 225 | }, 226 | 227 | 'http://x...y...#p': { 228 | href: 'http://x...y.../#p', 229 | protocol: 'http:', 230 | slashes: true, 231 | host: 'x...y...', 232 | hostname: 'x...y...', 233 | hash: '#p', 234 | pathname: '/', 235 | path: '/' 236 | }, 237 | 238 | 'http://x/p/"quoted"': { 239 | href: 'http://x/p/%22quoted%22', 240 | protocol: 'http:', 241 | slashes: true, 242 | host: 'x', 243 | hostname: 'x', 244 | pathname: '/p/%22quoted%22', 245 | path: '/p/%22quoted%22' 246 | }, 247 | 248 | ' Is a URL!': { 249 | href: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', 250 | pathname: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', 251 | path: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!' 252 | }, 253 | 254 | 'http://www.narwhaljs.org/blog/categories?id=news': { 255 | href: 'http://www.narwhaljs.org/blog/categories?id=news', 256 | protocol: 'http:', 257 | slashes: true, 258 | host: 'www.narwhaljs.org', 259 | hostname: 'www.narwhaljs.org', 260 | search: '?id=news', 261 | query: 'id=news', 262 | pathname: '/blog/categories', 263 | path: '/blog/categories?id=news' 264 | }, 265 | 266 | 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=': { 267 | href: 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', 268 | protocol: 'http:', 269 | slashes: true, 270 | host: 'mt0.google.com', 271 | hostname: 'mt0.google.com', 272 | pathname: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', 273 | path: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=' 274 | }, 275 | 276 | 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': { 277 | href: 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', 278 | protocol: 'http:', 279 | slashes: true, 280 | host: 'mt0.google.com', 281 | hostname: 'mt0.google.com', 282 | search: '???&hl=en&src=api&x=2&y=2&z=3&s=', 283 | query: '??&hl=en&src=api&x=2&y=2&z=3&s=', 284 | pathname: '/vt/lyrs=m@114', 285 | path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' 286 | }, 287 | 288 | 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': { 289 | href: 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', 290 | protocol: 'http:', 291 | slashes: true, 292 | host: 'mt0.google.com', 293 | auth: 'user:pass', 294 | hostname: 'mt0.google.com', 295 | search: '???&hl=en&src=api&x=2&y=2&z=3&s=', 296 | query: '??&hl=en&src=api&x=2&y=2&z=3&s=', 297 | pathname: '/vt/lyrs=m@114', 298 | path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' 299 | }, 300 | 301 | 'file:///etc/passwd': { 302 | href: 'file:///etc/passwd', 303 | slashes: true, 304 | protocol: 'file:', 305 | pathname: '/etc/passwd', 306 | hostname: '', 307 | host: '', 308 | path: '/etc/passwd' 309 | }, 310 | 311 | 'file://localhost/etc/passwd': { 312 | href: 'file://localhost/etc/passwd', 313 | protocol: 'file:', 314 | slashes: true, 315 | pathname: '/etc/passwd', 316 | hostname: 'localhost', 317 | host: 'localhost', 318 | path: '/etc/passwd' 319 | }, 320 | 321 | 'file://foo/etc/passwd': { 322 | href: 'file://foo/etc/passwd', 323 | protocol: 'file:', 324 | slashes: true, 325 | pathname: '/etc/passwd', 326 | hostname: 'foo', 327 | host: 'foo', 328 | path: '/etc/passwd' 329 | }, 330 | 331 | 'file:///etc/node/': { 332 | href: 'file:///etc/node/', 333 | slashes: true, 334 | protocol: 'file:', 335 | pathname: '/etc/node/', 336 | hostname: '', 337 | host: '', 338 | path: '/etc/node/' 339 | }, 340 | 341 | 'file://localhost/etc/node/': { 342 | href: 'file://localhost/etc/node/', 343 | protocol: 'file:', 344 | slashes: true, 345 | pathname: '/etc/node/', 346 | hostname: 'localhost', 347 | host: 'localhost', 348 | path: '/etc/node/' 349 | }, 350 | 351 | 'file://foo/etc/node/': { 352 | href: 'file://foo/etc/node/', 353 | protocol: 'file:', 354 | slashes: true, 355 | pathname: '/etc/node/', 356 | hostname: 'foo', 357 | host: 'foo', 358 | path: '/etc/node/' 359 | }, 360 | 361 | 'http:/baz/../foo/bar': { 362 | href: 'http:/baz/../foo/bar', 363 | protocol: 'http:', 364 | pathname: '/baz/../foo/bar', 365 | path: '/baz/../foo/bar' 366 | }, 367 | 368 | 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag': { 369 | href: 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag', 370 | protocol: 'http:', 371 | slashes: true, 372 | host: 'example.com:8000', 373 | auth: 'user:pass', 374 | port: '8000', 375 | hostname: 'example.com', 376 | hash: '#frag', 377 | search: '?baz=quux', 378 | query: 'baz=quux', 379 | pathname: '/foo/bar', 380 | path: '/foo/bar?baz=quux' 381 | }, 382 | 383 | '//user:pass@example.com:8000/foo/bar?baz=quux#frag': { 384 | href: '//user:pass@example.com:8000/foo/bar?baz=quux#frag', 385 | slashes: true, 386 | host: 'example.com:8000', 387 | auth: 'user:pass', 388 | port: '8000', 389 | hostname: 'example.com', 390 | hash: '#frag', 391 | search: '?baz=quux', 392 | query: 'baz=quux', 393 | pathname: '/foo/bar', 394 | path: '/foo/bar?baz=quux' 395 | }, 396 | 397 | '/foo/bar?baz=quux#frag': { 398 | href: '/foo/bar?baz=quux#frag', 399 | hash: '#frag', 400 | search: '?baz=quux', 401 | query: 'baz=quux', 402 | pathname: '/foo/bar', 403 | path: '/foo/bar?baz=quux' 404 | }, 405 | 406 | 'http:/foo/bar?baz=quux#frag': { 407 | href: 'http:/foo/bar?baz=quux#frag', 408 | protocol: 'http:', 409 | hash: '#frag', 410 | search: '?baz=quux', 411 | query: 'baz=quux', 412 | pathname: '/foo/bar', 413 | path: '/foo/bar?baz=quux' 414 | }, 415 | 416 | 'mailto:foo@bar.com?subject=hello': { 417 | href: 'mailto:foo@bar.com?subject=hello', 418 | protocol: 'mailto:', 419 | host: 'bar.com', 420 | auth: 'foo', 421 | hostname: 'bar.com', 422 | search: '?subject=hello', 423 | query: 'subject=hello', 424 | path: '?subject=hello' 425 | }, 426 | 427 | 'javascript:alert(\'hello\');': { 428 | href: 'javascript:alert(\'hello\');', 429 | protocol: 'javascript:', 430 | pathname: 'alert(\'hello\');', 431 | path: 'alert(\'hello\');' 432 | }, 433 | 434 | 'xmpp:isaacschlueter@jabber.org': { 435 | href: 'xmpp:isaacschlueter@jabber.org', 436 | protocol: 'xmpp:', 437 | host: 'jabber.org', 438 | auth: 'isaacschlueter', 439 | hostname: 'jabber.org' 440 | }, 441 | 442 | 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar': { 443 | href: 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', 444 | protocol: 'http:', 445 | slashes: true, 446 | host: '127.0.0.1:8080', 447 | auth: 'atpass:foo@bar', 448 | hostname: '127.0.0.1', 449 | port: '8080', 450 | pathname: '/path', 451 | search: '?search=foo', 452 | query: 'search=foo', 453 | hash: '#bar', 454 | path: '/path?search=foo' 455 | }, 456 | 457 | 'svn+ssh://foo/bar': { 458 | href: 'svn+ssh://foo/bar', 459 | host: 'foo', 460 | hostname: 'foo', 461 | protocol: 'svn+ssh:', 462 | pathname: '/bar', 463 | path: '/bar', 464 | slashes: true 465 | }, 466 | 467 | 'dash-test://foo/bar': { 468 | href: 'dash-test://foo/bar', 469 | host: 'foo', 470 | hostname: 'foo', 471 | protocol: 'dash-test:', 472 | pathname: '/bar', 473 | path: '/bar', 474 | slashes: true 475 | }, 476 | 477 | 'dash-test:foo/bar': { 478 | href: 'dash-test:foo/bar', 479 | host: 'foo', 480 | hostname: 'foo', 481 | protocol: 'dash-test:', 482 | pathname: '/bar', 483 | path: '/bar' 484 | }, 485 | 486 | 'dot.test://foo/bar': { 487 | href: 'dot.test://foo/bar', 488 | host: 'foo', 489 | hostname: 'foo', 490 | protocol: 'dot.test:', 491 | pathname: '/bar', 492 | path: '/bar', 493 | slashes: true 494 | }, 495 | 496 | 'dot.test:foo/bar': { 497 | href: 'dot.test:foo/bar', 498 | host: 'foo', 499 | hostname: 'foo', 500 | protocol: 'dot.test:', 501 | pathname: '/bar', 502 | path: '/bar' 503 | }, 504 | 505 | // IDNA tests 506 | 'http://www.日本語.com/': { 507 | href: 'http://www.xn--wgv71a119e.com/', 508 | protocol: 'http:', 509 | slashes: true, 510 | host: 'www.xn--wgv71a119e.com', 511 | hostname: 'www.xn--wgv71a119e.com', 512 | pathname: '/', 513 | path: '/' 514 | }, 515 | 516 | 'http://example.Bücher.com/': { 517 | href: 'http://example.xn--bcher-kva.com/', 518 | protocol: 'http:', 519 | slashes: true, 520 | host: 'example.xn--bcher-kva.com', 521 | hostname: 'example.xn--bcher-kva.com', 522 | pathname: '/', 523 | path: '/' 524 | }, 525 | 526 | 'http://www.Äffchen.com/': { 527 | href: 'http://www.xn--ffchen-9ta.com/', 528 | protocol: 'http:', 529 | slashes: true, 530 | host: 'www.xn--ffchen-9ta.com', 531 | hostname: 'www.xn--ffchen-9ta.com', 532 | pathname: '/', 533 | path: '/' 534 | }, 535 | 536 | 'http://www.Äffchen.cOm;A/b/c?d=e#f gi': { 537 | href: 'http://www.xn--ffchen-9ta.com/;A/b/c?d=e#f%20g%3Ch%3Ei', 538 | protocol: 'http:', 539 | slashes: true, 540 | host: 'www.xn--ffchen-9ta.com', 541 | hostname: 'www.xn--ffchen-9ta.com', 542 | pathname: ';A/b/c', 543 | search: '?d=e', 544 | query: 'd=e', 545 | hash: '#f%20g%3Ch%3Ei', 546 | path: ';A/b/c?d=e' 547 | }, 548 | 549 | 'http://SÉLIER.COM/': { 550 | href: 'http://xn--slier-bsa.com/', 551 | protocol: 'http:', 552 | slashes: true, 553 | host: 'xn--slier-bsa.com', 554 | hostname: 'xn--slier-bsa.com', 555 | pathname: '/', 556 | path: '/' 557 | }, 558 | 559 | 'http://ليهمابتكلموشعربي؟.ي؟/': { 560 | href: 'http://xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f/', 561 | protocol: 'http:', 562 | slashes: true, 563 | host: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f', 564 | hostname: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f', 565 | pathname: '/', 566 | path: '/' 567 | }, 568 | 569 | 'http://➡.ws/➡': { 570 | href: 'http://xn--hgi.ws/➡', 571 | protocol: 'http:', 572 | slashes: true, 573 | host: 'xn--hgi.ws', 574 | hostname: 'xn--hgi.ws', 575 | pathname: '/➡', 576 | path: '/➡' 577 | }, 578 | 579 | 'http://bucket_name.s3.amazonaws.com/image.jpg': { 580 | protocol: 'http:', 581 | slashes: true, 582 | host: 'bucket_name.s3.amazonaws.com', 583 | hostname: 'bucket_name.s3.amazonaws.com', 584 | pathname: '/image.jpg', 585 | href: 'http://bucket_name.s3.amazonaws.com/image.jpg', 586 | path: '/image.jpg' 587 | }, 588 | 589 | 'git+http://github.com/joyent/node.git': { 590 | protocol: 'git+http:', 591 | slashes: true, 592 | host: 'github.com', 593 | hostname: 'github.com', 594 | pathname: '/joyent/node.git', 595 | path: '/joyent/node.git', 596 | href: 'git+http://github.com/joyent/node.git' 597 | }, 598 | 599 | /* 600 | * if local1@domain1 is uses as a relative URL it may 601 | * be parse into auth@hostname, but here there is no 602 | * way to make it work in url.parse, I add the test to be explicit 603 | */ 604 | 'local1@domain1': { 605 | pathname: 'local1@domain1', 606 | path: 'local1@domain1', 607 | href: 'local1@domain1' 608 | }, 609 | 610 | /* 611 | * While this may seem counter-intuitive, a browser will parse 612 | * as a path. 613 | */ 614 | 'www.example.com': { 615 | href: 'www.example.com', 616 | pathname: 'www.example.com', 617 | path: 'www.example.com' 618 | }, 619 | 620 | // ipv6 support 621 | '[fe80::1]': { 622 | href: '[fe80::1]', 623 | pathname: '[fe80::1]', 624 | path: '[fe80::1]' 625 | }, 626 | 627 | 'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]': { 628 | protocol: 'coap:', 629 | slashes: true, 630 | host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]', 631 | hostname: 'fedc:ba98:7654:3210:fedc:ba98:7654:3210', 632 | href: 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/', 633 | pathname: '/', 634 | path: '/' 635 | }, 636 | 637 | 'coap://[1080:0:0:0:8:800:200C:417A]:61616/': { 638 | protocol: 'coap:', 639 | slashes: true, 640 | host: '[1080:0:0:0:8:800:200c:417a]:61616', 641 | port: '61616', 642 | hostname: '1080:0:0:0:8:800:200c:417a', 643 | href: 'coap://[1080:0:0:0:8:800:200c:417a]:61616/', 644 | pathname: '/', 645 | path: '/' 646 | }, 647 | 648 | 'http://user:password@[3ffe:2a00:100:7031::1]:8080': { 649 | protocol: 'http:', 650 | slashes: true, 651 | auth: 'user:password', 652 | host: '[3ffe:2a00:100:7031::1]:8080', 653 | port: '8080', 654 | hostname: '3ffe:2a00:100:7031::1', 655 | href: 'http://user:password@[3ffe:2a00:100:7031::1]:8080/', 656 | pathname: '/', 657 | path: '/' 658 | }, 659 | 660 | 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature': { 661 | protocol: 'coap:', 662 | slashes: true, 663 | auth: 'u:p', 664 | host: '[::192.9.5.5]:61616', 665 | port: '61616', 666 | hostname: '::192.9.5.5', 667 | href: 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature', 668 | search: '?n=Temperature', 669 | query: 'n=Temperature', 670 | pathname: '/.well-known/r', 671 | path: '/.well-known/r?n=Temperature' 672 | }, 673 | 674 | // empty port 675 | 'http://example.com:': { 676 | protocol: 'http:', 677 | slashes: true, 678 | host: 'example.com', 679 | hostname: 'example.com', 680 | href: 'http://example.com/', 681 | pathname: '/', 682 | path: '/' 683 | }, 684 | 685 | 'http://example.com:/a/b.html': { 686 | protocol: 'http:', 687 | slashes: true, 688 | host: 'example.com', 689 | hostname: 'example.com', 690 | href: 'http://example.com/a/b.html', 691 | pathname: '/a/b.html', 692 | path: '/a/b.html' 693 | }, 694 | 695 | 'http://example.com:?a=b': { 696 | protocol: 'http:', 697 | slashes: true, 698 | host: 'example.com', 699 | hostname: 'example.com', 700 | href: 'http://example.com/?a=b', 701 | search: '?a=b', 702 | query: 'a=b', 703 | pathname: '/', 704 | path: '/?a=b' 705 | }, 706 | 707 | 'http://example.com:#abc': { 708 | protocol: 'http:', 709 | slashes: true, 710 | host: 'example.com', 711 | hostname: 'example.com', 712 | href: 'http://example.com/#abc', 713 | hash: '#abc', 714 | pathname: '/', 715 | path: '/' 716 | }, 717 | 718 | 'http://[fe80::1]:/a/b?a=b#abc': { 719 | protocol: 'http:', 720 | slashes: true, 721 | host: '[fe80::1]', 722 | hostname: 'fe80::1', 723 | href: 'http://[fe80::1]/a/b?a=b#abc', 724 | search: '?a=b', 725 | query: 'a=b', 726 | hash: '#abc', 727 | pathname: '/a/b', 728 | path: '/a/b?a=b' 729 | }, 730 | 731 | 'http://-lovemonsterz.tumblr.com/rss': { 732 | protocol: 'http:', 733 | slashes: true, 734 | host: '-lovemonsterz.tumblr.com', 735 | hostname: '-lovemonsterz.tumblr.com', 736 | href: 'http://-lovemonsterz.tumblr.com/rss', 737 | pathname: '/rss', 738 | path: '/rss' 739 | }, 740 | 741 | 'http://-lovemonsterz.tumblr.com:80/rss': { 742 | protocol: 'http:', 743 | slashes: true, 744 | port: '80', 745 | host: '-lovemonsterz.tumblr.com:80', 746 | hostname: '-lovemonsterz.tumblr.com', 747 | href: 'http://-lovemonsterz.tumblr.com:80/rss', 748 | pathname: '/rss', 749 | path: '/rss' 750 | }, 751 | 752 | 'http://user:pass@-lovemonsterz.tumblr.com/rss': { 753 | protocol: 'http:', 754 | slashes: true, 755 | auth: 'user:pass', 756 | host: '-lovemonsterz.tumblr.com', 757 | hostname: '-lovemonsterz.tumblr.com', 758 | href: 'http://user:pass@-lovemonsterz.tumblr.com/rss', 759 | pathname: '/rss', 760 | path: '/rss' 761 | }, 762 | 763 | 'http://user:pass@-lovemonsterz.tumblr.com:80/rss': { 764 | protocol: 'http:', 765 | slashes: true, 766 | auth: 'user:pass', 767 | port: '80', 768 | host: '-lovemonsterz.tumblr.com:80', 769 | hostname: '-lovemonsterz.tumblr.com', 770 | href: 'http://user:pass@-lovemonsterz.tumblr.com:80/rss', 771 | pathname: '/rss', 772 | path: '/rss' 773 | }, 774 | 775 | 'http://_jabber._tcp.google.com/test': { 776 | protocol: 'http:', 777 | slashes: true, 778 | host: '_jabber._tcp.google.com', 779 | hostname: '_jabber._tcp.google.com', 780 | href: 'http://_jabber._tcp.google.com/test', 781 | pathname: '/test', 782 | path: '/test' 783 | }, 784 | 785 | 'http://user:pass@_jabber._tcp.google.com/test': { 786 | protocol: 'http:', 787 | slashes: true, 788 | auth: 'user:pass', 789 | host: '_jabber._tcp.google.com', 790 | hostname: '_jabber._tcp.google.com', 791 | href: 'http://user:pass@_jabber._tcp.google.com/test', 792 | pathname: '/test', 793 | path: '/test' 794 | }, 795 | 796 | 'http://_jabber._tcp.google.com:80/test': { 797 | protocol: 'http:', 798 | slashes: true, 799 | port: '80', 800 | host: '_jabber._tcp.google.com:80', 801 | hostname: '_jabber._tcp.google.com', 802 | href: 'http://_jabber._tcp.google.com:80/test', 803 | pathname: '/test', 804 | path: '/test' 805 | }, 806 | 807 | 'http://user:pass@_jabber._tcp.google.com:80/test': { 808 | protocol: 'http:', 809 | slashes: true, 810 | auth: 'user:pass', 811 | port: '80', 812 | host: '_jabber._tcp.google.com:80', 813 | hostname: '_jabber._tcp.google.com', 814 | href: 'http://user:pass@_jabber._tcp.google.com:80/test', 815 | pathname: '/test', 816 | path: '/test' 817 | }, 818 | 819 | 'http://x:1/\' <>"`/{}|\\^~`/': { 820 | protocol: 'http:', 821 | slashes: true, 822 | host: 'x:1', 823 | port: '1', 824 | hostname: 'x', 825 | pathname: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/', 826 | path: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/', 827 | href: 'http://x:1/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/' 828 | }, 829 | 830 | 'http://a@b@c/': { 831 | protocol: 'http:', 832 | slashes: true, 833 | auth: 'a@b', 834 | host: 'c', 835 | hostname: 'c', 836 | href: 'http://a%40b@c/', 837 | path: '/', 838 | pathname: '/' 839 | }, 840 | 841 | 'http://a@b?@c': { 842 | protocol: 'http:', 843 | slashes: true, 844 | auth: 'a', 845 | host: 'b', 846 | hostname: 'b', 847 | href: 'http://a@b/?@c', 848 | path: '/?@c', 849 | pathname: '/', 850 | search: '?@c', 851 | query: '@c' 852 | }, 853 | 854 | 'http://a\r" \t\n<\'b:b@c\r\nd/e?f': { 855 | protocol: 'http:', 856 | slashes: true, 857 | auth: 'a\r" \t\n<\'b:b', 858 | host: 'c', 859 | port: null, 860 | hostname: 'c', 861 | hash: null, 862 | search: '?f', 863 | query: 'f', 864 | pathname: '%0D%0Ad/e', 865 | path: '%0D%0Ad/e?f', 866 | href: 'http://a%0D%22%20%09%0A%3C\'b:b@c/%0D%0Ad/e?f' 867 | }, 868 | 869 | // git urls used by npm 870 | 'git+ssh://git@github.com:npm/npm': { 871 | protocol: 'git+ssh:', 872 | slashes: true, 873 | auth: 'git', 874 | host: 'github.com', 875 | port: null, 876 | hostname: 'github.com', 877 | hash: null, 878 | search: null, 879 | query: null, 880 | pathname: '/:npm/npm', 881 | path: '/:npm/npm', 882 | href: 'git+ssh://git@github.com/:npm/npm' 883 | } 884 | 885 | }; 886 | 887 | Object.keys(parseTests).forEach(function (u) { 888 | test('parse(' + u + ')', function () { 889 | var actual = url.parse(u); 890 | var spaced = url.parse(' \t ' + u + '\n\t'); 891 | var expected = parseTests[u]; 892 | 893 | Object.keys(actual).forEach(function (i) { 894 | if (expected[i] === undefined && actual[i] === null) { 895 | expected[i] = null; 896 | } 897 | }); 898 | 899 | assert.deepEqual(actual, expected); 900 | assert.deepEqual(spaced, expected); 901 | 902 | var expected = parseTests[u].href, 903 | actual = url.format(parseTests[u]); 904 | 905 | assert.equal(actual, expected, 'format(' + u + ') == ' + u + '\nactual:' + actual); 906 | }); 907 | }); 908 | 909 | var parseTestsWithQueryString = { 910 | '/foo/bar?baz=quux#frag': { 911 | href: '/foo/bar?baz=quux#frag', 912 | hash: '#frag', 913 | search: '?baz=quux', 914 | query: { 915 | baz: 'quux' 916 | }, 917 | pathname: '/foo/bar', 918 | path: '/foo/bar?baz=quux' 919 | }, 920 | 'http://example.com': { 921 | href: 'http://example.com/', 922 | protocol: 'http:', 923 | slashes: true, 924 | host: 'example.com', 925 | hostname: 'example.com', 926 | query: {}, 927 | search: '', 928 | pathname: '/', 929 | path: '/' 930 | }, 931 | '/example': { 932 | protocol: null, 933 | slashes: null, 934 | auth: null, 935 | host: null, 936 | port: null, 937 | hostname: null, 938 | hash: null, 939 | search: '', 940 | query: {}, 941 | pathname: '/example', 942 | path: '/example', 943 | href: '/example' 944 | }, 945 | '/example?query=value': { 946 | protocol: null, 947 | slashes: null, 948 | auth: null, 949 | host: null, 950 | port: null, 951 | hostname: null, 952 | hash: null, 953 | search: '?query=value', 954 | query: { query: 'value' }, 955 | pathname: '/example', 956 | path: '/example?query=value', 957 | href: '/example?query=value' 958 | } 959 | }; 960 | 961 | Object.keys(parseTestsWithQueryString).forEach(function (u) { 962 | test('parse(' + u + ')', function () { 963 | var actual = url.parse(u, true); 964 | var expected = parseTestsWithQueryString[u]; 965 | for (var i in actual) { 966 | if (actual[i] === null && expected[i] === undefined) { 967 | expected[i] = null; 968 | } 969 | } 970 | 971 | assert.deepEqual(actual, expected); 972 | }); 973 | }); 974 | 975 | /* 976 | * some extra formatting tests, just to verify 977 | * that it'll format slightly wonky content to a valid url. 978 | */ 979 | var formatTests = { 980 | 'http://example.com?': { 981 | href: 'http://example.com/?', 982 | protocol: 'http:', 983 | slashes: true, 984 | host: 'example.com', 985 | hostname: 'example.com', 986 | search: '?', 987 | query: {}, 988 | pathname: '/' 989 | }, 990 | 'http://example.com?foo=bar#frag': { 991 | href: 'http://example.com/?foo=bar#frag', 992 | protocol: 'http:', 993 | host: 'example.com', 994 | hostname: 'example.com', 995 | hash: '#frag', 996 | search: '?foo=bar', 997 | query: 'foo=bar', 998 | pathname: '/' 999 | }, 1000 | 'http://example.com?foo=@bar#frag': { 1001 | href: 'http://example.com/?foo=@bar#frag', 1002 | protocol: 'http:', 1003 | host: 'example.com', 1004 | hostname: 'example.com', 1005 | hash: '#frag', 1006 | search: '?foo=@bar', 1007 | query: 'foo=@bar', 1008 | pathname: '/' 1009 | }, 1010 | 'http://example.com?foo=/bar/#frag': { 1011 | href: 'http://example.com/?foo=/bar/#frag', 1012 | protocol: 'http:', 1013 | host: 'example.com', 1014 | hostname: 'example.com', 1015 | hash: '#frag', 1016 | search: '?foo=/bar/', 1017 | query: 'foo=/bar/', 1018 | pathname: '/' 1019 | }, 1020 | 'http://example.com?foo=?bar/#frag': { 1021 | href: 'http://example.com/?foo=?bar/#frag', 1022 | protocol: 'http:', 1023 | host: 'example.com', 1024 | hostname: 'example.com', 1025 | hash: '#frag', 1026 | search: '?foo=?bar/', 1027 | query: 'foo=?bar/', 1028 | pathname: '/' 1029 | }, 1030 | 'http://example.com#frag=?bar/#frag': { 1031 | href: 'http://example.com/#frag=?bar/#frag', 1032 | protocol: 'http:', 1033 | host: 'example.com', 1034 | hostname: 'example.com', 1035 | hash: '#frag=?bar/#frag', 1036 | pathname: '/' 1037 | }, 1038 | 'http://google.com" onload="alert(42)/': { 1039 | href: 'http://google.com/%22%20onload=%22alert(42)/', 1040 | protocol: 'http:', 1041 | host: 'google.com', 1042 | pathname: '/%22%20onload=%22alert(42)/' 1043 | }, 1044 | 'http://a.com/a/b/c?s#h': { 1045 | href: 'http://a.com/a/b/c?s#h', 1046 | protocol: 'http', 1047 | host: 'a.com', 1048 | pathname: 'a/b/c', 1049 | hash: 'h', 1050 | search: 's' 1051 | }, 1052 | 'xmpp:isaacschlueter@jabber.org': { 1053 | href: 'xmpp:isaacschlueter@jabber.org', 1054 | protocol: 'xmpp:', 1055 | host: 'jabber.org', 1056 | auth: 'isaacschlueter', 1057 | hostname: 'jabber.org' 1058 | }, 1059 | 'http://atpass:foo%40bar@127.0.0.1/': { 1060 | href: 'http://atpass:foo%40bar@127.0.0.1/', 1061 | auth: 'atpass:foo@bar', 1062 | hostname: '127.0.0.1', 1063 | protocol: 'http:', 1064 | pathname: '/' 1065 | }, 1066 | 'http://atslash%2F%40:%2F%40@foo/': { 1067 | href: 'http://atslash%2F%40:%2F%40@foo/', 1068 | auth: 'atslash/@:/@', 1069 | hostname: 'foo', 1070 | protocol: 'http:', 1071 | pathname: '/' 1072 | }, 1073 | 'svn+ssh://foo/bar': { 1074 | href: 'svn+ssh://foo/bar', 1075 | hostname: 'foo', 1076 | protocol: 'svn+ssh:', 1077 | pathname: '/bar', 1078 | slashes: true 1079 | }, 1080 | 'dash-test://foo/bar': { 1081 | href: 'dash-test://foo/bar', 1082 | hostname: 'foo', 1083 | protocol: 'dash-test:', 1084 | pathname: '/bar', 1085 | slashes: true 1086 | }, 1087 | 'dash-test:foo/bar': { 1088 | href: 'dash-test:foo/bar', 1089 | hostname: 'foo', 1090 | protocol: 'dash-test:', 1091 | pathname: '/bar' 1092 | }, 1093 | 'dot.test://foo/bar': { 1094 | href: 'dot.test://foo/bar', 1095 | hostname: 'foo', 1096 | protocol: 'dot.test:', 1097 | pathname: '/bar', 1098 | slashes: true 1099 | }, 1100 | 'dot.test:foo/bar': { 1101 | href: 'dot.test:foo/bar', 1102 | hostname: 'foo', 1103 | protocol: 'dot.test:', 1104 | pathname: '/bar' 1105 | }, 1106 | // ipv6 support 1107 | 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature': { 1108 | href: 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature', 1109 | protocol: 'coap:', 1110 | auth: 'u:p', 1111 | hostname: '::1', 1112 | port: '61616', 1113 | pathname: '/.well-known/r', 1114 | search: 'n=Temperature' 1115 | }, 1116 | 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton': { 1117 | href: 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton', 1118 | protocol: 'coap', 1119 | host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616', 1120 | pathname: '/s/stopButton' 1121 | }, 1122 | 1123 | /* 1124 | * encode context-specific delimiters in path and query, but do not touch 1125 | * other non-delimiter chars like `%`. 1126 | * 1127 | */ 1128 | 1129 | // `#`,`?` in path 1130 | '/path/to/%%23%3F+=&.txt?foo=theA1#bar': { 1131 | href: '/path/to/%%23%3F+=&.txt?foo=theA1#bar', 1132 | pathname: '/path/to/%#?+=&.txt', 1133 | query: { 1134 | foo: 'theA1' 1135 | }, 1136 | hash: '#bar' 1137 | }, 1138 | 1139 | // `#`,`?` in path + `#` in query 1140 | '/path/to/%%23%3F+=&.txt?foo=the%231#bar': { 1141 | href: '/path/to/%%23%3F+=&.txt?foo=the%231#bar', 1142 | pathname: '/path/to/%#?+=&.txt', 1143 | query: { 1144 | foo: 'the#1' 1145 | }, 1146 | hash: '#bar' 1147 | }, 1148 | 1149 | // `?` and `#` in path and search 1150 | 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag': { 1151 | href: 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag', 1152 | protocol: 'http:', 1153 | hostname: 'ex.com', 1154 | hash: '#frag', 1155 | search: '?abc=the#1?&foo=bar', 1156 | pathname: '/foo?100%m#r' 1157 | }, 1158 | 1159 | // `?` and `#` in search only 1160 | 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag': { 1161 | href: 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag', 1162 | protocol: 'http:', 1163 | hostname: 'ex.com', 1164 | hash: '#frag', 1165 | search: '?abc=the#1?&foo=bar', 1166 | pathname: '/fooA100%mBr' 1167 | }, 1168 | 1169 | // colons in keys 1170 | 'https://cdpn.io/test?custom%3Aid=12': { 1171 | href: 'https://cdpn.io/test?custom%3Aid=12', 1172 | protocol: 'https:', 1173 | hostname: 'cdpn.io', 1174 | hash: '', 1175 | search: '?custom%3Aid=12', 1176 | pathname: '/test', 1177 | query: { 1178 | 'custom:id': '12' 1179 | } 1180 | } 1181 | }; 1182 | 1183 | Object.keys(formatTests).forEach(function (u) { 1184 | test('format(' + u + ')', function () { 1185 | var expect = formatTests[u].href; 1186 | delete formatTests[u].href; 1187 | var actual = url.format(u); 1188 | var actualObj = url.format(formatTests[u]); 1189 | assert.equal(actual, expect, 'wonky format(' + u + ') == ' + expect + '\nactual:' + actual); 1190 | assert.equal(actualObj, expect, 'wonky format(' + JSON.stringify(formatTests[u]) + ') == ' + expect + '\nactual: ' + actualObj); 1191 | }); 1192 | }); 1193 | 1194 | /* 1195 | *[from, path, expected] 1196 | */ 1197 | var relativeTests = [ 1198 | [ 1199 | '/foo/bar/baz', 'quux', '/foo/bar/quux' 1200 | ], 1201 | [ 1202 | '/foo/bar/baz', 'quux/asdf', '/foo/bar/quux/asdf' 1203 | ], 1204 | [ 1205 | '/foo/bar/baz', 'quux/baz', '/foo/bar/quux/baz' 1206 | ], 1207 | [ 1208 | '/foo/bar/baz', '../quux/baz', '/foo/quux/baz' 1209 | ], 1210 | [ 1211 | '/foo/bar/baz', '/bar', '/bar' 1212 | ], 1213 | [ 1214 | '/foo/bar/baz/', 'quux', '/foo/bar/baz/quux' 1215 | ], 1216 | [ 1217 | '/foo/bar/baz/', 'quux/baz', '/foo/bar/baz/quux/baz' 1218 | ], 1219 | [ 1220 | '/foo/bar/baz', '../../../../../../../../quux/baz', '/quux/baz' 1221 | ], 1222 | [ 1223 | '/foo/bar/baz', '../../../../../../../quux/baz', '/quux/baz' 1224 | ], 1225 | [ 1226 | '/foo', '.', '/' 1227 | ], 1228 | [ 1229 | '/foo', '..', '/' 1230 | ], 1231 | [ 1232 | '/foo/', '.', '/foo/' 1233 | ], 1234 | [ 1235 | '/foo/', '..', '/' 1236 | ], 1237 | [ 1238 | '/foo/bar', '.', '/foo/' 1239 | ], 1240 | [ 1241 | '/foo/bar', '..', '/' 1242 | ], 1243 | [ 1244 | '/foo/bar/', '.', '/foo/bar/' 1245 | ], 1246 | [ 1247 | '/foo/bar/', '..', '/foo/' 1248 | ], 1249 | [ 1250 | 'foo/bar', '../../../baz', '../../baz' 1251 | ], 1252 | [ 1253 | 'foo/bar/', '../../../baz', '../baz' 1254 | ], 1255 | [ 1256 | 'http://example.com/b//c//d;p?q#blarg', 'https:#hash2', 'https:///#hash2' 1257 | ], 1258 | [ 1259 | 'http://example.com/b//c//d;p?q#blarg', 1260 | 'https:/p/a/t/h?s#hash2', 1261 | 'https://p/a/t/h?s#hash2' 1262 | ], 1263 | [ 1264 | 'http://example.com/b//c//d;p?q#blarg', 1265 | 'https://u:p@h.com/p/a/t/h?s#hash2', 1266 | 'https://u:p@h.com/p/a/t/h?s#hash2' 1267 | ], 1268 | [ 1269 | 'http://example.com/b//c//d;p?q#blarg', 1270 | 'https:/a/b/c/d', 1271 | 'https://a/b/c/d' 1272 | ], 1273 | [ 1274 | 'http://example.com/b//c//d;p?q#blarg', 1275 | 'http:#hash2', 1276 | 'http://example.com/b//c//d;p?q#hash2' 1277 | ], 1278 | [ 1279 | 'http://example.com/b//c//d;p?q#blarg', 1280 | 'http:/p/a/t/h?s#hash2', 1281 | 'http://example.com/p/a/t/h?s#hash2' 1282 | ], 1283 | [ 1284 | 'http://example.com/b//c//d;p?q#blarg', 1285 | 'http://u:p@h.com/p/a/t/h?s#hash2', 1286 | 'http://u:p@h.com/p/a/t/h?s#hash2' 1287 | ], 1288 | [ 1289 | 'http://example.com/b//c//d;p?q#blarg', 1290 | 'http:/a/b/c/d', 1291 | 'http://example.com/a/b/c/d' 1292 | ], 1293 | [ 1294 | '/foo/bar/baz', '/../etc/passwd', '/etc/passwd' 1295 | ] 1296 | ]; 1297 | 1298 | relativeTests.forEach(function (relativeTest) { 1299 | test('resolve(' + [relativeTest[0], relativeTest[1]] + ')', function () { 1300 | var a = url.resolve(relativeTest[0], relativeTest[1]), 1301 | e = relativeTest[2]; 1302 | assert.equal(a, e, 'resolve(' + [relativeTest[0], relativeTest[1]] + ') == ' + e + '\n actual=' + a); 1303 | }); 1304 | }); 1305 | 1306 | // https://github.com/joyent/node/issues/568 1307 | [ 1308 | undefined, 1309 | null, 1310 | true, 1311 | false, 1312 | 0.0, 1313 | 0, 1314 | [], 1315 | {} 1316 | ].forEach(function (val) { 1317 | test('parse(' + val + ')', function () { 1318 | assert['throws'](function () { url.parse(val); }, TypeError); 1319 | }); 1320 | }); 1321 | 1322 | /* 1323 | * 1324 | * Tests below taken from Chiron 1325 | * http://code.google.com/p/chironjs/source/browse/trunk/src/test/http/url.js 1326 | * 1327 | * Copyright (c) 2002-2008 Kris Kowal 1328 | * used with permission under MIT License 1329 | * 1330 | * Changes marked with @isaacs 1331 | */ 1332 | 1333 | var bases = [ 1334 | 'http://a/b/c/d;p?q', 1335 | 'http://a/b/c/d;p?q=1/2', 1336 | 'http://a/b/c/d;p=1/2?q', 1337 | 'fred:///s//a/b/c', 1338 | 'http:///s//a/b/c' 1339 | ]; 1340 | 1341 | // [to, from, result] 1342 | var relativeTests2 = [ 1343 | // http://lists.w3.org/Archives/Public/uri/2004Feb/0114.html 1344 | [ 1345 | '../c', 'foo:a/b', 'foo:c' 1346 | ], 1347 | [ 1348 | 'foo:.', 'foo:a', 'foo:' 1349 | ], 1350 | [ 1351 | '/foo/../../../bar', 'zz:abc', 'zz:/bar' 1352 | ], 1353 | [ 1354 | '/foo/../bar', 'zz:abc', 'zz:/bar' 1355 | ], 1356 | // @isaacs Disagree. Not how web browsers resolve this. 1357 | [ 1358 | 'foo/../../../bar', 'zz:abc', 'zz:bar' 1359 | ], 1360 | // ['foo/../../../bar', 'zz:abc', 'zz:../../bar'], // @isaacs Added 1361 | [ 1362 | 'foo/../bar', 'zz:abc', 'zz:bar' 1363 | ], 1364 | [ 1365 | 'zz:.', 'zz:abc', 'zz:' 1366 | ], 1367 | [ 1368 | '/.', bases[0], 'http://a/' 1369 | ], 1370 | [ 1371 | '/.foo', bases[0], 'http://a/.foo' 1372 | ], 1373 | [ 1374 | '.foo', bases[0], 'http://a/b/c/.foo' 1375 | ], 1376 | 1377 | /* 1378 | * http://gbiv.com/protocols/uri/test/rel_examples1.html 1379 | * examples from RFC 2396 1380 | */ 1381 | [ 1382 | 'g:h', bases[0], 'g:h' 1383 | ], 1384 | [ 1385 | 'g', bases[0], 'http://a/b/c/g' 1386 | ], 1387 | [ 1388 | './g', bases[0], 'http://a/b/c/g' 1389 | ], 1390 | [ 1391 | 'g/', bases[0], 'http://a/b/c/g/' 1392 | ], 1393 | [ 1394 | '/g', bases[0], 'http://a/g' 1395 | ], 1396 | [ 1397 | '//g', bases[0], 'http://g/' 1398 | ], 1399 | /* 1400 | * changed with RFC 2396bis 1401 | * ('?y', bases[0], 'http://a/b/c/d;p?y'], 1402 | */ 1403 | [ 1404 | '?y', bases[0], 'http://a/b/c/d;p?y' 1405 | ], 1406 | [ 1407 | 'g?y', bases[0], 'http://a/b/c/g?y' 1408 | ], 1409 | /* 1410 | * changed with RFC 2396bis 1411 | * ('#s', bases[0], CURRENT_DOC_URI + '#s'], 1412 | */ 1413 | [ 1414 | '#s', bases[0], 'http://a/b/c/d;p?q#s' 1415 | ], 1416 | [ 1417 | 'g#s', bases[0], 'http://a/b/c/g#s' 1418 | ], 1419 | [ 1420 | 'g?y#s', bases[0], 'http://a/b/c/g?y#s' 1421 | ], 1422 | [ 1423 | ';x', bases[0], 'http://a/b/c/;x' 1424 | ], 1425 | [ 1426 | 'g;x', bases[0], 'http://a/b/c/g;x' 1427 | ], 1428 | [ 1429 | 'g;x?y#s', bases[0], 'http://a/b/c/g;x?y#s' 1430 | ], 1431 | /* 1432 | * changed with RFC 2396bis 1433 | * ('', bases[0], CURRENT_DOC_URI], 1434 | */ 1435 | [ 1436 | '', bases[0], 'http://a/b/c/d;p?q' 1437 | ], 1438 | [ 1439 | '.', bases[0], 'http://a/b/c/' 1440 | ], 1441 | [ 1442 | './', bases[0], 'http://a/b/c/' 1443 | ], 1444 | [ 1445 | '..', bases[0], 'http://a/b/' 1446 | ], 1447 | [ 1448 | '../', bases[0], 'http://a/b/' 1449 | ], 1450 | [ 1451 | '../g', bases[0], 'http://a/b/g' 1452 | ], 1453 | [ 1454 | '../..', bases[0], 'http://a/' 1455 | ], 1456 | [ 1457 | '../../', bases[0], 'http://a/' 1458 | ], 1459 | [ 1460 | '../../g', bases[0], 'http://a/g' 1461 | ], 1462 | [ 1463 | '../../../g', bases[0], ('http://a/../g', 'http://a/g') 1464 | ], 1465 | [ 1466 | '../../../../g', bases[0], ('http://a/../../g', 'http://a/g') 1467 | ], 1468 | /* 1469 | * changed with RFC 2396bis 1470 | * ('/./g', bases[0], 'http://a/./g'], 1471 | */ 1472 | [ 1473 | '/./g', bases[0], 'http://a/g' 1474 | ], 1475 | /* 1476 | * changed with RFC 2396bis 1477 | * ('/../g', bases[0], 'http://a/../g'], 1478 | */ 1479 | [ 1480 | '/../g', bases[0], 'http://a/g' 1481 | ], 1482 | [ 1483 | 'g.', bases[0], 'http://a/b/c/g.' 1484 | ], 1485 | [ 1486 | '.g', bases[0], 'http://a/b/c/.g' 1487 | ], 1488 | [ 1489 | 'g..', bases[0], 'http://a/b/c/g..' 1490 | ], 1491 | [ 1492 | '..g', bases[0], 'http://a/b/c/..g' 1493 | ], 1494 | [ 1495 | './../g', bases[0], 'http://a/b/g' 1496 | ], 1497 | [ 1498 | './g/.', bases[0], 'http://a/b/c/g/' 1499 | ], 1500 | [ 1501 | 'g/./h', bases[0], 'http://a/b/c/g/h' 1502 | ], 1503 | [ 1504 | 'g/../h', bases[0], 'http://a/b/c/h' 1505 | ], 1506 | [ 1507 | 'g;x=1/./y', bases[0], 'http://a/b/c/g;x=1/y' 1508 | ], 1509 | [ 1510 | 'g;x=1/../y', bases[0], 'http://a/b/c/y' 1511 | ], 1512 | [ 1513 | 'g?y/./x', bases[0], 'http://a/b/c/g?y/./x' 1514 | ], 1515 | [ 1516 | 'g?y/../x', bases[0], 'http://a/b/c/g?y/../x' 1517 | ], 1518 | [ 1519 | 'g#s/./x', bases[0], 'http://a/b/c/g#s/./x' 1520 | ], 1521 | [ 1522 | 'g#s/../x', bases[0], 'http://a/b/c/g#s/../x' 1523 | ], 1524 | [ 1525 | 'http:g', bases[0], ('http:g', 'http://a/b/c/g') 1526 | ], 1527 | [ 1528 | 'http:', bases[0], ('http:', bases[0]) 1529 | ], 1530 | // not sure where this one originated 1531 | [ 1532 | '/a/b/c/./../../g', bases[0], 'http://a/a/g' 1533 | ], 1534 | 1535 | /* 1536 | * http://gbiv.com/protocols/uri/test/rel_examples2.html 1537 | * slashes in base URI's query args 1538 | */ 1539 | [ 1540 | 'g', bases[1], 'http://a/b/c/g' 1541 | ], 1542 | [ 1543 | './g', bases[1], 'http://a/b/c/g' 1544 | ], 1545 | [ 1546 | 'g/', bases[1], 'http://a/b/c/g/' 1547 | ], 1548 | [ 1549 | '/g', bases[1], 'http://a/g' 1550 | ], 1551 | [ 1552 | '//g', bases[1], 'http://g/' 1553 | ], 1554 | /* 1555 | * changed in RFC 2396bis 1556 | * ('?y', bases[1], 'http://a/b/c/?y'], 1557 | */ 1558 | [ 1559 | '?y', bases[1], 'http://a/b/c/d;p?y' 1560 | ], 1561 | [ 1562 | 'g?y', bases[1], 'http://a/b/c/g?y' 1563 | ], 1564 | [ 1565 | 'g?y/./x', bases[1], 'http://a/b/c/g?y/./x' 1566 | ], 1567 | [ 1568 | 'g?y/../x', bases[1], 'http://a/b/c/g?y/../x' 1569 | ], 1570 | [ 1571 | 'g#s', bases[1], 'http://a/b/c/g#s' 1572 | ], 1573 | [ 1574 | 'g#s/./x', bases[1], 'http://a/b/c/g#s/./x' 1575 | ], 1576 | [ 1577 | 'g#s/../x', bases[1], 'http://a/b/c/g#s/../x' 1578 | ], 1579 | [ 1580 | './', bases[1], 'http://a/b/c/' 1581 | ], 1582 | [ 1583 | '../', bases[1], 'http://a/b/' 1584 | ], 1585 | [ 1586 | '../g', bases[1], 'http://a/b/g' 1587 | ], 1588 | [ 1589 | '../../', bases[1], 'http://a/' 1590 | ], 1591 | [ 1592 | '../../g', bases[1], 'http://a/g' 1593 | ], 1594 | 1595 | /* 1596 | * http://gbiv.com/protocols/uri/test/rel_examples3.html 1597 | * slashes in path params 1598 | * all of these changed in RFC 2396bis 1599 | */ 1600 | [ 1601 | 'g', bases[2], 'http://a/b/c/d;p=1/g' 1602 | ], 1603 | [ 1604 | './g', bases[2], 'http://a/b/c/d;p=1/g' 1605 | ], 1606 | [ 1607 | 'g/', bases[2], 'http://a/b/c/d;p=1/g/' 1608 | ], 1609 | [ 1610 | 'g?y', bases[2], 'http://a/b/c/d;p=1/g?y' 1611 | ], 1612 | [ 1613 | ';x', bases[2], 'http://a/b/c/d;p=1/;x' 1614 | ], 1615 | [ 1616 | 'g;x', bases[2], 'http://a/b/c/d;p=1/g;x' 1617 | ], 1618 | [ 1619 | 'g;x=1/./y', bases[2], 'http://a/b/c/d;p=1/g;x=1/y' 1620 | ], 1621 | [ 1622 | 'g;x=1/../y', bases[2], 'http://a/b/c/d;p=1/y' 1623 | ], 1624 | [ 1625 | './', bases[2], 'http://a/b/c/d;p=1/' 1626 | ], 1627 | [ 1628 | '../', bases[2], 'http://a/b/c/' 1629 | ], 1630 | [ 1631 | '../g', bases[2], 'http://a/b/c/g' 1632 | ], 1633 | [ 1634 | '../../', bases[2], 'http://a/b/' 1635 | ], 1636 | [ 1637 | '../../g', bases[2], 'http://a/b/g' 1638 | ], 1639 | 1640 | /* 1641 | * http://gbiv.com/protocols/uri/test/rel_examples4.html 1642 | * double and triple slash, unknown scheme 1643 | */ 1644 | [ 1645 | 'g:h', bases[3], 'g:h' 1646 | ], 1647 | [ 1648 | 'g', bases[3], 'fred:///s//a/b/g' 1649 | ], 1650 | [ 1651 | './g', bases[3], 'fred:///s//a/b/g' 1652 | ], 1653 | [ 1654 | 'g/', bases[3], 'fred:///s//a/b/g/' 1655 | ], 1656 | [ 1657 | '/g', bases[3], 'fred:///g' 1658 | ], // may change to fred:///s//a/g 1659 | [ 1660 | '//g', bases[3], 'fred://g' 1661 | ], // may change to fred:///s//g 1662 | [ 1663 | '//g/x', bases[3], 'fred://g/x' 1664 | ], // may change to fred:///s//g/x 1665 | [ 1666 | '///g', bases[3], 'fred:///g' 1667 | ], 1668 | [ 1669 | './', bases[3], 'fred:///s//a/b/' 1670 | ], 1671 | [ 1672 | '../', bases[3], 'fred:///s//a/' 1673 | ], 1674 | [ 1675 | '../g', bases[3], 'fred:///s//a/g' 1676 | ], 1677 | 1678 | [ 1679 | '../../', bases[3], 'fred:///s//' 1680 | ], 1681 | [ 1682 | '../../g', bases[3], 'fred:///s//g' 1683 | ], 1684 | [ 1685 | '../../../g', bases[3], 'fred:///s/g' 1686 | ], 1687 | // may change to fred:///s//a/../../../g 1688 | [ 1689 | '../../../../g', bases[3], 'fred:///g' 1690 | ], 1691 | 1692 | /* 1693 | * http://gbiv.com/protocols/uri/test/rel_examples5.html 1694 | * double and triple slash, well-known scheme 1695 | */ 1696 | [ 1697 | 'g:h', bases[4], 'g:h' 1698 | ], 1699 | [ 1700 | 'g', bases[4], 'http:///s//a/b/g' 1701 | ], 1702 | [ 1703 | './g', bases[4], 'http:///s//a/b/g' 1704 | ], 1705 | [ 1706 | 'g/', bases[4], 'http:///s//a/b/g/' 1707 | ], 1708 | [ 1709 | '/g', bases[4], 'http:///g' 1710 | ], // may change to http:///s//a/g 1711 | [ 1712 | '//g', bases[4], 'http://g/' 1713 | ], // may change to http:///s//g 1714 | [ 1715 | '//g/x', bases[4], 'http://g/x' 1716 | ], // may change to http:///s//g/x 1717 | [ 1718 | '///g', bases[4], 'http:///g' 1719 | ], 1720 | [ 1721 | './', bases[4], 'http:///s//a/b/' 1722 | ], 1723 | [ 1724 | '../', bases[4], 'http:///s//a/' 1725 | ], 1726 | [ 1727 | '../g', bases[4], 'http:///s//a/g' 1728 | ], 1729 | [ 1730 | '../../', bases[4], 'http:///s//' 1731 | ], 1732 | [ 1733 | '../../g', bases[4], 'http:///s//g' 1734 | ], 1735 | // may change to http:///s//a/../../g 1736 | [ 1737 | '../../../g', bases[4], 'http:///s/g' 1738 | ], 1739 | // may change to http:///s//a/../../../g 1740 | [ 1741 | '../../../../g', bases[4], 'http:///g' 1742 | ], 1743 | 1744 | // from Dan Connelly's tests in http://www.w3.org/2000/10/swap/uripath.py 1745 | [ 1746 | 'bar:abc', 'foo:xyz', 'bar:abc' 1747 | ], 1748 | [ 1749 | '../abc', 'http://example/x/y/z', 'http://example/x/abc' 1750 | ], 1751 | [ 1752 | 'http://example/x/abc', 'http://example2/x/y/z', 'http://example/x/abc' 1753 | ], 1754 | [ 1755 | '../r', 'http://ex/x/y/z', 'http://ex/x/r' 1756 | ], 1757 | [ 1758 | 'q/r', 'http://ex/x/y', 'http://ex/x/q/r' 1759 | ], 1760 | [ 1761 | 'q/r#s', 'http://ex/x/y', 'http://ex/x/q/r#s' 1762 | ], 1763 | [ 1764 | 'q/r#s/t', 'http://ex/x/y', 'http://ex/x/q/r#s/t' 1765 | ], 1766 | [ 1767 | 'ftp://ex/x/q/r', 'http://ex/x/y', 'ftp://ex/x/q/r' 1768 | ], 1769 | [ 1770 | '', 'http://ex/x/y', 'http://ex/x/y' 1771 | ], 1772 | [ 1773 | '', 'http://ex/x/y/', 'http://ex/x/y/' 1774 | ], 1775 | [ 1776 | '', 'http://ex/x/y/pdq', 'http://ex/x/y/pdq' 1777 | ], 1778 | [ 1779 | 'z/', 'http://ex/x/y/', 'http://ex/x/y/z/' 1780 | ], 1781 | [ 1782 | '#Animal', 1783 | 'file:/swap/test/animal.rdf', 1784 | 'file:/swap/test/animal.rdf#Animal' 1785 | ], 1786 | [ 1787 | '../abc', 'file:/e/x/y/z', 'file:/e/x/abc' 1788 | ], 1789 | [ 1790 | '/example/x/abc', 'file:/example2/x/y/z', 'file:/example/x/abc' 1791 | ], 1792 | [ 1793 | '../r', 'file:/ex/x/y/z', 'file:/ex/x/r' 1794 | ], 1795 | [ 1796 | '/r', 'file:/ex/x/y/z', 'file:/r' 1797 | ], 1798 | [ 1799 | 'q/r', 'file:/ex/x/y', 'file:/ex/x/q/r' 1800 | ], 1801 | [ 1802 | 'q/r#s', 'file:/ex/x/y', 'file:/ex/x/q/r#s' 1803 | ], 1804 | [ 1805 | 'q/r#', 'file:/ex/x/y', 'file:/ex/x/q/r#' 1806 | ], 1807 | [ 1808 | 'q/r#s/t', 'file:/ex/x/y', 'file:/ex/x/q/r#s/t' 1809 | ], 1810 | [ 1811 | 'ftp://ex/x/q/r', 'file:/ex/x/y', 'ftp://ex/x/q/r' 1812 | ], 1813 | [ 1814 | '', 'file:/ex/x/y', 'file:/ex/x/y' 1815 | ], 1816 | [ 1817 | '', 'file:/ex/x/y/', 'file:/ex/x/y/' 1818 | ], 1819 | [ 1820 | '', 'file:/ex/x/y/pdq', 'file:/ex/x/y/pdq' 1821 | ], 1822 | [ 1823 | 'z/', 'file:/ex/x/y/', 'file:/ex/x/y/z/' 1824 | ], 1825 | [ 1826 | 'file://meetings.example.com/cal#m1', 1827 | 'file:/devel/WWW/2000/10/swap/test/reluri-1.n3', 1828 | 'file://meetings.example.com/cal#m1' 1829 | ], 1830 | [ 1831 | 'file://meetings.example.com/cal#m1', 1832 | 'file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3', 1833 | 'file://meetings.example.com/cal#m1' 1834 | ], 1835 | [ 1836 | './#blort', 'file:/some/dir/foo', 'file:/some/dir/#blort' 1837 | ], 1838 | [ 1839 | './#', 'file:/some/dir/foo', 'file:/some/dir/#' 1840 | ], 1841 | // Ryan Lee 1842 | [ 1843 | './', 'http://example/x/abc.efg', 'http://example/x/' 1844 | ], 1845 | 1846 | /* 1847 | * Graham Klyne's tests 1848 | * http://www.ninebynine.org/Software/HaskellUtils/Network/UriTest.xls 1849 | * 01-31 are from Connelly's cases 1850 | */ 1851 | 1852 | // 32-49 1853 | [ 1854 | './q:r', 'http://ex/x/y', 'http://ex/x/q:r' 1855 | ], 1856 | [ 1857 | './p=q:r', 'http://ex/x/y', 'http://ex/x/p=q:r' 1858 | ], 1859 | [ 1860 | '?pp/rr', 'http://ex/x/y?pp/qq', 'http://ex/x/y?pp/rr' 1861 | ], 1862 | [ 1863 | 'y/z', 'http://ex/x/y?pp/qq', 'http://ex/x/y/z' 1864 | ], 1865 | [ 1866 | 'local/qual@domain.org#frag', 1867 | 'mailto:local', 1868 | 'mailto:local/qual@domain.org#frag' 1869 | ], 1870 | [ 1871 | 'more/qual2@domain2.org#frag', 1872 | 'mailto:local/qual1@domain1.org', 1873 | 'mailto:local/more/qual2@domain2.org#frag' 1874 | ], 1875 | [ 1876 | 'y?q', 'http://ex/x/y?q', 'http://ex/x/y?q' 1877 | ], 1878 | [ 1879 | '/x/y?q', 'http://ex?p', 'http://ex/x/y?q' 1880 | ], 1881 | [ 1882 | 'c/d', 'foo:a/b', 'foo:a/c/d' 1883 | ], 1884 | [ 1885 | '/c/d', 'foo:a/b', 'foo:/c/d' 1886 | ], 1887 | [ 1888 | '', 'foo:a/b?c#d', 'foo:a/b?c' 1889 | ], 1890 | [ 1891 | 'b/c', 'foo:a', 'foo:b/c' 1892 | ], 1893 | [ 1894 | '../b/c', 'foo:/a/y/z', 'foo:/a/b/c' 1895 | ], 1896 | [ 1897 | './b/c', 'foo:a', 'foo:b/c' 1898 | ], 1899 | [ 1900 | '/./b/c', 'foo:a', 'foo:/b/c' 1901 | ], 1902 | [ 1903 | '../../d', 'foo://a//b/c', 'foo://a/d' 1904 | ], 1905 | [ 1906 | '.', 'foo:a', 'foo:' 1907 | ], 1908 | [ 1909 | '..', 'foo:a', 'foo:' 1910 | ], 1911 | 1912 | /* 1913 | * 50-57[cf. TimBL comments -- 1914 | * http://lists.w3.org/Archives/Public/uri/2003Feb/0028.html, 1915 | * http://lists.w3.org/Archives/Public/uri/2003Jan/0008.html) 1916 | */ 1917 | [ 1918 | 'abc', 'http://example/x/y%2Fz', 'http://example/x/abc' 1919 | ], 1920 | [ 1921 | '../../x%2Fabc', 'http://example/a/x/y/z', 'http://example/a/x%2Fabc' 1922 | ], 1923 | [ 1924 | '../x%2Fabc', 'http://example/a/x/y%2Fz', 'http://example/a/x%2Fabc' 1925 | ], 1926 | [ 1927 | 'abc', 'http://example/x%2Fy/z', 'http://example/x%2Fy/abc' 1928 | ], 1929 | [ 1930 | 'q%3Ar', 'http://ex/x/y', 'http://ex/x/q%3Ar' 1931 | ], 1932 | [ 1933 | '/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc' 1934 | ], 1935 | [ 1936 | '/x%2Fabc', 'http://example/x/y/z', 'http://example/x%2Fabc' 1937 | ], 1938 | [ 1939 | '/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc' 1940 | ], 1941 | 1942 | // 70-77 1943 | [ 1944 | 'local2@domain2', 'mailto:local1@domain1?query1', 'mailto:local2@domain2' 1945 | ], 1946 | [ 1947 | 'local2@domain2?query2', 1948 | 'mailto:local1@domain1', 1949 | 'mailto:local2@domain2?query2' 1950 | ], 1951 | [ 1952 | 'local2@domain2?query2', 1953 | 'mailto:local1@domain1?query1', 1954 | 'mailto:local2@domain2?query2' 1955 | ], 1956 | [ 1957 | '?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2' 1958 | ], 1959 | [ 1960 | 'local@domain?query2', 'mailto:?query1', 'mailto:local@domain?query2' 1961 | ], 1962 | [ 1963 | '?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2' 1964 | ], 1965 | [ 1966 | 'http://example/a/b?c/../d', 'foo:bar', 'http://example/a/b?c/../d' 1967 | ], 1968 | [ 1969 | 'http://example/a/b#c/../d', 'foo:bar', 'http://example/a/b#c/../d' 1970 | ], 1971 | 1972 | /* 1973 | * 82-88 1974 | * @isaacs Disagree. Not how browsers do it. 1975 | * ['http:this', 'http://example.org/base/uri', 'http:this'], 1976 | * @isaacs Added 1977 | */ 1978 | [ 1979 | 'http:this', 'http://example.org/base/uri', 'http://example.org/base/this' 1980 | ], 1981 | [ 1982 | 'http:this', 'http:base', 'http:this' 1983 | ], 1984 | [ 1985 | './/g', 'f:/a', 'f://g' 1986 | ], 1987 | [ 1988 | 'b/c//d/e', 'f://example.org/base/a', 'f://example.org/base/b/c//d/e' 1989 | ], 1990 | [ 1991 | 'm2@example.ord/c2@example.org', 1992 | 'mid:m@example.ord/c@example.org', 1993 | 'mid:m@example.ord/m2@example.ord/c2@example.org' 1994 | ], 1995 | [ 1996 | 'mini1.xml', 1997 | 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/', 1998 | 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml' 1999 | ], 2000 | [ 2001 | '../b/c', 'foo:a/y/z', 'foo:a/b/c' 2002 | ], 2003 | 2004 | // changeing auth 2005 | [ 2006 | 'http://diff:auth@www.example.com', 2007 | 'http://asdf:qwer@www.example.com', 2008 | 'http://diff:auth@www.example.com/' 2009 | ] 2010 | ]; 2011 | 2012 | relativeTests2.forEach(function (relativeTest) { 2013 | test('resolve(' + [relativeTest[1], relativeTest[0]] + ')', function () { 2014 | var a = url.resolve(relativeTest[1], relativeTest[0]), 2015 | e = relativeTest[2]; 2016 | assert.equal(a, e, 'resolve(' + [relativeTest[1], relativeTest[0]] + ') == ' + e + '\n actual=' + a); 2017 | }); 2018 | }); 2019 | 2020 | /* 2021 | * if format and parse are inverse operations then 2022 | * resolveObject(parse(x), y) == parse(resolve(x, y)) 2023 | */ 2024 | 2025 | // host and hostname are special, in this case a '' value is important 2026 | /* eslint no-unused-vars: 1 */ 2027 | var emptyIsImportant = { host: true, hostname: '' }; 2028 | 2029 | // format: [from, path, expected] 2030 | relativeTests.forEach(function (relativeTest) { 2031 | test('resolveObject(' + [relativeTest[0], relativeTest[1]] + ')', function () { 2032 | var actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]); 2033 | var expected = url.parse(relativeTest[2]); 2034 | 2035 | assert.deepEqual(actual, expected); 2036 | 2037 | expected = relativeTest[2]; 2038 | actual = url.format(actual); 2039 | 2040 | assert.equal(actual, expected, 'format(' + actual + ') == ' + expected + '\nactual:' + actual); 2041 | }); 2042 | }); 2043 | 2044 | test('format with querystring', function () { 2045 | var obj = { protocol: 'https:', host: 'google.com', pathname: 'test', query: { message: ['value1', 'value2'], 'custom:id': 12 } }; 2046 | 2047 | var actual = url.format(obj); 2048 | var expected = nodeURL.format(obj); 2049 | 2050 | assert.equal(actual, expected, 'format(' + actual + ') == ' + expected + '\nactual:' + actual); 2051 | }); 2052 | 2053 | /* 2054 | * format: [to, from, result] 2055 | * the test: ['.//g', 'f:/a', 'f://g'] is a fundamental problem 2056 | * url.parse('f:/a') does not have a host 2057 | * url.resolve('f:/a', './/g') does not have a host because you have moved 2058 | * down to the g directory. i.e. f: //g, however when this url is parsed 2059 | * f:// will indicate that the host is g which is not the case. 2060 | * it is unclear to me how to keep this information from being lost 2061 | * it may be that a pathname of ////g should collapse to /g but this seems 2062 | * to be a lot of work for an edge case. Right now I remove the test 2063 | */ 2064 | if (relativeTests2[181][0] === './/g' && relativeTests2[181][1] === 'f:/a' && relativeTests2[181][2] === 'f://g') { 2065 | relativeTests2.splice(181, 1); 2066 | } 2067 | 2068 | relativeTests2.forEach(function (relativeTest) { 2069 | test('resolveObject(' + [relativeTest[1], relativeTest[0]] + ')', function () { 2070 | var actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]), 2071 | expected = url.parse(relativeTest[2]); 2072 | 2073 | assert.deepEqual(actual, expected); 2074 | 2075 | var expected = relativeTest[2], 2076 | actual = url.format(actual); 2077 | 2078 | assert.equal(actual, expected, 'format(' + relativeTest[1] + ') == ' + expected + '\nactual:' + actual); 2079 | }); 2080 | }); 2081 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/*.js 2 | --recursive 3 | -------------------------------------------------------------------------------- /url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Joyent, Inc. and other Node contributors. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to permit 9 | * persons to whom the Software is furnished to do so, subject to the 10 | * following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 18 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var punycode = require('punycode/'); 27 | 28 | function Url() { 29 | this.protocol = null; 30 | this.slashes = null; 31 | this.auth = null; 32 | this.host = null; 33 | this.port = null; 34 | this.hostname = null; 35 | this.hash = null; 36 | this.search = null; 37 | this.query = null; 38 | this.pathname = null; 39 | this.path = null; 40 | this.href = null; 41 | } 42 | 43 | // Reference: RFC 3986, RFC 1808, RFC 2396 44 | 45 | /* 46 | * define these here so at least they only have to be 47 | * compiled once on the first module load. 48 | */ 49 | var protocolPattern = /^([a-z0-9.+-]+:)/i, 50 | portPattern = /:[0-9]*$/, 51 | 52 | // Special case for a simple path URL 53 | simplePathPattern = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/, 54 | 55 | /* 56 | * RFC 2396: characters reserved for delimiting URLs. 57 | * We actually just auto-escape these. 58 | */ 59 | delims = [ 60 | '<', '>', '"', '`', ' ', '\r', '\n', '\t' 61 | ], 62 | 63 | // RFC 2396: characters not allowed for various reasons. 64 | unwise = [ 65 | '{', '}', '|', '\\', '^', '`' 66 | ].concat(delims), 67 | 68 | // Allowed by RFCs, but cause of XSS attacks. Always escape these. 69 | autoEscape = ['\''].concat(unwise), 70 | /* 71 | * Characters that are never ever allowed in a hostname. 72 | * Note that any invalid chars are also handled, but these 73 | * are the ones that are *expected* to be seen, so we fast-path 74 | * them. 75 | */ 76 | nonHostChars = [ 77 | '%', '/', '?', ';', '#' 78 | ].concat(autoEscape), 79 | hostEndingChars = [ 80 | '/', '?', '#' 81 | ], 82 | hostnameMaxLen = 255, 83 | hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, 84 | hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, 85 | // protocols that can allow "unsafe" and "unwise" chars. 86 | unsafeProtocol = { 87 | javascript: true, 88 | 'javascript:': true 89 | }, 90 | // protocols that never have a hostname. 91 | hostlessProtocol = { 92 | javascript: true, 93 | 'javascript:': true 94 | }, 95 | // protocols that always contain a // bit. 96 | slashedProtocol = { 97 | http: true, 98 | https: true, 99 | ftp: true, 100 | gopher: true, 101 | file: true, 102 | 'http:': true, 103 | 'https:': true, 104 | 'ftp:': true, 105 | 'gopher:': true, 106 | 'file:': true 107 | }, 108 | querystring = require('qs'); 109 | 110 | function urlParse(url, parseQueryString, slashesDenoteHost) { 111 | if (url && typeof url === 'object' && url instanceof Url) { return url; } 112 | 113 | var u = new Url(); 114 | u.parse(url, parseQueryString, slashesDenoteHost); 115 | return u; 116 | } 117 | 118 | Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { 119 | if (typeof url !== 'string') { 120 | throw new TypeError("Parameter 'url' must be a string, not " + typeof url); 121 | } 122 | 123 | /* 124 | * Copy chrome, IE, opera backslash-handling behavior. 125 | * Back slashes before the query string get converted to forward slashes 126 | * See: https://code.google.com/p/chromium/issues/detail?id=25916 127 | */ 128 | var queryIndex = url.indexOf('?'), 129 | splitter = queryIndex !== -1 && queryIndex < url.indexOf('#') ? '?' : '#', 130 | uSplit = url.split(splitter), 131 | slashRegex = /\\/g; 132 | uSplit[0] = uSplit[0].replace(slashRegex, '/'); 133 | url = uSplit.join(splitter); 134 | 135 | var rest = url; 136 | 137 | /* 138 | * trim before proceeding. 139 | * This is to support parse stuff like " http://foo.com \n" 140 | */ 141 | rest = rest.trim(); 142 | 143 | if (!slashesDenoteHost && url.split('#').length === 1) { 144 | // Try fast path regexp 145 | var simplePath = simplePathPattern.exec(rest); 146 | if (simplePath) { 147 | this.path = rest; 148 | this.href = rest; 149 | this.pathname = simplePath[1]; 150 | if (simplePath[2]) { 151 | this.search = simplePath[2]; 152 | if (parseQueryString) { 153 | this.query = querystring.parse(this.search.substr(1)); 154 | } else { 155 | this.query = this.search.substr(1); 156 | } 157 | } else if (parseQueryString) { 158 | this.search = ''; 159 | this.query = {}; 160 | } 161 | return this; 162 | } 163 | } 164 | 165 | var proto = protocolPattern.exec(rest); 166 | if (proto) { 167 | proto = proto[0]; 168 | var lowerProto = proto.toLowerCase(); 169 | this.protocol = lowerProto; 170 | rest = rest.substr(proto.length); 171 | } 172 | 173 | /* 174 | * figure out if it's got a host 175 | * user@server is *always* interpreted as a hostname, and url 176 | * resolution will treat //foo/bar as host=foo,path=bar because that's 177 | * how the browser resolves relative URLs. 178 | */ 179 | if (slashesDenoteHost || proto || rest.match(/^\/\/[^@/]+@[^@/]+/)) { 180 | var slashes = rest.substr(0, 2) === '//'; 181 | if (slashes && !(proto && hostlessProtocol[proto])) { 182 | rest = rest.substr(2); 183 | this.slashes = true; 184 | } 185 | } 186 | 187 | if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) { 188 | 189 | /* 190 | * there's a hostname. 191 | * the first instance of /, ?, ;, or # ends the host. 192 | * 193 | * If there is an @ in the hostname, then non-host chars *are* allowed 194 | * to the left of the last @ sign, unless some host-ending character 195 | * comes *before* the @-sign. 196 | * URLs are obnoxious. 197 | * 198 | * ex: 199 | * http://a@b@c/ => user:a@b host:c 200 | * http://a@b?@c => user:a host:c path:/?@c 201 | */ 202 | 203 | /* 204 | * v0.12 TODO(isaacs): This is not quite how Chrome does things. 205 | * Review our test case against browsers more comprehensively. 206 | */ 207 | 208 | // find the first instance of any hostEndingChars 209 | var hostEnd = -1; 210 | for (var i = 0; i < hostEndingChars.length; i++) { 211 | var hec = rest.indexOf(hostEndingChars[i]); 212 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { hostEnd = hec; } 213 | } 214 | 215 | /* 216 | * at this point, either we have an explicit point where the 217 | * auth portion cannot go past, or the last @ char is the decider. 218 | */ 219 | var auth, atSign; 220 | if (hostEnd === -1) { 221 | // atSign can be anywhere. 222 | atSign = rest.lastIndexOf('@'); 223 | } else { 224 | /* 225 | * atSign must be in auth portion. 226 | * http://a@b/c@d => host:b auth:a path:/c@d 227 | */ 228 | atSign = rest.lastIndexOf('@', hostEnd); 229 | } 230 | 231 | /* 232 | * Now we have a portion which is definitely the auth. 233 | * Pull that off. 234 | */ 235 | if (atSign !== -1) { 236 | auth = rest.slice(0, atSign); 237 | rest = rest.slice(atSign + 1); 238 | this.auth = decodeURIComponent(auth); 239 | } 240 | 241 | // the host is the remaining to the left of the first non-host char 242 | hostEnd = -1; 243 | for (var i = 0; i < nonHostChars.length; i++) { 244 | var hec = rest.indexOf(nonHostChars[i]); 245 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { hostEnd = hec; } 246 | } 247 | // if we still have not hit it, then the entire thing is a host. 248 | if (hostEnd === -1) { hostEnd = rest.length; } 249 | 250 | this.host = rest.slice(0, hostEnd); 251 | rest = rest.slice(hostEnd); 252 | 253 | // pull out port. 254 | this.parseHost(); 255 | 256 | /* 257 | * we've indicated that there is a hostname, 258 | * so even if it's empty, it has to be present. 259 | */ 260 | this.hostname = this.hostname || ''; 261 | 262 | /* 263 | * if hostname begins with [ and ends with ] 264 | * assume that it's an IPv6 address. 265 | */ 266 | var ipv6Hostname = this.hostname[0] === '[' && this.hostname[this.hostname.length - 1] === ']'; 267 | 268 | // validate a little. 269 | if (!ipv6Hostname) { 270 | var hostparts = this.hostname.split(/\./); 271 | for (var i = 0, l = hostparts.length; i < l; i++) { 272 | var part = hostparts[i]; 273 | if (!part) { continue; } 274 | if (!part.match(hostnamePartPattern)) { 275 | var newpart = ''; 276 | for (var j = 0, k = part.length; j < k; j++) { 277 | if (part.charCodeAt(j) > 127) { 278 | /* 279 | * we replace non-ASCII char with a temporary placeholder 280 | * we need this to make sure size of hostname is not 281 | * broken by replacing non-ASCII by nothing 282 | */ 283 | newpart += 'x'; 284 | } else { 285 | newpart += part[j]; 286 | } 287 | } 288 | // we test again with ASCII char only 289 | if (!newpart.match(hostnamePartPattern)) { 290 | var validParts = hostparts.slice(0, i); 291 | var notHost = hostparts.slice(i + 1); 292 | var bit = part.match(hostnamePartStart); 293 | if (bit) { 294 | validParts.push(bit[1]); 295 | notHost.unshift(bit[2]); 296 | } 297 | if (notHost.length) { 298 | rest = '/' + notHost.join('.') + rest; 299 | } 300 | this.hostname = validParts.join('.'); 301 | break; 302 | } 303 | } 304 | } 305 | } 306 | 307 | if (this.hostname.length > hostnameMaxLen) { 308 | this.hostname = ''; 309 | } else { 310 | // hostnames are always lower case. 311 | this.hostname = this.hostname.toLowerCase(); 312 | } 313 | 314 | if (!ipv6Hostname) { 315 | /* 316 | * IDNA Support: Returns a punycoded representation of "domain". 317 | * It only converts parts of the domain name that 318 | * have non-ASCII characters, i.e. it doesn't matter if 319 | * you call it with a domain that already is ASCII-only. 320 | */ 321 | this.hostname = punycode.toASCII(this.hostname); 322 | } 323 | 324 | var p = this.port ? ':' + this.port : ''; 325 | var h = this.hostname || ''; 326 | this.host = h + p; 327 | this.href += this.host; 328 | 329 | /* 330 | * strip [ and ] from the hostname 331 | * the host field still retains them, though 332 | */ 333 | if (ipv6Hostname) { 334 | this.hostname = this.hostname.substr(1, this.hostname.length - 2); 335 | if (rest[0] !== '/') { 336 | rest = '/' + rest; 337 | } 338 | } 339 | } 340 | 341 | /* 342 | * now rest is set to the post-host stuff. 343 | * chop off any delim chars. 344 | */ 345 | if (!unsafeProtocol[lowerProto]) { 346 | 347 | /* 348 | * First, make 100% sure that any "autoEscape" chars get 349 | * escaped, even if encodeURIComponent doesn't think they 350 | * need to be. 351 | */ 352 | for (var i = 0, l = autoEscape.length; i < l; i++) { 353 | var ae = autoEscape[i]; 354 | if (rest.indexOf(ae) === -1) { continue; } 355 | var esc = encodeURIComponent(ae); 356 | if (esc === ae) { 357 | esc = escape(ae); 358 | } 359 | rest = rest.split(ae).join(esc); 360 | } 361 | } 362 | 363 | // chop off from the tail first. 364 | var hash = rest.indexOf('#'); 365 | if (hash !== -1) { 366 | // got a fragment string. 367 | this.hash = rest.substr(hash); 368 | rest = rest.slice(0, hash); 369 | } 370 | var qm = rest.indexOf('?'); 371 | if (qm !== -1) { 372 | this.search = rest.substr(qm); 373 | this.query = rest.substr(qm + 1); 374 | if (parseQueryString) { 375 | this.query = querystring.parse(this.query); 376 | } 377 | rest = rest.slice(0, qm); 378 | } else if (parseQueryString) { 379 | // no query string, but parseQueryString still requested 380 | this.search = ''; 381 | this.query = {}; 382 | } 383 | if (rest) { this.pathname = rest; } 384 | if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { 385 | this.pathname = '/'; 386 | } 387 | 388 | // to support http.request 389 | if (this.pathname || this.search) { 390 | var p = this.pathname || ''; 391 | var s = this.search || ''; 392 | this.path = p + s; 393 | } 394 | 395 | // finally, reconstruct the href based on what has been validated. 396 | this.href = this.format(); 397 | return this; 398 | }; 399 | 400 | // format a parsed object into a url string 401 | function urlFormat(obj) { 402 | /* 403 | * ensure it's an object, and not a string url. 404 | * If it's an obj, this is a no-op. 405 | * this way, you can call url_format() on strings 406 | * to clean up potentially wonky urls. 407 | */ 408 | if (typeof obj === 'string') { obj = urlParse(obj); } 409 | if (!(obj instanceof Url)) { return Url.prototype.format.call(obj); } 410 | return obj.format(); 411 | } 412 | 413 | Url.prototype.format = function () { 414 | var auth = this.auth || ''; 415 | if (auth) { 416 | auth = encodeURIComponent(auth); 417 | auth = auth.replace(/%3A/i, ':'); 418 | auth += '@'; 419 | } 420 | 421 | var protocol = this.protocol || '', 422 | pathname = this.pathname || '', 423 | hash = this.hash || '', 424 | host = false, 425 | query = ''; 426 | 427 | if (this.host) { 428 | host = auth + this.host; 429 | } else if (this.hostname) { 430 | host = auth + (this.hostname.indexOf(':') === -1 ? this.hostname : '[' + this.hostname + ']'); 431 | if (this.port) { 432 | host += ':' + this.port; 433 | } 434 | } 435 | 436 | if (this.query && typeof this.query === 'object' && Object.keys(this.query).length) { 437 | query = querystring.stringify(this.query, { 438 | arrayFormat: 'repeat', 439 | addQueryPrefix: false 440 | }); 441 | } 442 | 443 | var search = this.search || (query && ('?' + query)) || ''; 444 | 445 | if (protocol && protocol.substr(-1) !== ':') { protocol += ':'; } 446 | 447 | /* 448 | * only the slashedProtocols get the //. Not mailto:, xmpp:, etc. 449 | * unless they had them to begin with. 450 | */ 451 | if (this.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) { 452 | host = '//' + (host || ''); 453 | if (pathname && pathname.charAt(0) !== '/') { pathname = '/' + pathname; } 454 | } else if (!host) { 455 | host = ''; 456 | } 457 | 458 | if (hash && hash.charAt(0) !== '#') { hash = '#' + hash; } 459 | if (search && search.charAt(0) !== '?') { search = '?' + search; } 460 | 461 | pathname = pathname.replace(/[?#]/g, function (match) { 462 | return encodeURIComponent(match); 463 | }); 464 | search = search.replace('#', '%23'); 465 | 466 | return protocol + host + pathname + search + hash; 467 | }; 468 | 469 | function urlResolve(source, relative) { 470 | return urlParse(source, false, true).resolve(relative); 471 | } 472 | 473 | Url.prototype.resolve = function (relative) { 474 | return this.resolveObject(urlParse(relative, false, true)).format(); 475 | }; 476 | 477 | function urlResolveObject(source, relative) { 478 | if (!source) { return relative; } 479 | return urlParse(source, false, true).resolveObject(relative); 480 | } 481 | 482 | Url.prototype.resolveObject = function (relative) { 483 | if (typeof relative === 'string') { 484 | var rel = new Url(); 485 | rel.parse(relative, false, true); 486 | relative = rel; 487 | } 488 | 489 | var result = new Url(); 490 | var tkeys = Object.keys(this); 491 | for (var tk = 0; tk < tkeys.length; tk++) { 492 | var tkey = tkeys[tk]; 493 | result[tkey] = this[tkey]; 494 | } 495 | 496 | /* 497 | * hash is always overridden, no matter what. 498 | * even href="" will remove it. 499 | */ 500 | result.hash = relative.hash; 501 | 502 | // if the relative url is empty, then there's nothing left to do here. 503 | if (relative.href === '') { 504 | result.href = result.format(); 505 | return result; 506 | } 507 | 508 | // hrefs like //foo/bar always cut to the protocol. 509 | if (relative.slashes && !relative.protocol) { 510 | // take everything except the protocol from relative 511 | var rkeys = Object.keys(relative); 512 | for (var rk = 0; rk < rkeys.length; rk++) { 513 | var rkey = rkeys[rk]; 514 | if (rkey !== 'protocol') { result[rkey] = relative[rkey]; } 515 | } 516 | 517 | // urlParse appends trailing / to urls like http://www.example.com 518 | if (slashedProtocol[result.protocol] && result.hostname && !result.pathname) { 519 | result.pathname = '/'; 520 | result.path = result.pathname; 521 | } 522 | 523 | result.href = result.format(); 524 | return result; 525 | } 526 | 527 | if (relative.protocol && relative.protocol !== result.protocol) { 528 | /* 529 | * if it's a known url protocol, then changing 530 | * the protocol does weird things 531 | * first, if it's not file:, then we MUST have a host, 532 | * and if there was a path 533 | * to begin with, then we MUST have a path. 534 | * if it is file:, then the host is dropped, 535 | * because that's known to be hostless. 536 | * anything else is assumed to be absolute. 537 | */ 538 | if (!slashedProtocol[relative.protocol]) { 539 | var keys = Object.keys(relative); 540 | for (var v = 0; v < keys.length; v++) { 541 | var k = keys[v]; 542 | result[k] = relative[k]; 543 | } 544 | result.href = result.format(); 545 | return result; 546 | } 547 | 548 | result.protocol = relative.protocol; 549 | if (!relative.host && !hostlessProtocol[relative.protocol]) { 550 | var relPath = (relative.pathname || '').split('/'); 551 | while (relPath.length && !(relative.host = relPath.shift())) { } 552 | if (!relative.host) { relative.host = ''; } 553 | if (!relative.hostname) { relative.hostname = ''; } 554 | if (relPath[0] !== '') { relPath.unshift(''); } 555 | if (relPath.length < 2) { relPath.unshift(''); } 556 | result.pathname = relPath.join('/'); 557 | } else { 558 | result.pathname = relative.pathname; 559 | } 560 | result.search = relative.search; 561 | result.query = relative.query; 562 | result.host = relative.host || ''; 563 | result.auth = relative.auth; 564 | result.hostname = relative.hostname || relative.host; 565 | result.port = relative.port; 566 | // to support http.request 567 | if (result.pathname || result.search) { 568 | var p = result.pathname || ''; 569 | var s = result.search || ''; 570 | result.path = p + s; 571 | } 572 | result.slashes = result.slashes || relative.slashes; 573 | result.href = result.format(); 574 | return result; 575 | } 576 | 577 | var isSourceAbs = result.pathname && result.pathname.charAt(0) === '/', 578 | isRelAbs = relative.host || relative.pathname && relative.pathname.charAt(0) === '/', 579 | mustEndAbs = isRelAbs || isSourceAbs || (result.host && relative.pathname), 580 | removeAllDots = mustEndAbs, 581 | srcPath = result.pathname && result.pathname.split('/') || [], 582 | relPath = relative.pathname && relative.pathname.split('/') || [], 583 | psychotic = result.protocol && !slashedProtocol[result.protocol]; 584 | 585 | /* 586 | * if the url is a non-slashed url, then relative 587 | * links like ../.. should be able 588 | * to crawl up to the hostname, as well. This is strange. 589 | * result.protocol has already been set by now. 590 | * Later on, put the first path part into the host field. 591 | */ 592 | if (psychotic) { 593 | result.hostname = ''; 594 | result.port = null; 595 | if (result.host) { 596 | if (srcPath[0] === '') { srcPath[0] = result.host; } else { srcPath.unshift(result.host); } 597 | } 598 | result.host = ''; 599 | if (relative.protocol) { 600 | relative.hostname = null; 601 | relative.port = null; 602 | if (relative.host) { 603 | if (relPath[0] === '') { relPath[0] = relative.host; } else { relPath.unshift(relative.host); } 604 | } 605 | relative.host = null; 606 | } 607 | mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); 608 | } 609 | 610 | if (isRelAbs) { 611 | // it's absolute. 612 | result.host = relative.host || relative.host === '' ? relative.host : result.host; 613 | result.hostname = relative.hostname || relative.hostname === '' ? relative.hostname : result.hostname; 614 | result.search = relative.search; 615 | result.query = relative.query; 616 | srcPath = relPath; 617 | // fall through to the dot-handling below. 618 | } else if (relPath.length) { 619 | /* 620 | * it's relative 621 | * throw away the existing file, and take the new path instead. 622 | */ 623 | if (!srcPath) { srcPath = []; } 624 | srcPath.pop(); 625 | srcPath = srcPath.concat(relPath); 626 | result.search = relative.search; 627 | result.query = relative.query; 628 | } else if (relative.search != null) { 629 | /* 630 | * just pull out the search. 631 | * like href='?foo'. 632 | * Put this after the other two cases because it simplifies the booleans 633 | */ 634 | if (psychotic) { 635 | result.host = srcPath.shift(); 636 | result.hostname = result.host; 637 | /* 638 | * occationaly the auth can get stuck only in host 639 | * this especially happens in cases like 640 | * url.resolveObject('mailto:local1@domain1', 'local2@domain2') 641 | */ 642 | var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; 643 | if (authInHost) { 644 | result.auth = authInHost.shift(); 645 | result.hostname = authInHost.shift(); 646 | result.host = result.hostname; 647 | } 648 | } 649 | result.search = relative.search; 650 | result.query = relative.query; 651 | // to support http.request 652 | if (result.pathname !== null || result.search !== null) { 653 | result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); 654 | } 655 | result.href = result.format(); 656 | return result; 657 | } 658 | 659 | if (!srcPath.length) { 660 | /* 661 | * no path at all. easy. 662 | * we've already handled the other stuff above. 663 | */ 664 | result.pathname = null; 665 | // to support http.request 666 | if (result.search) { 667 | result.path = '/' + result.search; 668 | } else { 669 | result.path = null; 670 | } 671 | result.href = result.format(); 672 | return result; 673 | } 674 | 675 | /* 676 | * if a url ENDs in . or .., then it must get a trailing slash. 677 | * however, if it ends in anything else non-slashy, 678 | * then it must NOT get a trailing slash. 679 | */ 680 | var last = srcPath.slice(-1)[0]; 681 | var hasTrailingSlash = (result.host || relative.host || srcPath.length > 1) && (last === '.' || last === '..') || last === ''; 682 | 683 | /* 684 | * strip single dots, resolve double dots to parent dir 685 | * if the path tries to go above the root, `up` ends up > 0 686 | */ 687 | var up = 0; 688 | for (var i = srcPath.length; i >= 0; i--) { 689 | last = srcPath[i]; 690 | if (last === '.') { 691 | srcPath.splice(i, 1); 692 | } else if (last === '..') { 693 | srcPath.splice(i, 1); 694 | up++; 695 | } else if (up) { 696 | srcPath.splice(i, 1); 697 | up--; 698 | } 699 | } 700 | 701 | // if the path is allowed to go above the root, restore leading ..s 702 | if (!mustEndAbs && !removeAllDots) { 703 | for (; up--; up) { 704 | srcPath.unshift('..'); 705 | } 706 | } 707 | 708 | if (mustEndAbs && srcPath[0] !== '' && (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { 709 | srcPath.unshift(''); 710 | } 711 | 712 | if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { 713 | srcPath.push(''); 714 | } 715 | 716 | var isAbsolute = srcPath[0] === '' || (srcPath[0] && srcPath[0].charAt(0) === '/'); 717 | 718 | // put the host back 719 | if (psychotic) { 720 | result.hostname = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; 721 | result.host = result.hostname; 722 | /* 723 | * occationaly the auth can get stuck only in host 724 | * this especially happens in cases like 725 | * url.resolveObject('mailto:local1@domain1', 'local2@domain2') 726 | */ 727 | var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; 728 | if (authInHost) { 729 | result.auth = authInHost.shift(); 730 | result.hostname = authInHost.shift(); 731 | result.host = result.hostname; 732 | } 733 | } 734 | 735 | mustEndAbs = mustEndAbs || (result.host && srcPath.length); 736 | 737 | if (mustEndAbs && !isAbsolute) { 738 | srcPath.unshift(''); 739 | } 740 | 741 | if (srcPath.length > 0) { 742 | result.pathname = srcPath.join('/'); 743 | } else { 744 | result.pathname = null; 745 | result.path = null; 746 | } 747 | 748 | // to support request.http 749 | if (result.pathname !== null || result.search !== null) { 750 | result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : ''); 751 | } 752 | result.auth = relative.auth || result.auth; 753 | result.slashes = result.slashes || relative.slashes; 754 | result.href = result.format(); 755 | return result; 756 | }; 757 | 758 | Url.prototype.parseHost = function () { 759 | var host = this.host; 760 | var port = portPattern.exec(host); 761 | if (port) { 762 | port = port[0]; 763 | if (port !== ':') { 764 | this.port = port.substr(1); 765 | } 766 | host = host.substr(0, host.length - port.length); 767 | } 768 | if (host) { this.hostname = host; } 769 | }; 770 | 771 | exports.parse = urlParse; 772 | exports.resolve = urlResolve; 773 | exports.resolveObject = urlResolveObject; 774 | exports.format = urlFormat; 775 | 776 | exports.Url = Url; 777 | --------------------------------------------------------------------------------