diff --git a/.gitignore b/.gitignore index dea969504a90f1..3e24725728ac5b 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ _UpgradeReport_Files/ ipch/ *.sdf *.opensdf +*.VC.db *.VC.opendb .vs/ .vscode/ @@ -68,11 +69,9 @@ ipch/ /config_fips.gypi *-nodegyp* /gyp-mac-tool -/dist-osx /npm.wxs /tools/msvs/npm.wixobj /tools/msvs/genfiles/ -/tools/osx-pkg.pmdoc/index.xml /test/addons/??_*/ email.md deps/v8-* @@ -100,6 +99,7 @@ deps/npm/node_modules/.bin/ # build/release artifacts /*.tar.* +/*.pkg /SHASUMS*.txt* # test artifacts @@ -115,3 +115,6 @@ icu_config.gypi deps/uv/.github/ deps/uv/docs/code/ deps/uv/docs/src/guide/ + +# do not override V8's .gitignore +!deps/v8/** diff --git a/.mailmap b/.mailmap index fd6816a5dce7d3..f04496bf3b51ca 100644 --- a/.mailmap +++ b/.mailmap @@ -188,6 +188,8 @@ Kathy Truong k3kathy Kazuyuki Yamada Keith M Wesolowski Kelsey Breseman +Khaidi Chu XadillaX +Khaidi Chu Kiyoshi Nomo kysnm Koichi Kobayashi Kris Kowal @@ -264,6 +266,7 @@ Roman Klauke Roman Reiss Ron Korving Ron Korving ronkorving +Ruben Bridgewater Russell Dempsey Ryan Dahl Ryan Emery diff --git a/AUTHORS b/AUTHORS index 8d8e74493aff0a..57d5a25ea0a7e3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -911,7 +911,7 @@ HUANG Wei DC Daniel Turing Julie Pagano -Ruben Bridgewater +Ruben Bridgewater Felix Becker Igor Klopov Tsarevich Dmitry @@ -1493,7 +1493,7 @@ Sreepurna Jasti Rafael Fragoso Andrei Cioromila Frank Lanitz -XadillaX +Khaidi Chu Akshay Iyer Rick Bullotta Rajaram Gaunker @@ -1532,7 +1532,6 @@ Dan Homola cornholio <0@mcornholio.ru> Tamás Hódi DuanPengfei <2459714173@qq.com> -Ruben Bridgewater Lakshmi Swetha Gopireddy Rob Wu Steven Winston @@ -2036,5 +2035,34 @@ Hannes Magnusson ChungNgoops Jose M. Palacios Diaz hmammedzadeh +IHsuan +Francisco Gerardo Neri Andriano +Shilo Mangam +idandagan1 +Cameron Moorehead +TomerOmri +Collins Abitekaniza +Federico Kauffman +Benno Fünfstück +Ram Goli +babygoat +Will Clark +Jem Bezooyen +Haejin Jo +Hakan Kimeiga +Tyler +Shinya Kanamaru +you12724 +routerman +April Webster +Jure Triglav +alnyan +rt33 +Ulmanb +xortiz +Waleed Ashraf +Mir Mufaqam Ali +Nicholas Drane +Shobhit Chittora # Generated by tools/update-authors.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a9e6675a555a..3c5d98c6ba24e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ release. -6.12.3
+6.13.0
+6.12.3
6.12.2
6.12.1
6.12.0
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d9ad342881c1a9..5a935352a1f9f0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,8 @@ # Code of Conduct The Node.js Code of Conduct document has moved to -https://github.com/nodejs/TSC/blob/master/CODE_OF_CONDUCT.md. Please update +https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md. Please update links to this document accordingly. + +The Node.js Moderation policy can be found at +https://github.com/nodejs/admin/blob/master/Moderation-Policy.md diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index ce43bc208f7d05..78401264844c52 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -4,7 +4,7 @@ * [Issues and Pull Requests](#issues-and-pull-requests) - [Managing Issues and Pull Requests](#managing-issues-and-pull-requests) - - [Welcoming First-Time Contributiors](#welcoming-first-time-contributiors) + - [Welcoming First-Time Contributors](#welcoming-first-time-contributors) - [Closing Issues and Pull Requests](#closing-issues-and-pull-requests) * [Accepting Modifications](#accepting-modifications) - [Code Reviews and Consensus Seeking](#code-reviews-and-consensus-seeking) @@ -33,7 +33,7 @@ - [How is an LTS release cut?](#how-is-an-lts-release-cut) This document contains information for Collaborators of the Node.js -project regarding maintaining the code, documentation and issues. +project regarding managing the project's code, documentation, and issue tracker. Collaborators should be familiar with the guidelines for new contributors in [CONTRIBUTING.md](./CONTRIBUTING.md) and also @@ -52,11 +52,11 @@ may also notify other qualified parties for more input on an issue or a pull request. [See "Who to CC in issues"](./doc/onboarding-extras.md#who-to-cc-in-issues) -### Welcoming First-Time Contributiors +### Welcoming First-Time Contributors Courtesy should always be shown to individuals submitting issues and pull requests to the Node.js project. Be welcoming to first-time contributors, -identified by the GitHub ![badge](./doc/first_timer_badge.png) badge. +identified by the GitHub ![First-time contributor](./doc/first_timer_badge.png) badge. For first-time contributors, check if the commit author is the same as the pull request author, and ask if they have configured their git @@ -116,6 +116,11 @@ oppose the PR, it can be landed. Where there is disagreement among TSC members or objections from one or more Collaborators, `semver-major` pull requests should be put on the TSC meeting agenda. +#### Helpful resources + +* How to respectfully and usefully review code, part [one](https://mtlynch.io/human-code-reviews-1/) and [two](https://mtlynch.io/human-code-reviews-2/) +* [How to write a positive code review](https://css-tricks.com/code-review-etiquette/) + ### Waiting for Approvals Before landing pull requests, sufficient time should be left for input @@ -142,6 +147,7 @@ test should *fail* before the change, and *pass* after the change. All pull requests that modify executable code should be subjected to continuous integration tests on the [project CI server](https://ci.nodejs.org/). +The pull request should have a CI status indicator if possible. #### Useful CI Jobs @@ -200,11 +206,10 @@ Node.js API are internal: - Any native C/C++ APIs/ABIs exported by the Node.js `*.h` header files that are hidden behind the `NODE_WANT_INTERNALS` flag are internal. -Exception to each of these points can be made if use or behavior of a given -internal API can be demonstrated to be sufficiently relied upon by the Node.js -ecosystem such that any changes would cause too much breakage. The threshold -for what qualifies as too much breakage is to be decided on a case-by-case -basis by the TSC. +Exceptions can be made if use or behavior of a given internal API can be +demonstrated to be sufficiently relied upon by the Node.js ecosystem such that +any changes would cause too much breakage. The threshold for what qualifies as +too much breakage is to be decided on a case-by-case basis by the TSC. If it is determined that a currently undocumented object, property, method, argument, or event *should* be documented, then a pull request adding the @@ -243,14 +248,14 @@ properties to an options argument) are semver-minor changes. #### Breaking Changes and Deprecations -With a few notable exceptions outlined below, when backwards incompatible -changes to a *Public* API are necessary, the existing API *must* be deprecated -*first* and the new API either introduced in parallel or added after the next -major Node.js version following the deprecation as a replacement for the -deprecated API. In other words, as a general rule, existing *Public* APIs -*must not* change (in a backwards incompatible way) without a deprecation. +With a few exceptions outlined below, when backward-incompatible changes to a +*Public* API are necessary, the existing API *must* be deprecated *first* and +the new API either introduced in parallel or added after the next major Node.js +version following the deprecation as a replacement for the deprecated API. In +other words, as a general rule, existing *Public* APIs *must not* change (in a +backward-incompatible way) without a deprecation. -Exception to this rule is given in the following cases: +Exceptions to this rule may be made in the following cases: * Adding or removing errors thrown or reported by a Public API; * Changing error messages; @@ -351,7 +356,7 @@ recommended but not required. ### Deprecations _Deprecation_ refers to the identification of Public APIs that should no longer -be used and that may be removed or modified in non-backwards compatible ways in +be used and that may be removed or modified in backward-incompatible ways in a future major release of Node.js. Deprecation may be used with internal APIs if there is expected impact on the user community. @@ -491,7 +496,7 @@ Check and re-review the changes: $ git diff upstream/master ``` -Check number of commits and commit messages: +Check the number of commits and commit messages: ```text $ git log upstream/master...master @@ -652,7 +657,7 @@ commit final. #### What is LTS? Long Term Support (often referred to as *LTS*) guarantees application developers -a 30 month support cycle with specific versions of Node.js. +a 30-month support cycle with specific versions of Node.js. You can find more information [in the full release plan](https://github.com/nodejs/Release#release-plan). @@ -665,7 +670,7 @@ certain performance improvements that can be demonstrated to not break existing applications. Semver-minor changes are only permitted if required for bug fixes and then only on a case-by-case basis with LTS WG and possibly Technical Steering Committee (TSC) review. Semver-major changes are permitted only if -required for security related fixes. +required for security-related fixes. Once a Current branch moves into Maintenance mode, only **critical** bugs, **critical** security fixes, and documentation updates will be permitted. diff --git a/Makefile b/Makefile index c9fed0c03b32f4..c947a2a4367d05 100644 --- a/Makefile +++ b/Makefile @@ -530,8 +530,7 @@ BINARYTAR=$(BINARYNAME).tar XZ=$(shell which xz > /dev/null 2>&1; echo $$?) XZ_COMPRESSION ?= 9e PKG=$(TARNAME).pkg -PACKAGEMAKER ?= /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker -PKGDIR=out/dist-osx +MACOSOUTDIR=out/macos release-only: @if [ "$(DISTTYPE)" != "nightly" ] && [ "$(DISTTYPE)" != "next-nightly" ] && \ @@ -561,24 +560,53 @@ release-only: fi $(PKG): release-only - $(RM) -r $(PKGDIR) - $(RM) -r out/deps out/Release + $(RM) -r $(MACOSOUTDIR) + mkdir -p $(MACOSOUTDIR)/installer/productbuild + cat tools/macos-installer/productbuild/distribution.xml.tmpl \ + | sed -E "s/\\{nodeversion\\}/$(FULLVERSION)/g" \ + | sed -E "s/\\{npmversion\\}/$(NPMVERSION)/g" \ + >$(MACOSOUTDIR)/installer/productbuild/distribution.xml ; \ + + @for dirname in tools/macos-installer/productbuild/Resources/*/; do \ + lang=$$(basename $$dirname) ; \ + mkdir -p $(MACOSOUTDIR)/installer/productbuild/Resources/$$lang ; \ + printf "Found localization directory $$dirname\n" ; \ + cat $$dirname/welcome.html.tmpl \ + | sed -E "s/\\{nodeversion\\}/$(FULLVERSION)/g" \ + | sed -E "s/\\{npmversion\\}/$(NPMVERSION)/g" \ + >$(MACOSOUTDIR)/installer/productbuild/Resources/$$lang/welcome.html ; \ + cat $$dirname/conclusion.html.tmpl \ + | sed -E "s/\\{nodeversion\\}/$(FULLVERSION)/g" \ + | sed -E "s/\\{npmversion\\}/$(NPMVERSION)/g" \ + >$(MACOSOUTDIR)/installer/productbuild/Resources/$$lang/conclusion.html ; \ + done $(PYTHON) ./configure \ --dest-cpu=x64 \ --tag=$(TAG) \ --release-urlbase=$(RELEASE_URLBASE) \ $(CONFIG_FLAGS) $(BUILD_RELEASE_FLAGS) - $(MAKE) install V=$(V) DESTDIR=$(PKGDIR) - SIGN="$(CODESIGN_CERT)" PKGDIR="$(PKGDIR)/usr/local" bash \ + $(MAKE) install V=$(V) DESTDIR=$(MACOSOUTDIR)/dist/node + SIGN="$(CODESIGN_CERT)" PKGDIR="$(MACOSOUTDIR)/dist/node/usr/local" bash \ tools/osx-codesign.sh - cat tools/osx-pkg.pmdoc/index.xml.tmpl \ - | sed -E "s/\\{nodeversion\\}/$(FULLVERSION)/g" \ - | sed -E "s/\\{npmversion\\}/$(NPMVERSION)/g" \ - > tools/osx-pkg.pmdoc/index.xml - $(PACKAGEMAKER) \ - --id "org.nodejs.pkg" \ - --doc tools/osx-pkg.pmdoc \ - --out $(PKG) + mkdir -p $(MACOSOUTDIR)/dist/npm/usr/local/lib/node_modules + mkdir -p $(MACOSOUTDIR)/pkgs + mv $(MACOSOUTDIR)/dist/node/usr/local/lib/node_modules/npm \ + $(MACOSOUTDIR)/dist/npm/usr/local/lib/node_modules + unlink $(MACOSOUTDIR)/dist/node/usr/local/bin/npm + $(NODE) tools/license2rtf.js < LICENSE > \ + $(MACOSOUTDIR)/installer/productbuild/Resources/license.rtf + cp doc/osx_installer_logo.png $(MACOSOUTDIR)/installer/productbuild/Resources + pkgbuild --version $(FULLVERSION) \ + --identifier org.nodejs.node.pkg \ + --root $(MACOSOUTDIR)/dist/node $(MACOSOUTDIR)/pkgs/node-$(FULLVERSION).pkg + pkgbuild --version $(NPMVERSION) \ + --identifier org.nodejs.npm.pkg \ + --root $(MACOSOUTDIR)/dist/npm \ + --scripts ./tools/macos-installer/pkgbuild/npm/scripts \ + $(MACOSOUTDIR)/pkgs/npm-$(NPMVERSION).pkg + productbuild --distribution $(MACOSOUTDIR)/installer/productbuild/distribution.xml \ + --resources $(MACOSOUTDIR)/installer/productbuild/Resources \ + --package-path $(MACOSOUTDIR)/pkgs ./$(PKG) SIGN="$(PRODUCTSIGN_CERT)" PKG="$(PKG)" bash tools/osx-productsign.sh pkg: $(PKG) diff --git a/README.md b/README.md index 7830a915824d35..43d2bad78234b3 100644 --- a/README.md +++ b/README.md @@ -172,23 +172,22 @@ Node.js from source along with a list of officially supported platforms. ## Security -All security bugs in Node.js are taken seriously and should be reported by -emailing security@nodejs.org. This will be delivered to a subset of the project -team who handle security issues. Please don't disclose security bugs -publicly until they have been handled by the security team. +Security flaws in Node.js should be reported by emailing security@nodejs.org. +Please do not disclose security bugs publicly until they have been handled by +the security team. -Your email will be acknowledged within 24 hours, and you’ll receive a more +Your email will be acknowledged within 24 hours, and you will receive a more detailed response to your email within 48 hours indicating the next steps in handling your report. There are no hard and fast rules to determine if a bug is worth reporting as -a security issue. The general rule is any issue worth reporting -must allow an attacker to compromise the confidentiality, integrity -or availability of the Node.js application or its system for which the attacker -does not already have the capability. +a security issue. The general rule is an issue worth reporting should allow an +attacker to compromise the confidentiality, integrity, or availability of the +Node.js application or its system for which the attacker does not already have +the capability. To illustrate the point, here are some examples of past issues and what the -Security Reponse Team thinks of them. When in doubt, however, please do send +Security Response Team thinks of them. When in doubt, however, please do send us a report nonetheless. @@ -241,6 +240,8 @@ For more information about the governance of the Node.js project, see **Сковорода Никита Андреевич** <chalkerx@gmail.com> (he/him) * [cjihrig](https://github.com/cjihrig) - **Colin Ihrig** <cjihrig@gmail.com> +* [danbev](https://github.com/danbev) - +**Daniel Bevenius** <daniel.bevenius@gmail.com> * [evanlucas](https://github.com/evanlucas) - **Evan Lucas** <evanlucas@me.com> (he/him) * [fhinkel](https://github.com/fhinkel) - @@ -251,8 +252,6 @@ For more information about the governance of the Node.js project, see **Fedor Indutny** <fedor.indutny@gmail.com> * [jasnell](https://github.com/jasnell) - **James M Snell** <jasnell@gmail.com> (he/him) -* [joshgav](https://github.com/joshgav) - -**Josh Gavant** <josh.gavant@outlook.com> * [joyeecheung](https://github.com/joyeecheung) - **Joyee Cheung** <joyeec9h3@gmail.com> (she/her) * [mcollina](https://github.com/mcollina) - @@ -284,6 +283,8 @@ For more information about the governance of the Node.js project, see **Chris Dickinson** <christopher.s.dickinson@gmail.com> * [isaacs](https://github.com/isaacs) - **Isaac Z. Schlueter** <i@izs.me> +* [joshgav](https://github.com/joshgav) - +**Josh Gavant** <josh.gavant@outlook.com> * [nebrius](https://github.com/nebrius) - **Bryan Hughes** <bryan@nebri.us> * [orangemocha](https://github.com/orangemocha) - @@ -415,8 +416,6 @@ For more information about the governance of the Node.js project, see **Luca Maraschi** <luca.maraschi@gmail.com> (he/him) * [maclover7](https://github.com/maclover7) - **Jon Moss** <me@jonathanmoss.me> (he/him) -* [matthewloring](https://github.com/matthewloring) - -**Matthew Loring** <mattloring@google.com> * [mcollina](https://github.com/mcollina) - **Matteo Collina** <matteo.collina@gmail.com> (he/him) * [mhdawson](https://github.com/mhdawson) - @@ -479,6 +478,8 @@ For more information about the governance of the Node.js project, see **Roman Reiss** <me@silverwind.io> * [srl295](https://github.com/srl295) - **Steven R Loomis** <srloomis@us.ibm.com> +* [starkwang](https://github.com/starkwang) - +**Weijia Wang** <starkwang@126.com> * [stefanmb](https://github.com/stefanmb) - **Stefan Budeanu** <stefan@budeanu.com> * [targos](https://github.com/targos) - @@ -490,7 +491,7 @@ For more information about the governance of the Node.js project, see * [thlorenz](https://github.com/thlorenz) - **Thorsten Lorenz** <thlorenz@gmx.de> * [TimothyGu](https://github.com/TimothyGu) - -**Timothy Gu** <timothygu99@gmail.com> (he/him) +**Tiancheng "Timothy" Gu** <timothygu99@gmail.com> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <tniessen@tnie.de> * [trevnorris](https://github.com/trevnorris) - @@ -520,6 +521,8 @@ For more information about the governance of the Node.js project, see **Isaac Z. Schlueter** <i@izs.me> * [lxe](https://github.com/lxe) - **Aleksey Smolenchuk** <lxe@lxe.co> +* [matthewloring](https://github.com/matthewloring) - +**Matthew Loring** <mattloring@google.com> * [monsanto](https://github.com/monsanto) - **Christopher Monsanto** <chris@monsan.to> * [Olegas](https://github.com/Olegas) - @@ -590,7 +593,7 @@ Previous releases may also have been signed with one of the following GPG keys: * [Working Groups][] [npm]: https://www.npmjs.com -[Code of Conduct]: https://github.com/nodejs/TSC/blob/master/CODE_OF_CONDUCT.md +[Code of Conduct]: https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md [Contributing to the project]: CONTRIBUTING.md [Node.js Help]: https://github.com/nodejs/help [Node.js Website]: https://nodejs.org/en/ diff --git a/benchmark/fs/write-stream-throughput.js b/benchmark/fs/write-stream-throughput.js index 812dc369d7b5b3..432748f996abb2 100644 --- a/benchmark/fs/write-stream-throughput.js +++ b/benchmark/fs/write-stream-throughput.js @@ -40,10 +40,6 @@ function main(conf) { var started = false; var ending = false; var ended = false; - setTimeout(function() { - ending = true; - f.end(); - }, dur * 1000); var f = fs.createWriteStream(filename); f.on('drain', write); @@ -65,6 +61,10 @@ function main(conf) { if (!started) { started = true; + setTimeout(function() { + ending = true; + f.end(); + }, dur * 1000); bench.start(); } diff --git a/benchmark/url/legacy-vs-whatwg-url-get-prop.js b/benchmark/url/legacy-vs-whatwg-url-get-prop.js new file mode 100644 index 00000000000000..229a4e60652b64 --- /dev/null +++ b/benchmark/url/legacy-vs-whatwg-url-get-prop.js @@ -0,0 +1,97 @@ +'use strict'; +const common = require('../common.js'); +const url = require('url'); +const URL = url.URL; +const assert = require('assert'); +const inputs = require('../fixtures/url-inputs.js').urls; + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + method: ['legacy', 'whatwg'], + n: [1e5] +}); + +// At the time of writing, when using a passed property name to index +// the object, Crankshaft would generate a LoadKeyedGeneric even when it +// remains a constant in the function, so here we must use the literal +// instead to get a LoadNamedField. +function useLegacy(n, input) { + const obj = url.parse(input); + const noDead = { + protocol: obj.protocol, + auth: obj.auth, + host: obj.host, + hostname: obj.hostname, + port: obj.port, + pathname: obj.pathname, + search: obj.search, + hash: obj.hash + }; + // It's necessary to assign the values to an object + // to avoid loop invariant code motion. + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead.protocol = obj.protocol; + noDead.auth = obj.auth; + noDead.host = obj.host; + noDead.hostname = obj.hostname; + noDead.port = obj.port; + noDead.pathname = obj.pathname; + noDead.search = obj.search; + noDead.hash = obj.hash; + } + bench.end(n); + return noDead; +} + +function useWHATWG(n, input) { + const obj = new URL(input); + const noDead = { + protocol: obj.protocol, + auth: `${obj.username}:${obj.password}`, + host: obj.host, + hostname: obj.hostname, + port: obj.port, + pathname: obj.pathname, + search: obj.search, + hash: obj.hash + }; + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead.protocol = obj.protocol; + noDead.auth = `${obj.username}:${obj.password}`; + noDead.host = obj.host; + noDead.hostname = obj.hostname; + noDead.port = obj.port; + noDead.pathname = obj.pathname; + noDead.search = obj.search; + noDead.hash = obj.hash; + } + bench.end(n); + return noDead; +} + +function main(conf) { + const type = conf.type; + const n = conf.n | 0; + const method = conf.method; + + const input = inputs[type]; + if (!input) { + throw new Error('Unknown input type'); + } + + var noDead; // Avoid dead code elimination. + switch (method) { + case 'legacy': + noDead = useLegacy(n, input); + break; + case 'whatwg': + noDead = useWHATWG(n, input); + break; + default: + throw new Error('Unknown method'); + } + + assert.ok(noDead); +} diff --git a/benchmark/url/legacy-vs-whatwg-url-parse.js b/benchmark/url/legacy-vs-whatwg-url-parse.js new file mode 100644 index 00000000000000..ec386b7b85597d --- /dev/null +++ b/benchmark/url/legacy-vs-whatwg-url-parse.js @@ -0,0 +1,57 @@ +'use strict'; +const common = require('../common.js'); +const url = require('url'); +const URL = url.URL; +const assert = require('assert'); +const inputs = require('../fixtures/url-inputs.js').urls; + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + method: ['legacy', 'whatwg'], + n: [1e5] +}); + +function useLegacy(n, input) { + var noDead = url.parse(input); + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead = url.parse(input); + } + bench.end(n); + return noDead; +} + +function useWHATWG(n, input) { + var noDead = new URL(input); + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead = new URL(input); + } + bench.end(n); + return noDead; +} + +function main(conf) { + const type = conf.type; + const n = conf.n | 0; + const method = conf.method; + + const input = inputs[type]; + if (!input) { + throw new Error('Unknown input type'); + } + + var noDead; // Avoid dead code elimination. + switch (method) { + case 'legacy': + noDead = useLegacy(n, input); + break; + case 'whatwg': + noDead = useWHATWG(n, input); + break; + default: + throw new Error('Unknown method'); + } + + assert.ok(noDead); +} diff --git a/benchmark/url/legacy-vs-whatwg-url-searchparams-parse.js b/benchmark/url/legacy-vs-whatwg-url-searchparams-parse.js new file mode 100644 index 00000000000000..b4a80af4e5eabd --- /dev/null +++ b/benchmark/url/legacy-vs-whatwg-url-searchparams-parse.js @@ -0,0 +1,51 @@ +'use strict'; +const common = require('../common.js'); +const { URLSearchParams } = require('url'); +const querystring = require('querystring'); +const inputs = require('../fixtures/url-inputs.js').searchParams; + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + method: ['legacy', 'whatwg'], + n: [1e6] +}); + +function useLegacy(n, input) { + querystring.parse(input); + bench.start(); + for (var i = 0; i < n; i += 1) { + querystring.parse(input); + } + bench.end(n); +} + +function useWHATWG(n, input) { + new URLSearchParams(input); + bench.start(); + for (var i = 0; i < n; i += 1) { + new URLSearchParams(input); + } + bench.end(n); +} + +function main(conf) { + const type = conf.type; + const n = conf.n | 0; + const method = conf.method; + + const input = inputs[type]; + if (!input) { + throw new Error('Unknown input type'); + } + + switch (method) { + case 'legacy': + useLegacy(n, input); + break; + case 'whatwg': + useWHATWG(n, input); + break; + default: + throw new Error('Unknown method'); + } +} diff --git a/benchmark/url/legacy-vs-whatwg-url-searchparams-serialize.js b/benchmark/url/legacy-vs-whatwg-url-searchparams-serialize.js new file mode 100644 index 00000000000000..2b8d2c36a810b3 --- /dev/null +++ b/benchmark/url/legacy-vs-whatwg-url-searchparams-serialize.js @@ -0,0 +1,53 @@ +'use strict'; +const common = require('../common.js'); +const { URLSearchParams } = require('url'); +const querystring = require('querystring'); +const inputs = require('../fixtures/url-inputs.js').searchParams; + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + method: ['legacy', 'whatwg'], + n: [1e6] +}); + +function useLegacy(n, input, prop) { + const obj = querystring.parse(input); + querystring.stringify(obj); + bench.start(); + for (var i = 0; i < n; i += 1) { + querystring.stringify(obj); + } + bench.end(n); +} + +function useWHATWG(n, input, prop) { + const obj = new URLSearchParams(input); + obj.toString(); + bench.start(); + for (var i = 0; i < n; i += 1) { + obj.toString(); + } + bench.end(n); +} + +function main(conf) { + const type = conf.type; + const n = conf.n | 0; + const method = conf.method; + + const input = inputs[type]; + if (!input) { + throw new Error('Unknown input type'); + } + + switch (method) { + case 'legacy': + useLegacy(n, input); + break; + case 'whatwg': + useWHATWG(n, input); + break; + default: + throw new Error('Unknown method'); + } +} diff --git a/benchmark/url/legacy-vs-whatwg-url-serialize.js b/benchmark/url/legacy-vs-whatwg-url-serialize.js new file mode 100644 index 00000000000000..35b459a10c0e0b --- /dev/null +++ b/benchmark/url/legacy-vs-whatwg-url-serialize.js @@ -0,0 +1,59 @@ +'use strict'; +const common = require('../common.js'); +const url = require('url'); +const URL = url.URL; +const assert = require('assert'); +const inputs = require('../fixtures/url-inputs.js').urls; + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + method: ['legacy', 'whatwg'], + n: [1e5] +}); + +function useLegacy(n, input, prop) { + const obj = url.parse(input); + var noDead = url.format(obj); + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead = url.format(obj); + } + bench.end(n); + return noDead; +} + +function useWHATWG(n, input, prop) { + const obj = new URL(input); + var noDead = obj.toString(); + bench.start(); + for (var i = 0; i < n; i += 1) { + noDead = obj.toString(); + } + bench.end(n); + return noDead; +} + +function main(conf) { + const type = conf.type; + const n = conf.n | 0; + const method = conf.method; + + const input = inputs[type]; + if (!input) { + throw new Error('Unknown input type'); + } + + var noDead; // Avoid dead code elimination. + switch (method) { + case 'legacy': + noDead = useLegacy(n, input); + break; + case 'whatwg': + noDead = useWHATWG(n, input); + break; + default: + throw new Error('Unknown method'); + } + + assert.ok(noDead); +} diff --git a/benchmark/url/url-searchparams-iteration.js b/benchmark/url/url-searchparams-iteration.js new file mode 100644 index 00000000000000..0f4b71a0a183dd --- /dev/null +++ b/benchmark/url/url-searchparams-iteration.js @@ -0,0 +1,61 @@ +'use strict'; +const common = require('../common.js'); +const assert = require('assert'); +const { URLSearchParams } = require('url'); + +const bench = common.createBenchmark(main, { + method: ['forEach', 'iterator'], + n: [1e6] +}); + +const str = 'one=single&two=first&three=first&two=2nd&three=2nd&three=3rd'; + +function forEach(n) { + const params = new URLSearchParams(str); + const noDead = []; + const cb = (val, key) => { + noDead[0] = key; + noDead[1] = val; + }; + + bench.start(); + for (var i = 0; i < n; i += 1) + params.forEach(cb); + bench.end(n); + + assert.strictEqual(noDead[0], 'three'); + assert.strictEqual(noDead[1], '3rd'); +} + +function iterator(n) { + const params = new URLSearchParams(str); + const noDead = []; + + bench.start(); + for (var i = 0; i < n; i += 1) { + for (const pair of params) { + noDead[0] = pair[0]; + noDead[1] = pair[1]; + } + } + bench.end(n); + + assert.strictEqual(noDead[0], 'three'); + assert.strictEqual(noDead[1], '3rd'); +} + +function main(conf) { + const method = conf.method; + const n = conf.n | 0; + + switch (method) { + case 'forEach': + forEach(n); + break; + case 'iterator': + iterator(n); + break; + default: + throw new Error('Unknown method'); + } +} diff --git a/benchmark/url/url-searchparams-read.js b/benchmark/url/url-searchparams-read.js new file mode 100644 index 00000000000000..762ffcca03d69d --- /dev/null +++ b/benchmark/url/url-searchparams-read.js @@ -0,0 +1,58 @@ +'use strict'; +const common = require('../common.js'); +const { URLSearchParams } = require('url'); + +const bench = common.createBenchmark(main, { + method: ['get', 'getAll', 'has'], + param: ['one', 'two', 'three', 'nonexistent'], + n: [2e7] +}); + +const str = 'one=single&two=first&three=first&two=2nd&three=2nd&three=3rd'; + +function get(n, param) { + const params = new URLSearchParams(str); + + bench.start(); + for (var i = 0; i < n; i += 1) + params.get(param); + bench.end(n); +} + +function getAll(n, param) { + const params = new URLSearchParams(str); + + bench.start(); + for (var i = 0; i < n; i += 1) + params.getAll(param); + bench.end(n); +} + +function has(n, param) { + const params = new URLSearchParams(str); + + bench.start(); + for (var i = 0; i < n; i += 1) + params.has(param); + bench.end(n); +} + +function main(conf) { + const method = conf.method; + const param = conf.param; + const n = conf.n | 0; + + switch (method) { + case 'get': + get(n, param); + break; + case 'getAll': + getAll(n, param); + break; + case 'has': + has(n, param); + break; + default: + throw new Error('Unknown method'); + } +} diff --git a/benchmark/url/url-searchparams-sort.js b/benchmark/url/url-searchparams-sort.js new file mode 100644 index 00000000000000..677ce511cf3ea2 --- /dev/null +++ b/benchmark/url/url-searchparams-sort.js @@ -0,0 +1,48 @@ +'use strict'; +const common = require('../common.js'); +const URLSearchParams = require('url').URLSearchParams; + +const inputs = { + empty: '', + sorted: 'a&b&c&d&e&f&g&h&i&j&k&l&m&n&o&p&q&r&s&t&u&v&w&x&y&z', + almostsorted: 'a&b&c&d&e&f&g&i&h&j&k&l&m&n&o&p&q&r&s&t&u&w&v&x&y&z', + reversed: 'z&y&x&w&v&u&t&s&r&q&p&o&n&m&l&k&j&i&h&g&f&e&d&c&b&a', + random: 'm&t&d&c&z&v&a&n&p&y&u&o&h&l&f&j&e&q&b&i&s&x&k&w&r&g', + // 8 parameters + short: 'm&t&d&c&z&v&a&n', + // 88 parameters + long: 'g&r&t&h&s&r&d&w&b&n&h&k&x&m&k&h&o&e&x&c&c&g&e&b&p&p&s&n&j&b&y&z&' + + 'u&l&o&r&w&a&u&l&m&f&j&q&p&f&e&y&e&n&e&l&m&w&u&w&t&n&t&q&v&y&c&o&' + + 'k&f&j&i&l&m&g&j&d&i&z&q&p&x&q&q&d&n&y&w&g&i&v&r' +}; + +function getParams(str) { + const out = []; + for (const key of str.split('&')) { + out.push(key, ''); + } + return out; +} + +const bench = common.createBenchmark(main, { + type: Object.keys(inputs), + n: [1e6] +}, { + flags: ['--expose-internals'] +}); + +function main(conf) { + const searchParams = require('internal/url').searchParamsSymbol; + const input = inputs[conf.type]; + const n = conf.n | 0; + const params = new URLSearchParams(); + const array = getParams(input); + + var i; + bench.start(); + for (i = 0; i < n; i++) { + params[searchParams] = array.slice(); + params.sort(); + } + bench.end(n); +} diff --git a/benchmark/url/usvstring.js b/benchmark/url/usvstring.js new file mode 100644 index 00000000000000..40a945037385cf --- /dev/null +++ b/benchmark/url/usvstring.js @@ -0,0 +1,28 @@ +'use strict'; +const common = require('../common.js'); + +const inputs = { + valid: 'adsfadsfadsf', + validsurr: '\uda23\ude23\uda1f\udfaa\ud800\udfff\uda23\ude23\uda1f\udfaa' + + '\ud800\udfff', + someinvalid: 'asasfdfasd\uda23', + allinvalid: '\udc45\uda23 \udf00\udc00 \udfaa\uda12 \udc00\udfaa', + nonstring: { toString() { return 'asdf'; } } +}; +const bench = common.createBenchmark(main, { + input: Object.keys(inputs), + n: [5e7] +}, { + flags: ['--expose-internals'] +}); + +function main(conf) { + const { toUSVString } = require('internal/url'); + const str = inputs[conf.input]; + const n = conf.n | 0; + + bench.start(); + for (var i = 0; i < n; i++) + toUSVString(str); + bench.end(n); +} diff --git a/benchmark/url/whatwg-url-idna.js b/benchmark/url/whatwg-url-idna.js new file mode 100644 index 00000000000000..3d0ea3dc8fe516 --- /dev/null +++ b/benchmark/url/whatwg-url-idna.js @@ -0,0 +1,45 @@ +'use strict'; +const common = require('../common.js'); +const { domainToASCII, domainToUnicode } = require('url'); + +const inputs = { + empty: { + ascii: '', + unicode: '' + }, + none: { + ascii: 'passports', + unicode: 'passports' + }, + some: { + ascii: 'Paßstraße', + unicode: 'xn--Pastrae-1vae' + }, + all: { + ascii: '他们不说中文', + unicode: 'xn--ihqwczyycu19kkg2c' + }, + nonstring: { + ascii: { toString() { return ''; } }, + unicode: { toString() { return ''; } } + } +}; + +const bench = common.createBenchmark(main, { + input: Object.keys(inputs), + to: ['ascii', 'unicode'], + n: [5e6] +}); + +function main(conf) { + const n = conf.n | 0; + const to = conf.to; + const input = inputs[conf.input][to]; + const method = to === 'ascii' ? domainToASCII : domainToUnicode; + + bench.start(); + for (var i = 0; i < n; i++) { + method(input); + } + bench.end(n); +} diff --git a/benchmark/url/whatwg-url-properties.js b/benchmark/url/whatwg-url-properties.js new file mode 100644 index 00000000000000..3a865d2335ab3c --- /dev/null +++ b/benchmark/url/whatwg-url-properties.js @@ -0,0 +1,75 @@ +'use strict'; +const common = require('../common.js'); +const URL = require('url').URL; +const inputs = require('../fixtures/url-inputs.js').urls; + +const bench = common.createBenchmark(main, { + input: Object.keys(inputs), + prop: ['href', 'origin', 'protocol', + 'username', 'password', 'host', 'hostname', 'port', + 'pathname', 'search', 'searchParams', 'hash'], + n: [3e5] +}); + +function setAndGet(n, url, prop, alternative) { + const old = url[prop]; + bench.start(); + for (var i = 0; i < n; i += 1) { + url[prop] = n % 2 === 0 ? alternative : old; // set + url[prop]; // get + } + bench.end(n); +} + +function get(n, url, prop) { + bench.start(); + for (var i = 0; i < n; i += 1) { + url[prop]; // get + } + bench.end(n); +} + +const alternatives = { + href: 'http://user:pass@foo.bar.com:21/aaa/zzz?l=25#test', + protocol: 'https:', + username: 'user2', + password: 'pass2', + host: 'foo.bar.net:22', + hostname: 'foo.bar.org', + port: '23', + pathname: '/aaa/bbb', + search: '?k=99', + hash: '#abcd' +}; + +function getAlternative(prop) { + return alternatives[prop]; +} + +function main(conf) { + const n = conf.n | 0; + const input = inputs[conf.input]; + const url = new URL(input); + const prop = conf.prop; + + switch (prop) { + case 'protocol': + case 'username': + case 'password': + case 'host': + case 'hostname': + case 'port': + case 'pathname': + case 'search': + case 'hash': + case 'href': + setAndGet(n, url, prop, getAlternative(prop)); + break; + case 'origin': + case 'searchParams': + get(n, url, prop); + break; + default: + throw new Error('Unknown prop'); + } +} diff --git a/configure b/configure index 10d46159a83923..e67a15c4753dd3 100755 --- a/configure +++ b/configure @@ -172,6 +172,12 @@ parser.add_option('--openssl-use-def-ca-store', dest='use_openssl_ca_store', help='Use OpenSSL supplied CA store instead of compiled-in Mozilla CA copy.') +parser.add_option('--openssl-system-ca-path', + action='store', + dest='openssl_system_ca_path', + help='Use the specified path to system CA (PEM format) in addition to ' + 'the OpenSSL supplied CA store or compiled-in Mozilla CA copy.') + shared_optgroup.add_option('--shared-http-parser', action='store_true', dest='shared_http_parser', @@ -988,6 +994,8 @@ def configure_openssl(o): o['variables']['openssl_no_asm'] = 1 if options.openssl_no_asm else 0 if options.use_openssl_ca_store: o['defines'] += ['NODE_OPENSSL_CERT_STORE'] + if options.openssl_system_ca_path: + o['variables']['openssl_system_ca_path'] = options.openssl_system_ca_path o['variables']['node_without_node_options'] = b(options.without_node_options) if options.without_node_options: o['defines'] += ['NODE_WITHOUT_NODE_OPTIONS'] diff --git a/deps/uv/.mailmap b/deps/uv/.mailmap index 500c2b5185c5e8..da4214365c4155 100644 --- a/deps/uv/.mailmap +++ b/deps/uv/.mailmap @@ -26,6 +26,7 @@ Marc Schlaich Michael Michael Neumann Nicholas Vavilov +Nick Logan Rasmus Christian Pedersen Rasmus Christian Pedersen Robert Mustacchi diff --git a/deps/uv/AUTHORS b/deps/uv/AUTHORS index 4747c06b30600f..03255534fa1de3 100644 --- a/deps/uv/AUTHORS +++ b/deps/uv/AUTHORS @@ -315,3 +315,6 @@ darobs Zheng, Lei Carlo Marcelo Arenas Belón Scott Parker +Wade Brainerd +rayrase +Pekka Nikander diff --git a/deps/uv/ChangeLog b/deps/uv/ChangeLog index 64b18695e16847..595b3871278898 100644 --- a/deps/uv/ChangeLog +++ b/deps/uv/ChangeLog @@ -1,3 +1,55 @@ +2017.11.11, Version 1.16.1 (Stable), 4056fbe46493ef87237e307e0025e551db875e13 + +Changes since version 1.16.0: + +* unix: move net/if.h include (cjihrig) + +* win: fix undeclared NDIS_IF_MAX_STRING_SIZE (Nick Logan) + + +2017.11.07, Version 1.16.0 (Stable), d68779f0ea742918f653b9c20237460271c39aeb + +Changes since version 1.15.0: + +* win: change st_blksize from `2048` to `4096` (Joran Dirk Greef) + +* unix,win: add fs open flags, map O_DIRECT|O_DSYNC (Joran Dirk Greef) + +* win, fs: fix non-symlink reparse points (Wade Brainerd) + +* test: fix -Wstrict-prototypes warnings (Ben Noordhuis) + +* unix, windows: map ENOTTY errno (Ben Noordhuis) + +* unix: fall back to fsync() if F_FULLFSYNC fails (Joran Dirk Greef) + +* unix: do not close invalid kqueue fd after fork (jBarz) + +* zos: reset epoll data after fork (jBarz) + +* zos: skip fork_threadpool_queue_work_simple (jBarz) + +* test: keep platform_output as first test (Bartosz Sosnowski) + +* win: fix non-English dlopen error message (Bartosz Sosnowski) + +* unix,win: add uv_os_getppid() (cjihrig) + +* test: fix const qualification compiler warning (Ben Noordhuis) + +* doc: mark uv_default_loop() as not thread safe (rayrase) + +* win, pipe: null-initialize stream->shutdown_req (Jameson Nash) + +* tty, win: get SetWinEventHook pointer at startup (Bartosz Sosnowski) + +* test: no extra new line in skipped test output (Bartosz Sosnowski) + +* pipe: allow access from other users (Bartosz Sosnowski) + +* unix,win: add uv_if_{indextoname,indextoiid} (Pekka Nikander) + + 2017.10.03, Version 1.15.0 (Stable), 8b69ce1419d2958011d415a636810705c36c2cc2 Changes since version 1.14.1: diff --git a/deps/uv/Makefile.am b/deps/uv/Makefile.am index b94fdd63b58eea..6e548a69c374e6 100644 --- a/deps/uv/Makefile.am +++ b/deps/uv/Makefile.am @@ -212,6 +212,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-pipe-server-close.c \ test/test-pipe-close-stdout-read-stdin.c \ test/test-pipe-set-non-blocking.c \ + test/test-pipe-set-fchmod.c \ test/test-platform-output.c \ test/test-poll.c \ test/test-poll-close.c \ diff --git a/deps/uv/appveyor.yml b/deps/uv/appveyor.yml index f519bc099b6220..8ad69718b6e619 100644 --- a/deps/uv/appveyor.yml +++ b/deps/uv/appveyor.yml @@ -1,4 +1,4 @@ -version: v1.15.0.build{build} +version: v1.16.1.build{build} init: - git config --global core.autocrlf true diff --git a/deps/uv/configure.ac b/deps/uv/configure.ac index ebf5bc3d8ef9e5..5fc0f72434d92e 100644 --- a/deps/uv/configure.ac +++ b/deps/uv/configure.ac @@ -13,7 +13,7 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. AC_PREREQ(2.57) -AC_INIT([libuv], [1.15.0], [https://github.com/libuv/libuv/issues]) +AC_INIT([libuv], [1.16.1], [https://github.com/libuv/libuv/issues]) AC_CONFIG_MACRO_DIR([m4]) m4_include([m4/libuv-extra-automake-flags.m4]) m4_include([m4/as_case.m4]) diff --git a/deps/uv/docs/src/fs.rst b/deps/uv/docs/src/fs.rst index 2db915bc9e6706..f46c4e761a5d2f 100644 --- a/deps/uv/docs/src/fs.rst +++ b/deps/uv/docs/src/fs.rst @@ -353,3 +353,154 @@ Helper functions any attempts to close it or to use it after closing the fd may lead to malfunction. .. versionadded:: 1.12.0 + +File open constants +------------------- + +.. c:macro:: UV_FS_O_APPEND + + The file is opened in append mode. Before each write, the file offset is + positioned at the end of the file. + +.. c:macro:: UV_FS_O_CREAT + + The file is created if it does not already exist. + +.. c:macro:: UV_FS_O_DIRECT + + File I/O is done directly to and from user-space buffers, which must be + aligned. Buffer size and address should be a multiple of the physical sector + size of the block device. + + .. note:: + `UV_FS_O_DIRECT` is supported on Linux, and on Windows via + `FILE_FLAG_NO_BUFFERING `_. + `UV_FS_O_DIRECT` is not supported on macOS. + +.. c:macro:: UV_FS_O_DIRECTORY + + If the path is not a directory, fail the open. + + .. note:: + `UV_FS_O_DIRECTORY` is not supported on Windows. + +.. c:macro:: UV_FS_O_DSYNC + + The file is opened for synchronous I/O. Write operations will complete once + all data and a minimum of metadata are flushed to disk. + + .. note:: + `UV_FS_O_DSYNC` is supported on Windows via + `FILE_FLAG_WRITE_THROUGH `_. + +.. c:macro:: UV_FS_O_EXCL + + If the `O_CREAT` flag is set and the file already exists, fail the open. + + .. note:: + In general, the behavior of `O_EXCL` is undefined if it is used without + `O_CREAT`. There is one exception: on Linux 2.6 and later, `O_EXCL` can + be used without `O_CREAT` if pathname refers to a block device. If the + block device is in use by the system (e.g., mounted), the open will fail + with the error `EBUSY`. + +.. c:macro:: UV_FS_O_EXLOCK + + Atomically obtain an exclusive lock. + + .. note:: + `UV_FS_O_EXLOCK` is only supported on macOS. + +.. c:macro:: UV_FS_O_NOATIME + + Do not update the file access time when the file is read. + + .. note:: + `UV_FS_O_NOATIME` is not supported on Windows. + +.. c:macro:: UV_FS_O_NOCTTY + + If the path identifies a terminal device, opening the path will not cause + that terminal to become the controlling terminal for the process (if the + process does not already have one). + + .. note:: + `UV_FS_O_NOCTTY` is not supported on Windows. + +.. c:macro:: UV_FS_O_NOFOLLOW + + If the path is a symbolic link, fail the open. + + .. note:: + `UV_FS_O_NOFOLLOW` is not supported on Windows. + +.. c:macro:: UV_FS_O_NONBLOCK + + Open the file in nonblocking mode if possible. + + .. note:: + `UV_FS_O_NONBLOCK` is not supported on Windows. + +.. c:macro:: UV_FS_O_RANDOM + + Access is intended to be random. The system can use this as a hint to + optimize file caching. + + .. note:: + `UV_FS_O_RANDOM` is only supported on Windows via + `FILE_FLAG_RANDOM_ACCESS `_. + +.. c:macro:: UV_FS_O_RDONLY + + Open the file for read-only access. + +.. c:macro:: UV_FS_O_RDWR + + Open the file for read-write access. + +.. c:macro:: UV_FS_O_SEQUENTIAL + + Access is intended to be sequential from beginning to end. The system can + use this as a hint to optimize file caching. + + .. note:: + `UV_FS_O_SEQUENTIAL` is only supported on Windows via + `FILE_FLAG_SEQUENTIAL_SCAN `_. + +.. c:macro:: UV_FS_O_SHORT_LIVED + + The file is temporary and should not be flushed to disk if possible. + + .. note:: + `UV_FS_O_SHORT_LIVED` is only supported on Windows via + `FILE_ATTRIBUTE_TEMPORARY `_. + +.. c:macro:: UV_FS_O_SYMLINK + + Open the symbolic link itself rather than the resource it points to. + +.. c:macro:: UV_FS_O_SYNC + + The file is opened for synchronous I/O. Write operations will complete once + all data and all metadata are flushed to disk. + + .. note:: + `UV_FS_O_SYNC` is supported on Windows via + `FILE_FLAG_WRITE_THROUGH `_. + +.. c:macro:: UV_FS_O_TEMPORARY + + The file is temporary and should not be flushed to disk if possible. + + .. note:: + `UV_FS_O_TEMPORARY` is only supported on Windows via + `FILE_ATTRIBUTE_TEMPORARY `_. + +.. c:macro:: UV_FS_O_TRUNC + + If the file exists and is a regular file, and the file is opened + successfully for write access, its length shall be truncated to zero. + +.. c:macro:: UV_FS_O_WRONLY + + Open the file for write-only access. diff --git a/deps/uv/docs/src/loop.rst b/deps/uv/docs/src/loop.rst index 02543171de488b..c63f813993ff4b 100644 --- a/deps/uv/docs/src/loop.rst +++ b/deps/uv/docs/src/loop.rst @@ -86,6 +86,9 @@ API should) be closed with :c:func:`uv_loop_close` so the resources associated with it are freed. + .. warning:: + This function is not thread safe. + .. c:function:: int uv_run(uv_loop_t* loop, uv_run_mode mode) This function runs the event loop. It will act differently depending on the diff --git a/deps/uv/docs/src/misc.rst b/deps/uv/docs/src/misc.rst index 3fea708a8d38e2..2968d1cea1cc2b 100644 --- a/deps/uv/docs/src/misc.rst +++ b/deps/uv/docs/src/misc.rst @@ -59,6 +59,12 @@ Data types Abstract representation of a file descriptor. On Unix systems this is a `typedef` of `int` and on Windows a `HANDLE`. +.. c:type:: uv_pid_t + + Cross platform representation of a `pid_t`. + + .. versionadded:: 1.16.0 + .. c:type:: uv_rusage_t Data type for resource usage results. @@ -221,6 +227,12 @@ API On Windows not all fields are set, the unsupported fields are filled with zeroes. See :c:type:`uv_rusage_t` for more details. +.. c:function:: uv_pid_t uv_os_getppid(void) + + Returns the parent process ID. + + .. versionadded:: 1.16.0 + .. c:function:: int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) Gets information about the CPUs on the system. The `cpu_infos` array will @@ -271,6 +283,60 @@ API and :man:`inet_pton(3)`. On success they return 0. In case of error the target `dst` pointer is unmodified. +.. c:macro:: UV_IF_NAMESIZE + + Maximum IPv6 interface identifier name length. Defined as + `IFNAMSIZ` on Unix and `IF_NAMESIZE` on Linux and Windows. + + .. versionadded:: 1.16.0 + +.. c:function:: int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) + + IPv6-capable implementation of :man:`if_indextoname(3)`. When called, + `*size` indicates the length of the `buffer`, which is used to store the + result. + On success, zero is returned, `buffer` contains the interface name, and + `*size` represents the string length of the `buffer`, excluding the NUL + terminator byte from `*size`. On error, a negative result is + returned. If `buffer` is not large enough to hold the result, + `UV_ENOBUFS` is returned, and `*size` represents the necessary size in + bytes, including the NUL terminator byte into the `*size`. + + On Unix, the returned interface name can be used directly as an + interface identifier in scoped IPv6 addresses, e.g. + `fe80::abc:def1:2345%en0`. + + On Windows, the returned interface cannot be used as an interface + identifier, as Windows uses numerical interface identifiers, e.g. + `fe80::abc:def1:2345%5`. + + To get an interface identifier in a cross-platform compatible way, + use `uv_if_indextoiid()`. + + Example: + + :: + + char ifname[UV_IF_NAMESIZE]; + size_t size = sizeof(ifname); + uv_if_indextoname(sin6->sin6_scope_id, ifname, &size); + + .. versionadded:: 1.16.0 + +.. c:function:: int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) + + Retrieves a network interface identifier suitable for use in an IPv6 scoped + address. On Windows, returns the numeric `ifindex` as a string. On all other + platforms, `uv_if_indextoname()` is called. The result is written to + `buffer`, with `*size` indicating the length of `buffer`. If `buffer` is not + large enough to hold the result, then `UV_ENOBUFS` is returned, and `*size` + represents the size, including the NUL byte, required to hold the + result. + + See `uv_if_indextoname` for further details. + + .. versionadded:: 1.16.0 + .. c:function:: int uv_exepath(char* buffer, size_t* size) Gets the executable path. diff --git a/deps/uv/docs/src/pipe.rst b/deps/uv/docs/src/pipe.rst index d33b0f2b977b5b..bdaeeba9d435d5 100644 --- a/deps/uv/docs/src/pipe.rst +++ b/deps/uv/docs/src/pipe.rst @@ -102,3 +102,12 @@ API and call ``uv_accept(pipe, handle)``. .. seealso:: The :c:type:`uv_stream_t` API functions also apply. + +.. c:function:: int uv_pipe_chmod(uv_pipe_t* handle, int flags) + + Alters pipe permissions, allowing it to be accessed from processes run by + different users. Makes the pipe writable or readable by all users. Mode can + be ``UV_WRITABLE``, ``UV_READABLE`` or ``UV_WRITABLE | UV_READABLE``. This + function is blocking. + + .. versionadded:: 1.16.0 diff --git a/deps/uv/include/uv-errno.h b/deps/uv/include/uv-errno.h index 32bbc5177e4b2d..8a415331479873 100644 --- a/deps/uv/include/uv-errno.h +++ b/deps/uv/include/uv-errno.h @@ -422,4 +422,10 @@ # define UV__EREMOTEIO (-4030) #endif +#if defined(ENOTTY) && !defined(_WIN32) +# define UV__ENOTTY (-ENOTTY) +#else +# define UV__ENOTTY (-4029) +#endif + #endif /* UV_ERRNO_H_ */ diff --git a/deps/uv/include/uv-unix.h b/deps/uv/include/uv-unix.h index d7754509b1e27b..6565ff441ef292 100644 --- a/deps/uv/include/uv-unix.h +++ b/deps/uv/include/uv-unix.h @@ -123,6 +123,7 @@ typedef struct uv_buf_t { typedef int uv_file; typedef int uv_os_sock_t; typedef int uv_os_fd_t; +typedef pid_t uv_pid_t; #define UV_ONCE_INIT PTHREAD_ONCE_INIT @@ -365,4 +366,97 @@ typedef struct { uv_fs_event_cb cb; \ UV_PLATFORM_FS_EVENT_FIELDS \ +/* fs open() flags supported on this platform: */ +#if defined(O_APPEND) +# define UV_FS_O_APPEND O_APPEND +#else +# define UV_FS_O_APPEND 0 +#endif +#if defined(O_CREAT) +# define UV_FS_O_CREAT O_CREAT +#else +# define UV_FS_O_CREAT 0 +#endif +#if defined(O_DIRECT) +# define UV_FS_O_DIRECT O_DIRECT +#else +# define UV_FS_O_DIRECT 0 +#endif +#if defined(O_DIRECTORY) +# define UV_FS_O_DIRECTORY O_DIRECTORY +#else +# define UV_FS_O_DIRECTORY 0 +#endif +#if defined(O_DSYNC) +# define UV_FS_O_DSYNC O_DSYNC +#else +# define UV_FS_O_DSYNC 0 +#endif +#if defined(O_EXCL) +# define UV_FS_O_EXCL O_EXCL +#else +# define UV_FS_O_EXCL 0 +#endif +#if defined(O_EXLOCK) +# define UV_FS_O_EXLOCK O_EXLOCK +#else +# define UV_FS_O_EXLOCK 0 +#endif +#if defined(O_NOATIME) +# define UV_FS_O_NOATIME O_NOATIME +#else +# define UV_FS_O_NOATIME 0 +#endif +#if defined(O_NOCTTY) +# define UV_FS_O_NOCTTY O_NOCTTY +#else +# define UV_FS_O_NOCTTY 0 +#endif +#if defined(O_NOFOLLOW) +# define UV_FS_O_NOFOLLOW O_NOFOLLOW +#else +# define UV_FS_O_NOFOLLOW 0 +#endif +#if defined(O_NONBLOCK) +# define UV_FS_O_NONBLOCK O_NONBLOCK +#else +# define UV_FS_O_NONBLOCK 0 +#endif +#if defined(O_RDONLY) +# define UV_FS_O_RDONLY O_RDONLY +#else +# define UV_FS_O_RDONLY 0 +#endif +#if defined(O_RDWR) +# define UV_FS_O_RDWR O_RDWR +#else +# define UV_FS_O_RDWR 0 +#endif +#if defined(O_SYMLINK) +# define UV_FS_O_SYMLINK O_SYMLINK +#else +# define UV_FS_O_SYMLINK 0 +#endif +#if defined(O_SYNC) +# define UV_FS_O_SYNC O_SYNC +#else +# define UV_FS_O_SYNC 0 +#endif +#if defined(O_TRUNC) +# define UV_FS_O_TRUNC O_TRUNC +#else +# define UV_FS_O_TRUNC 0 +#endif +#if defined(O_WRONLY) +# define UV_FS_O_WRONLY O_WRONLY +#else +# define UV_FS_O_WRONLY 0 +#endif + +/* fs open() flags supported on other platforms: */ +#define UV_FS_O_RANDOM 0 +#define UV_FS_O_SHORT_LIVED 0 +#define UV_FS_O_SEQUENTIAL 0 +#define UV_FS_O_TEMPORARY 0 + #endif /* UV_UNIX_H */ diff --git a/deps/uv/include/uv-version.h b/deps/uv/include/uv-version.h index 55d7c91055c3ea..1c9113cdc9f7ee 100644 --- a/deps/uv/include/uv-version.h +++ b/deps/uv/include/uv-version.h @@ -31,8 +31,8 @@ */ #define UV_VERSION_MAJOR 1 -#define UV_VERSION_MINOR 15 -#define UV_VERSION_PATCH 0 +#define UV_VERSION_MINOR 16 +#define UV_VERSION_PATCH 1 #define UV_VERSION_IS_RELEASE 1 #define UV_VERSION_SUFFIX "" diff --git a/deps/uv/include/uv-win.h b/deps/uv/include/uv-win.h index 9677ff164b71e0..be150fc482f92e 100644 --- a/deps/uv/include/uv-win.h +++ b/deps/uv/include/uv-win.h @@ -222,6 +222,7 @@ typedef struct uv_buf_t { typedef int uv_file; typedef SOCKET uv_os_sock_t; typedef HANDLE uv_os_fd_t; +typedef int uv_pid_t; typedef HANDLE uv_thread_t; @@ -648,3 +649,28 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); #ifndef X_OK #define X_OK 1 #endif + +/* fs open() flags supported on this platform: */ +#define UV_FS_O_APPEND _O_APPEND +#define UV_FS_O_CREAT _O_CREAT +#define UV_FS_O_EXCL _O_EXCL +#define UV_FS_O_RANDOM _O_RANDOM +#define UV_FS_O_RDONLY _O_RDONLY +#define UV_FS_O_RDWR _O_RDWR +#define UV_FS_O_SEQUENTIAL _O_SEQUENTIAL +#define UV_FS_O_SHORT_LIVED _O_SHORT_LIVED +#define UV_FS_O_TEMPORARY _O_TEMPORARY +#define UV_FS_O_TRUNC _O_TRUNC +#define UV_FS_O_WRONLY _O_WRONLY + +/* fs open() flags supported on other platforms (or mapped on this platform): */ +#define UV_FS_O_DIRECT 0x2000000 /* FILE_FLAG_NO_BUFFERING */ +#define UV_FS_O_DIRECTORY 0 +#define UV_FS_O_DSYNC 0x4000000 /* FILE_FLAG_WRITE_THROUGH */ +#define UV_FS_O_EXLOCK 0 +#define UV_FS_O_NOATIME 0 +#define UV_FS_O_NOCTTY 0 +#define UV_FS_O_NOFOLLOW 0 +#define UV_FS_O_NONBLOCK 0 +#define UV_FS_O_SYMLINK 0 +#define UV_FS_O_SYNC 0x8000000 /* FILE_FLAG_WRITE_THROUGH */ diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index 0e4151d1389582..3f61812081da5e 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -141,6 +141,7 @@ extern "C" { XX(EMLINK, "too many links") \ XX(EHOSTDOWN, "host is down") \ XX(EREMOTEIO, "remote I/O error") \ + XX(ENOTTY, "inappropriate ioctl for device") \ #define UV_HANDLE_TYPE_MAP(XX) \ XX(ASYNC, async) \ @@ -709,6 +710,7 @@ UV_EXTERN int uv_pipe_getpeername(const uv_pipe_t* handle, UV_EXTERN void uv_pipe_pending_instances(uv_pipe_t* handle, int count); UV_EXTERN int uv_pipe_pending_count(uv_pipe_t* handle); UV_EXTERN uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle); +UV_EXTERN int uv_pipe_chmod(uv_pipe_t* handle, int flags); struct uv_poll_s { @@ -1068,6 +1070,7 @@ UV_EXTERN int uv_os_homedir(char* buffer, size_t* size); UV_EXTERN int uv_os_tmpdir(char* buffer, size_t* size); UV_EXTERN int uv_os_get_passwd(uv_passwd_t* pwd); UV_EXTERN void uv_os_free_passwd(uv_passwd_t* pwd); +UV_EXTERN uv_pid_t uv_os_getppid(void); UV_EXTERN int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count); UV_EXTERN void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count); @@ -1405,6 +1408,21 @@ UV_EXTERN int uv_ip6_name(const struct sockaddr_in6* src, char* dst, size_t size UV_EXTERN int uv_inet_ntop(int af, const void* src, char* dst, size_t size); UV_EXTERN int uv_inet_pton(int af, const char* src, void* dst); +#if defined(IF_NAMESIZE) +# define UV_IF_NAMESIZE (IF_NAMESIZE + 1) +#elif defined(IFNAMSIZ) +# define UV_IF_NAMESIZE (IFNAMSIZ + 1) +#else +# define UV_IF_NAMESIZE (16 + 1) +#endif + +UV_EXTERN int uv_if_indextoname(unsigned int ifindex, + char* buffer, + size_t* size); +UV_EXTERN int uv_if_indextoiid(unsigned int ifindex, + char* buffer, + size_t* size); + UV_EXTERN int uv_exepath(char* buffer, size_t* size); UV_EXTERN int uv_cwd(char* buffer, size_t* size); diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index ef82ee27b8568f..d64593a3134729 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -1343,3 +1343,8 @@ int uv_os_gethostname(char* buffer, size_t* size) { uv_os_fd_t uv_get_osfhandle(int fd) { return fd; } + + +uv_pid_t uv_os_getppid(void) { + return getppid(); +} diff --git a/deps/uv/src/unix/fs.c b/deps/uv/src/unix/fs.c index 2684c814a2b1d8..e0969a4c2f3d91 100644 --- a/deps/uv/src/unix/fs.c +++ b/deps/uv/src/unix/fs.c @@ -132,26 +132,33 @@ while (0) -static ssize_t uv__fs_fdatasync(uv_fs_t* req) { -#if defined(__linux__) || defined(__sun) || defined(__NetBSD__) - return fdatasync(req->file); -#elif defined(__APPLE__) +static ssize_t uv__fs_fsync(uv_fs_t* req) { +#if defined(__APPLE__) /* Apple's fdatasync and fsync explicitly do NOT flush the drive write cache * to the drive platters. This is in contrast to Linux's fdatasync and fsync * which do, according to recent man pages. F_FULLFSYNC is Apple's equivalent - * for flushing buffered data to permanent storage. + * for flushing buffered data to permanent storage. If F_FULLFSYNC is not + * supported by the file system we should fall back to fsync(). This is the + * same approach taken by sqlite. */ - return fcntl(req->file, F_FULLFSYNC); + int r; + + r = fcntl(req->file, F_FULLFSYNC); + if (r != 0 && errno == ENOTTY) + r = fsync(req->file); + return r; #else return fsync(req->file); #endif } -static ssize_t uv__fs_fsync(uv_fs_t* req) { -#if defined(__APPLE__) - /* See the comment in uv__fs_fdatasync. */ - return fcntl(req->file, F_FULLFSYNC); +static ssize_t uv__fs_fdatasync(uv_fs_t* req) { +#if defined(__linux__) || defined(__sun) || defined(__NetBSD__) + return fdatasync(req->file); +#elif defined(__APPLE__) + /* See the comment in uv__fs_fsync. */ + return uv__fs_fsync(req); #else return fsync(req->file); #endif diff --git a/deps/uv/src/unix/getaddrinfo.c b/deps/uv/src/unix/getaddrinfo.c index 2049aea2f38fd8..0185971697a95d 100644 --- a/deps/uv/src/unix/getaddrinfo.c +++ b/deps/uv/src/unix/getaddrinfo.c @@ -32,6 +32,7 @@ #include /* NULL */ #include #include +#include /* if_indextoname() */ /* EAI_* constants. */ #include @@ -200,3 +201,32 @@ void uv_freeaddrinfo(struct addrinfo* ai) { if (ai) freeaddrinfo(ai); } + + +int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) { + char ifname_buf[UV_IF_NAMESIZE]; + size_t len; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + if (if_indextoname(ifindex, ifname_buf) == NULL) + return -errno; + + len = strnlen(ifname_buf, sizeof(ifname_buf)); + + if (*size <= len) { + *size = len + 1; + return UV_ENOBUFS; + } + + memcpy(buffer, ifname_buf, len); + buffer[len] = '\0'; + *size = len; + + return 0; +} + +int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) { + return uv_if_indextoname(ifindex, buffer, size); +} diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c index c9adddbdb8722a..5e89bdced4e5f3 100644 --- a/deps/uv/src/unix/kqueue.c +++ b/deps/uv/src/unix/kqueue.c @@ -65,7 +65,6 @@ static int uv__has_forked_with_cfrunloop; int uv__io_fork(uv_loop_t* loop) { int err; - uv__close(loop->backend_fd); loop->backend_fd = -1; err = uv__kqueue_init(loop); if (err) diff --git a/deps/uv/src/unix/loop.c b/deps/uv/src/unix/loop.c index bcd49242cea2bc..5b5b0e095bbc2f 100644 --- a/deps/uv/src/unix/loop.c +++ b/deps/uv/src/unix/loop.c @@ -31,7 +31,6 @@ int uv_loop_init(uv_loop_t* loop) { void* saved_data; int err; - uv__signal_global_once_init(); saved_data = loop->data; memset(loop, 0, sizeof(*loop)); @@ -68,6 +67,7 @@ int uv_loop_init(uv_loop_t* loop) { if (err) return err; + uv__signal_global_once_init(); err = uv_signal_init(loop, &loop->child_watcher); if (err) goto fail_signal_init; diff --git a/deps/uv/src/unix/os390-syscalls.c b/deps/uv/src/unix/os390-syscalls.c index ca539c26f7b447..86c6852b4b608c 100644 --- a/deps/uv/src/unix/os390-syscalls.c +++ b/deps/uv/src/unix/os390-syscalls.c @@ -120,10 +120,41 @@ static void maybe_resize(uv__os390_epoll* lst, unsigned int len) { } +static void before_fork(void) { + uv_mutex_lock(&global_epoll_lock); +} + + +static void after_fork(void) { + uv_mutex_unlock(&global_epoll_lock); +} + + +static void child_fork(void) { + QUEUE* q; + uv_once_t child_once = UV_ONCE_INIT; + + /* reset once */ + memcpy(&once, &child_once, sizeof(child_once)); + + /* reset epoll list */ + while (!QUEUE_EMPTY(&global_epoll_queue)) { + q = QUEUE_HEAD(&global_epoll_queue); + QUEUE_REMOVE(q); + } + + uv_mutex_unlock(&global_epoll_lock); + uv_mutex_destroy(&global_epoll_lock); +} + + static void epoll_init(void) { QUEUE_INIT(&global_epoll_queue); if (uv_mutex_init(&global_epoll_lock)) abort(); + + if (pthread_atfork(&before_fork, &after_fork, &child_fork)) + abort(); } diff --git a/deps/uv/src/unix/pipe.c b/deps/uv/src/unix/pipe.c index 4a812955b0d1de..ac7cfb46a927fa 100644 --- a/deps/uv/src/unix/pipe.c +++ b/deps/uv/src/unix/pipe.c @@ -302,3 +302,56 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) { else return uv__handle_type(handle->accepted_fd); } + + +int uv_pipe_chmod(uv_pipe_t* handle, int mode) { + unsigned desired_mode; + struct stat pipe_stat; + char* name_buffer; + size_t name_len; + int r; + + if (handle == NULL || uv__stream_fd(handle) == -1) + return -EBADF; + + if (mode != UV_READABLE && + mode != UV_WRITABLE && + mode != (UV_WRITABLE | UV_READABLE)) + return -EINVAL; + + if (fstat(uv__stream_fd(handle), &pipe_stat) == -1) + return -errno; + + desired_mode = 0; + if (mode & UV_READABLE) + desired_mode |= S_IRUSR | S_IRGRP | S_IROTH; + if (mode & UV_WRITABLE) + desired_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + /* Exit early if pipe already has desired mode. */ + if ((pipe_stat.st_mode & desired_mode) == desired_mode) + return 0; + + pipe_stat.st_mode |= desired_mode; + + /* Unfortunately fchmod does not work on all platforms, we will use chmod. */ + name_len = 0; + r = uv_pipe_getsockname(handle, NULL, &name_len); + if (r != UV_ENOBUFS) + return r; + + name_buffer = uv__malloc(name_len); + if (name_buffer == NULL) + return UV_ENOMEM; + + r = uv_pipe_getsockname(handle, name_buffer, &name_len); + if (r != 0) { + uv__free(name_buffer); + return r; + } + + r = chmod(name_buffer, pipe_stat.st_mode); + uv__free(name_buffer); + + return r != -1 ? 0 : -errno; +} diff --git a/deps/uv/src/win/dl.c b/deps/uv/src/win/dl.c index d454014d8adf6e..97ac1c1ad10012 100644 --- a/deps/uv/src/win/dl.c +++ b/deps/uv/src/win/dl.c @@ -89,9 +89,9 @@ static void uv__format_fallback_error(uv_lib_t* lib, int errorno){ static int uv__dlerror(uv_lib_t* lib, const char* filename, DWORD errorno) { - static const char not_win32_app_msg[] = "%1 is not a valid Win32 application"; DWORD_PTR arg; DWORD res; + char* msg; if (lib->errmsg) { LocalFree(lib->errmsg); @@ -114,16 +114,16 @@ static int uv__dlerror(uv_lib_t* lib, const char* filename, DWORD errorno) { 0, (LPSTR) &lib->errmsg, 0, NULL); } - /* Inexpert hack to get the filename into the error message. */ - if (res && strstr(lib->errmsg, not_win32_app_msg)) { - LocalFree(lib->errmsg); + if (res && errorno == ERROR_BAD_EXE_FORMAT && strstr(lib->errmsg, "%1")) { + msg = lib->errmsg; lib->errmsg = NULL; arg = (DWORD_PTR) filename; res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_STRING, - not_win32_app_msg, + msg, 0, 0, (LPSTR) &lib->errmsg, 0, (va_list*) &arg); + LocalFree(msg); } if (!res) diff --git a/deps/uv/src/win/fs.c b/deps/uv/src/win/fs.c index c374a82ca01f75..b90eaa7548969a 100644 --- a/deps/uv/src/win/fs.c +++ b/deps/uv/src/win/fs.c @@ -415,21 +415,21 @@ void fs__open(uv_fs_t* req) { umask(current_umask); /* convert flags and mode to CreateFile parameters */ - switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) { - case _O_RDONLY: + switch (flags & (UV_FS_O_RDONLY | UV_FS_O_WRONLY | UV_FS_O_RDWR)) { + case UV_FS_O_RDONLY: access = FILE_GENERIC_READ; break; - case _O_WRONLY: + case UV_FS_O_WRONLY: access = FILE_GENERIC_WRITE; break; - case _O_RDWR: + case UV_FS_O_RDWR: access = FILE_GENERIC_READ | FILE_GENERIC_WRITE; break; default: goto einval; } - if (flags & _O_APPEND) { + if (flags & UV_FS_O_APPEND) { access &= ~FILE_WRITE_DATA; access |= FILE_APPEND_DATA; } @@ -442,23 +442,23 @@ void fs__open(uv_fs_t* req) { */ share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; - switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) { + switch (flags & (UV_FS_O_CREAT | UV_FS_O_EXCL | UV_FS_O_TRUNC)) { case 0: - case _O_EXCL: + case UV_FS_O_EXCL: disposition = OPEN_EXISTING; break; - case _O_CREAT: + case UV_FS_O_CREAT: disposition = OPEN_ALWAYS; break; - case _O_CREAT | _O_EXCL: - case _O_CREAT | _O_TRUNC | _O_EXCL: + case UV_FS_O_CREAT | UV_FS_O_EXCL: + case UV_FS_O_CREAT | UV_FS_O_TRUNC | UV_FS_O_EXCL: disposition = CREATE_NEW; break; - case _O_TRUNC: - case _O_TRUNC | _O_EXCL: + case UV_FS_O_TRUNC: + case UV_FS_O_TRUNC | UV_FS_O_EXCL: disposition = TRUNCATE_EXISTING; break; - case _O_CREAT | _O_TRUNC: + case UV_FS_O_CREAT | UV_FS_O_TRUNC: disposition = CREATE_ALWAYS; break; default: @@ -466,34 +466,49 @@ void fs__open(uv_fs_t* req) { } attributes |= FILE_ATTRIBUTE_NORMAL; - if (flags & _O_CREAT) { + if (flags & UV_FS_O_CREAT) { if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) { attributes |= FILE_ATTRIBUTE_READONLY; } } - if (flags & _O_TEMPORARY ) { + if (flags & UV_FS_O_TEMPORARY ) { attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY; access |= DELETE; } - if (flags & _O_SHORT_LIVED) { + if (flags & UV_FS_O_SHORT_LIVED) { attributes |= FILE_ATTRIBUTE_TEMPORARY; } - switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) { + switch (flags & (UV_FS_O_SEQUENTIAL | UV_FS_O_RANDOM)) { case 0: break; - case _O_SEQUENTIAL: + case UV_FS_O_SEQUENTIAL: attributes |= FILE_FLAG_SEQUENTIAL_SCAN; break; - case _O_RANDOM: + case UV_FS_O_RANDOM: attributes |= FILE_FLAG_RANDOM_ACCESS; break; default: goto einval; } + if (flags & UV_FS_O_DIRECT) { + attributes |= FILE_FLAG_NO_BUFFERING; + } + + switch (flags & (UV_FS_O_DSYNC | UV_FS_O_SYNC)) { + case 0: + break; + case UV_FS_O_DSYNC: + case UV_FS_O_SYNC: + attributes |= FILE_FLAG_WRITE_THROUGH; + break; + default: + goto einval; + } + /* Setting this flag makes it possible to open a directory. */ attributes |= FILE_FLAG_BACKUP_SEMANTICS; @@ -506,9 +521,9 @@ void fs__open(uv_fs_t* req) { NULL); if (file == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); - if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) && - !(flags & _O_EXCL)) { - /* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */ + if (error == ERROR_FILE_EXISTS && (flags & UV_FS_O_CREAT) && + !(flags & UV_FS_O_EXCL)) { + /* Special case: when ERROR_FILE_EXISTS happens and UV_FS_O_CREAT was */ /* specified, it means the path referred to a directory. */ SET_REQ_UV_ERROR(req, UV_EISDIR, error); } else { @@ -1070,7 +1085,8 @@ void fs__scandir(uv_fs_t* req) { } -INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) { +INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf, + int do_lstat) { FILE_ALL_INFORMATION file_info; FILE_FS_VOLUME_INFORMATION volume_info; NTSTATUS nt_status; @@ -1125,17 +1141,25 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) { */ statbuf->st_mode = 0; - if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * On Windows, FILE_ATTRIBUTE_REPARSE_POINT is a general purpose mechanism + * by which filesystem drivers can intercept and alter file system requests. + * + * The only reparse points we care about are symlinks and mount points, both + * of which are treated as POSIX symlinks. Further, we only care when + * invoked via lstat, which seeks information about the link instead of its + * target. Otherwise, reparse points must be treated as regular files. + */ + if (do_lstat && + (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { /* - * It is possible for a file to have FILE_ATTRIBUTE_REPARSE_POINT but not have - * any link data. In that case DeviceIoControl() in fs__readlink_handle() sets - * the last error to ERROR_NOT_A_REPARSE_POINT. Then the stat result mode - * calculated below will indicate a normal directory or file, as if - * FILE_ATTRIBUTE_REPARSE_POINT was not present. + * If reading the link fails, the reparse point is not a symlink and needs + * to be treated as a regular file. The higher level lstat function will + * detect this failure and retry without do_lstat if appropriate. */ - if (fs__readlink_handle(handle, NULL, &statbuf->st_size) == 0) { - statbuf->st_mode |= S_IFLNK; - } + if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) + return -1; + statbuf->st_mode |= S_IFLNK; } if (statbuf->st_mode == 0) { @@ -1178,8 +1202,12 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf) { * * Therefore we'll just report a sensible value that's quite commonly okay * on modern hardware. + * + * 4096 is the minimum required to be compatible with newer Advanced Format + * drives (which have 4096 bytes per physical sector), and to be backwards + * compatible with older drives (which have 512 bytes per physical sector). */ - statbuf->st_blksize = 2048; + statbuf->st_blksize = 4096; /* Todo: set st_flags to something meaningful. Also provide a wrapper for * chattr(2). @@ -1230,9 +1258,11 @@ INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) { return; } - if (fs__stat_handle(handle, &req->statbuf) != 0) { + if (fs__stat_handle(handle, &req->statbuf, do_lstat) != 0) { DWORD error = GetLastError(); - if (do_lstat && error == ERROR_SYMLINK_NOT_SUPPORTED) { + if (do_lstat && + (error == ERROR_SYMLINK_NOT_SUPPORTED || + error == ERROR_NOT_A_REPARSE_POINT)) { /* We opened a reparse point but it was not a symlink. Try again. */ fs__stat_impl(req, 0); @@ -1276,7 +1306,7 @@ static void fs__fstat(uv_fs_t* req) { return; } - if (fs__stat_handle(handle, &req->statbuf) != 0) { + if (fs__stat_handle(handle, &req->statbuf, 0) != 0) { SET_REQ_WIN32_ERROR(req, GetLastError()); return; } diff --git a/deps/uv/src/win/getaddrinfo.c b/deps/uv/src/win/getaddrinfo.c index baab838898a62e..282d919cf75513 100644 --- a/deps/uv/src/win/getaddrinfo.c +++ b/deps/uv/src/win/getaddrinfo.c @@ -28,6 +28,8 @@ /* EAI_* constants. */ #include +/* Needed for ConvertInterfaceIndexToLuid and ConvertInterfaceLuidToNameA */ +#include int uv__getaddrinfo_translate_error(int sys_err) { switch (sys_err) { @@ -73,6 +75,9 @@ int uv__getaddrinfo_translate_error(int sys_err) { /* Do we need different versions of this for different architectures? */ #define ALIGNED_SIZE(X) ((((X) + 3) >> 2) << 2) +#ifndef NDIS_IF_MAX_STRING_SIZE +#define NDIS_IF_MAX_STRING_SIZE IF_MAX_STRING_SIZE +#endif static void uv__getaddrinfo_work(struct uv__work* w) { uv_getaddrinfo_t* req; @@ -380,3 +385,69 @@ int uv_getaddrinfo(uv_loop_t* loop, } return uv_translate_sys_error(err); } + +int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) { + NET_LUID luid; + wchar_t wname[NDIS_IF_MAX_STRING_SIZE + 1]; /* Add one for the NUL. */ + DWORD bufsize; + int r; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + r = ConvertInterfaceIndexToLuid(ifindex, &luid); + + if (r != 0) + return uv_translate_sys_error(r); + + r = ConvertInterfaceLuidToNameW(&luid, wname, ARRAY_SIZE(wname)); + + if (r != 0) + return uv_translate_sys_error(r); + + /* Check how much space we need */ + bufsize = WideCharToMultiByte(CP_UTF8, 0, wname, -1, NULL, 0, NULL, NULL); + + if (bufsize == 0) { + return uv_translate_sys_error(GetLastError()); + } else if (bufsize > *size) { + *size = bufsize; + return UV_ENOBUFS; + } + + /* Convert to UTF-8 */ + bufsize = WideCharToMultiByte(CP_UTF8, + 0, + wname, + -1, + buffer, + *size, + NULL, + NULL); + + if (bufsize == 0) + return uv_translate_sys_error(GetLastError()); + + *size = bufsize - 1; + return 0; +} + +int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) { + int r; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + r = snprintf(buffer, *size, "%d", ifindex); + + if (r < 0) + return uv_translate_sys_error(r); + + if (r >= (int) *size) { + *size = r + 1; + return UV_ENOBUFS; + } + + *size = r; + return 0; +} diff --git a/deps/uv/src/win/internal.h b/deps/uv/src/win/internal.h index 444327d647f284..217fcdb5d7678e 100644 --- a/deps/uv/src/win/internal.h +++ b/deps/uv/src/win/internal.h @@ -326,7 +326,6 @@ void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle); void uv__util_init(void); uint64_t uv__hrtime(double scale); -int uv_parent_pid(void); int uv_current_pid(void); __declspec(noreturn) void uv_fatal_error(const int errorno, const char* syscall); int uv__getpwuid_r(uv_passwd_t* pwd); diff --git a/deps/uv/src/win/pipe.c b/deps/uv/src/win/pipe.c index 5c666788fd67f5..642213bc8886c2 100644 --- a/deps/uv/src/win/pipe.c +++ b/deps/uv/src/win/pipe.c @@ -31,6 +31,9 @@ #include "stream-inl.h" #include "req-inl.h" +#include +#include + typedef struct uv__ipc_queue_item_s uv__ipc_queue_item_t; struct uv__ipc_queue_item_s { @@ -202,7 +205,7 @@ int uv_stdio_pipe_server(uv_loop_t* loop, uv_pipe_t* handle, DWORD access, uv_unique_pipe_name(ptr, name, nameSize); pipeHandle = CreateNamedPipeA(name, - access | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + access | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 65536, 65536, 0, NULL); @@ -534,7 +537,7 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { */ handle->pipe.serv.accept_reqs[0].pipeHandle = CreateNamedPipeW(handle->name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | - FILE_FLAG_FIRST_PIPE_INSTANCE, + FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); @@ -803,7 +806,7 @@ static void uv_pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, assert(req->pipeHandle == INVALID_HANDLE_VALUE); req->pipeHandle = CreateNamedPipeW(handle->name, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); @@ -1969,7 +1972,7 @@ int uv_pipe_open(uv_pipe_t* pipe, uv_file file) { if (pipe->ipc) { assert(!(pipe->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)); - pipe->pipe.conn.ipc_pid = uv_parent_pid(); + pipe->pipe.conn.ipc_pid = uv_os_getppid(); assert(pipe->pipe.conn.ipc_pid != -1); } return 0; @@ -2132,3 +2135,80 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) { else return UV_TCP; } + +int uv_pipe_chmod(uv_pipe_t* handle, int mode) { + SID_IDENTIFIER_AUTHORITY sid_world = SECURITY_WORLD_SID_AUTHORITY; + PACL old_dacl, new_dacl; + PSECURITY_DESCRIPTOR sd; + EXPLICIT_ACCESS ea; + PSID everyone; + int error; + + if (handle == NULL || handle->handle == INVALID_HANDLE_VALUE) + return UV_EBADF; + + if (mode != UV_READABLE && + mode != UV_WRITABLE && + mode != (UV_WRITABLE | UV_READABLE)) + return UV_EINVAL; + + if (!AllocateAndInitializeSid(&sid_world, + 1, + SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, + &everyone)) { + error = GetLastError(); + goto done; + } + + if (GetSecurityInfo(handle->handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &old_dacl, + NULL, + &sd)) { + error = GetLastError(); + goto clean_sid; + } + + memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); + if (mode & UV_READABLE) + ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; + if (mode & UV_WRITABLE) + ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES; + ea.grfAccessPermissions |= SYNCHRONIZE; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName = (LPTSTR)everyone; + + if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl)) { + error = GetLastError(); + goto clean_sd; + } + + if (SetSecurityInfo(handle->handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + new_dacl, + NULL)) { + error = GetLastError(); + goto clean_dacl; + } + + error = 0; + +clean_dacl: + LocalFree((HLOCAL) new_dacl); +clean_sd: + LocalFree((HLOCAL) sd); +clean_sid: + FreeSid(everyone); +done: + return uv_translate_sys_error(error); +} diff --git a/deps/uv/src/win/stream-inl.h b/deps/uv/src/win/stream-inl.h index bf12148afe1605..dba03747043be7 100644 --- a/deps/uv/src/win/stream-inl.h +++ b/deps/uv/src/win/stream-inl.h @@ -36,6 +36,7 @@ INLINE static void uv_stream_init(uv_loop_t* loop, uv__handle_init(loop, (uv_handle_t*) handle, type); handle->write_queue_size = 0; handle->activecnt = 0; + handle->stream.conn.shutdown_req = NULL; } @@ -47,8 +48,6 @@ INLINE static void uv_connection_init(uv_stream_t* handle) { handle->read_req.event_handle = NULL; handle->read_req.wait_handle = INVALID_HANDLE_VALUE; handle->read_req.data = handle; - - handle->stream.conn.shutdown_req = NULL; } diff --git a/deps/uv/src/win/util.c b/deps/uv/src/win/util.c index a2acda1152f663..2aec9f8dfe3e2b 100644 --- a/deps/uv/src/win/util.c +++ b/deps/uv/src/win/util.c @@ -331,7 +331,7 @@ uint64_t uv_get_total_memory(void) { } -int uv_parent_pid(void) { +uv_pid_t uv_os_getppid(void) { int parent_pid = -1; HANDLE handle; PROCESSENTRY32 pe; diff --git a/deps/uv/test/benchmark-pump.c b/deps/uv/test/benchmark-pump.c index 88f2dc5c658e27..8685258e052793 100644 --- a/deps/uv/test/benchmark-pump.c +++ b/deps/uv/test/benchmark-pump.c @@ -36,9 +36,9 @@ static int TARGET_CONNECTIONS; static void do_write(uv_stream_t*); -static void maybe_connect_some(); +static void maybe_connect_some(void); -static uv_req_t* req_alloc(); +static uv_req_t* req_alloc(void); static void req_free(uv_req_t* uv_req); static void buf_alloc(uv_handle_t* handle, size_t size, uv_buf_t* buf); diff --git a/deps/uv/test/runner.c b/deps/uv/test/runner.c index 5e44118775edbc..f017902a04f7c8 100644 --- a/deps/uv/test/runner.c +++ b/deps/uv/test/runner.c @@ -81,6 +81,7 @@ int run_tests(int benchmark_output) { int skipped; int current; int test_result; + int skip; task_entry_t* task; /* Count the number of tests. */ @@ -92,7 +93,9 @@ int run_tests(int benchmark_output) { } } - qsort(TASKS, actual, sizeof(TASKS[0]), compare_task); + /* Keep platform_output first. */ + skip = (actual > 0 && 0 == strcmp(TASKS[0].task_name, "platform_output")); + qsort(TASKS + skip, actual - skip, sizeof(TASKS[0]), compare_task); fprintf(stderr, "1..%d\n", total); fflush(stderr); @@ -127,6 +130,7 @@ void log_tap_result(int test_count, const char* result; const char* directive; char reason[1024]; + int reason_length; switch (status) { case TEST_OK: @@ -144,6 +148,9 @@ void log_tap_result(int test_count, if (status == TEST_SKIP && process_output_size(process) > 0) { process_read_last_line(process, reason, sizeof reason); + reason_length = strlen(reason); + if (reason_length > 0 && reason[reason_length - 1] == '\n') + reason[reason_length - 1] = '\0'; } else { reason[0] = '\0'; } diff --git a/deps/uv/test/test-fork.c b/deps/uv/test/test-fork.c index 3716a3ad975c17..ba85b531064ae5 100644 --- a/deps/uv/test/test-fork.c +++ b/deps/uv/test/test-fork.c @@ -636,6 +636,7 @@ static void assert_run_work(uv_loop_t* const loop) { } +#ifndef __MVS__ TEST_IMPL(fork_threadpool_queue_work_simple) { /* The threadpool works in a child process. */ @@ -672,6 +673,7 @@ TEST_IMPL(fork_threadpool_queue_work_simple) { MAKE_VALGRIND_HAPPY(); return 0; } +#endif /* !__MVS__ */ #endif /* !_WIN32 */ diff --git a/deps/uv/test/test-fs.c b/deps/uv/test/test-fs.c index 0000e563a73ffc..6afa650793e670 100644 --- a/deps/uv/test/test-fs.c +++ b/deps/uv/test/test-fs.c @@ -115,6 +115,17 @@ static char test_buf[] = "test-buffer\n"; static char test_buf2[] = "second-buffer\n"; static uv_buf_t iov; +#ifdef _WIN32 +/* + * This tag and guid have no special meaning, and don't conflict with + * reserved ids. +*/ +static unsigned REPARSE_TAG = 0x9913; +static GUID REPARSE_GUID = { + 0x1bf6205f, 0x46ae, 0x4527, + 0xb1, 0x0c, 0xc5, 0x09, 0xb7, 0x55, 0x22, 0x80 }; +#endif + static void check_permission(const char* filename, unsigned int mode) { int r; uv_fs_t req; @@ -1991,6 +2002,109 @@ TEST_IMPL(fs_symlink_dir) { } +#ifdef _WIN32 +TEST_IMPL(fs_non_symlink_reparse_point) { + uv_fs_t req; + int r; + HANDLE file_handle; + REPARSE_GUID_DATA_BUFFER reparse_buffer; + DWORD bytes_returned; + uv_dirent_t dent; + + /* set-up */ + unlink("test_dir/test_file"); + rmdir("test_dir"); + + loop = uv_default_loop(); + + uv_fs_mkdir(NULL, &req, "test_dir", 0777, NULL); + uv_fs_req_cleanup(&req); + + file_handle = CreateFile("test_dir/test_file", + GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, + 0, + NULL, + CREATE_ALWAYS, + FILE_FLAG_OPEN_REPARSE_POINT | + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + ASSERT(file_handle != INVALID_HANDLE_VALUE); + + memset(&reparse_buffer, 0, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE); + reparse_buffer.ReparseTag = REPARSE_TAG; + reparse_buffer.ReparseDataLength = 0; + reparse_buffer.ReparseGuid = REPARSE_GUID; + + r = DeviceIoControl(file_handle, + FSCTL_SET_REPARSE_POINT, + &reparse_buffer, + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + NULL, + 0, + &bytes_returned, + NULL); + ASSERT(r != 0); + + CloseHandle(file_handle); + + r = uv_fs_readlink(NULL, &req, "test_dir/test_file", NULL); + ASSERT(r == UV_EINVAL && GetLastError() == ERROR_SYMLINK_NOT_SUPPORTED); + uv_fs_req_cleanup(&req); + +/* + Placeholder tests for exercising the behavior fixed in issue #995. + To run, update the path with the IP address of a Mac with the hard drive + shared via SMB as "Macintosh HD". + + r = uv_fs_stat(NULL, &req, "\\\\\\Macintosh HD\\.DS_Store", NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); + + r = uv_fs_lstat(NULL, &req, "\\\\\\Macintosh HD\\.DS_Store", NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); +*/ + +/* + uv_fs_stat and uv_fs_lstat can only work on non-symlink reparse + points when a minifilter driver is registered which intercepts + associated filesystem requests. Installing a driver is beyond + the scope of this test. + + r = uv_fs_stat(NULL, &req, "test_dir/test_file", NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); + + r = uv_fs_lstat(NULL, &req, "test_dir/test_file", NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); +*/ + + r = uv_fs_scandir(NULL, &scandir_req, "test_dir", 0, NULL); + ASSERT(r == 1); + ASSERT(scandir_req.result == 1); + ASSERT(scandir_req.ptr); + while (UV_EOF != uv_fs_scandir_next(&scandir_req, &dent)) { + ASSERT(strcmp(dent.name, "test_file") == 0); + /* uv_fs_scandir incorrectly identifies non-symlink reparse points + as links because it doesn't open the file and verify the reparse + point tag. The PowerShell Get-ChildItem command shares this + behavior, so it's reasonable to leave it as is. */ + ASSERT(dent.type == UV_DIRENT_LINK); + } + uv_fs_req_cleanup(&scandir_req); + ASSERT(!scandir_req.ptr); + + /* clean-up */ + unlink("test_dir/test_file"); + rmdir("test_dir"); + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif + + TEST_IMPL(fs_utime) { utime_check_t checkme; const char* path = "test_file"; diff --git a/deps/uv/test/test-ip6-addr.c b/deps/uv/test/test-ip6-addr.c index 156fccde39deb6..25570dcacde1e3 100644 --- a/deps/uv/test/test-ip6-addr.c +++ b/deps/uv/test/test-ip6-addr.c @@ -44,8 +44,12 @@ TEST_IMPL(ip6_addr_link_local) { const char* device_name; /* 40 bytes address, 16 bytes device name, plus reserve. */ char scoped_addr[128]; + size_t scoped_addr_len; + char interface_id[UV_IF_NAMESIZE]; + size_t interface_id_len; int count; int ix; + int r; ASSERT(0 == uv_interface_addresses(&addresses, &count)); @@ -67,19 +71,29 @@ TEST_IMPL(ip6_addr_link_local) { iface_index = address->address.address6.sin6_scope_id; device_name = address->name; + scoped_addr_len = sizeof(scoped_addr); + ASSERT(0 == uv_if_indextoname(iface_index, scoped_addr, &scoped_addr_len)); +#ifndef _WIN32 + /* This assert fails on Windows, as Windows semantics are different. */ + ASSERT(0 == strcmp(device_name, scoped_addr)); +#endif + + interface_id_len = sizeof(interface_id); + r = uv_if_indextoiid(iface_index, interface_id, &interface_id_len); + ASSERT(0 == r); #ifdef _WIN32 - snprintf(scoped_addr, - sizeof(scoped_addr), - "%s%%%d", - string_address, - iface_index); + /* On Windows, the interface identifier is the numeric string of the index. */ + ASSERT(strtol(interface_id, NULL, 10) == iface_index); #else + /* On Unix/Linux, the interface identifier is the interface device name. */ + ASSERT(0 == strcmp(device_name, interface_id)); +#endif + snprintf(scoped_addr, sizeof(scoped_addr), "%s%%%s", string_address, - device_name); -#endif + interface_id); fprintf(stderr, "Testing link-local address %s " "(iface_index: 0x%02x, device_name: %s)\n", @@ -96,6 +110,9 @@ TEST_IMPL(ip6_addr_link_local) { uv_free_interface_addresses(addresses, count); + scoped_addr_len = sizeof(scoped_addr); + ASSERT(0 != uv_if_indextoname((unsigned int)-1, scoped_addr, &scoped_addr_len)); + MAKE_VALGRIND_HAPPY(); return 0; } diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index 0dde57c2ed22ab..3e88e6c928d096 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -204,6 +204,7 @@ TEST_DECLARE (pipe_ref4) TEST_DECLARE (pipe_close_stdout_read_stdin) #endif TEST_DECLARE (pipe_set_non_blocking) +TEST_DECLARE (pipe_set_chmod) TEST_DECLARE (process_ref) TEST_DECLARE (has_ref) TEST_DECLARE (active) @@ -282,6 +283,9 @@ TEST_DECLARE (fs_readlink) TEST_DECLARE (fs_realpath) TEST_DECLARE (fs_symlink) TEST_DECLARE (fs_symlink_dir) +#ifdef _WIN32 +TEST_DECLARE (fs_non_symlink_reparse_point) +#endif TEST_DECLARE (fs_utime) TEST_DECLARE (fs_futime) TEST_DECLARE (fs_file_open_append) @@ -398,8 +402,10 @@ TEST_DECLARE (fork_signal_to_child_closed) TEST_DECLARE (fork_fs_events_child) TEST_DECLARE (fork_fs_events_child_dir) TEST_DECLARE (fork_fs_events_file_parent_child) +#ifndef __MVS__ TEST_DECLARE (fork_threadpool_queue_work_simple) #endif +#endif TASK_LIST_START TEST_ENTRY_CUSTOM (platform_output, 0, 1, 5000) @@ -438,6 +444,7 @@ TASK_LIST_START TEST_ENTRY (pipe_close_stdout_read_stdin) #endif TEST_ENTRY (pipe_set_non_blocking) + TEST_ENTRY (pipe_set_chmod) TEST_ENTRY (tty) #ifdef _WIN32 TEST_ENTRY (tty_raw) @@ -790,6 +797,9 @@ TASK_LIST_START TEST_ENTRY (fs_realpath) TEST_ENTRY (fs_symlink) TEST_ENTRY (fs_symlink_dir) +#ifdef _WIN32 + TEST_ENTRY (fs_non_symlink_reparse_point) +#endif TEST_ENTRY (fs_stat_missing_path) TEST_ENTRY (fs_read_file_eof) TEST_ENTRY (fs_file_open_append) @@ -861,8 +871,10 @@ TASK_LIST_START TEST_ENTRY (fork_fs_events_child) TEST_ENTRY (fork_fs_events_child_dir) TEST_ENTRY (fork_fs_events_file_parent_child) +#ifndef __MVS__ TEST_ENTRY (fork_threadpool_queue_work_simple) #endif +#endif #if 0 /* These are for testing the test runner. */ diff --git a/deps/uv/test/test-pipe-set-fchmod.c b/deps/uv/test/test-pipe-set-fchmod.c new file mode 100644 index 00000000000000..59f0e6f5454f89 --- /dev/null +++ b/deps/uv/test/test-pipe-set-fchmod.c @@ -0,0 +1,66 @@ +/* Copyright libuv project contributors. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + + +#include "uv.h" +#include "task.h" + +TEST_IMPL(pipe_set_chmod) { + uv_pipe_t pipe_handle; + uv_loop_t* loop; + int r; + + loop = uv_default_loop(); + + r = uv_pipe_init(loop, &pipe_handle, 0); + ASSERT(r == 0); + + r = uv_pipe_bind(&pipe_handle, TEST_PIPENAME); + ASSERT(r == 0); + + /* No easy way to test if this works, we will only make sure that */ + /* the call is successful. */ + r = uv_pipe_chmod(&pipe_handle, UV_READABLE); + if (r == UV_EPERM) { + MAKE_VALGRIND_HAPPY(); + RETURN_SKIP("Insufficient privileges to alter pipe fmode"); + } + ASSERT(r == 0); + + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE); + ASSERT(r == 0); + + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE | UV_READABLE); + ASSERT(r == 0); + + r = uv_pipe_chmod(NULL, UV_WRITABLE | UV_READABLE); + ASSERT(r == UV_EBADF); + + r = uv_pipe_chmod(&pipe_handle, 12345678); + ASSERT(r == UV_EINVAL); + + uv_close((uv_handle_t*)&pipe_handle, NULL); + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE | UV_READABLE); + ASSERT(r == UV_EBADF); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/deps/uv/test/test-platform-output.c b/deps/uv/test/test-platform-output.c index 72c176edc459fb..50ed59a6d23a02 100644 --- a/deps/uv/test/test-platform-output.c +++ b/deps/uv/test/test-platform-output.c @@ -29,6 +29,7 @@ TEST_IMPL(platform_output) { size_t rss; size_t size; double uptime; + uv_pid_t ppid; uv_rusage_t rusage; uv_cpu_info_t* cpus; uv_interface_address_t* interfaces; @@ -144,5 +145,9 @@ TEST_IMPL(platform_output) { printf(" shell: %s\n", pwd.shell); printf(" home directory: %s\n", pwd.homedir); + ppid = uv_os_getppid(); + ASSERT(ppid > 0); + printf("uv_os_getppid: %d\n", (int) ppid); + return 0; } diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c index 91d831e19b92fe..d3958fe216f983 100644 --- a/deps/uv/test/test-spawn.c +++ b/deps/uv/test/test-spawn.c @@ -1721,9 +1721,9 @@ TEST_IMPL(spawn_quoted_path) { RETURN_SKIP("Test for Windows"); #else char* quoted_path_env[2]; - options.file = "not_existing"; - args[0] = options.file; + args[0] = "not_existing"; args[1] = NULL; + options.file = args[0]; options.args = args; options.exit_cb = exit_cb; options.flags = 0; diff --git a/deps/uv/uv.gyp b/deps/uv/uv.gyp index 38765eefd129b2..9d9bb4b735a310 100644 --- a/deps/uv/uv.gyp +++ b/deps/uv/uv.gyp @@ -393,6 +393,7 @@ 'test/test-pipe-server-close.c', 'test/test-pipe-close-stdout-read-stdin.c', 'test/test-pipe-set-non-blocking.c', + 'test/test-pipe-set-fchmod.c', 'test/test-platform-output.c', 'test/test-poll.c', 'test/test-poll-close.c', diff --git a/doc/api/buffer.md b/doc/api/buffer.md index d8948cae9cfc8a..835295763ea876 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -906,13 +906,11 @@ added: v0.11.13 * `targetStart` {integer} The offset within `target` at which to begin comparison. **Default:** `0` * `targetEnd` {integer} The offset with `target` at which to end comparison - (not inclusive). Ignored when `targetStart` is `undefined`. - **Default:** `target.length` + (not inclusive). **Default:** `target.length` * `sourceStart` {integer} The offset within `buf` at which to begin comparison. - Ignored when `targetStart` is `undefined`. **Default:** `0` + **Default:** `0` * `sourceEnd` {integer} The offset within `buf` at which to end comparison - (not inclusive). Ignored when `targetStart` is `undefined`. - **Default:** [`buf.length`] + (not inclusive). **Default:** [`buf.length`] * Returns: {integer} Compares `buf` with `target` and returns a number indicating whether `buf` @@ -982,9 +980,9 @@ added: v0.1.90 * `targetStart` {integer} The offset within `target` at which to begin copying to. **Default:** `0` * `sourceStart` {integer} The offset within `buf` at which to begin copying from. - Ignored when `targetStart` is `undefined`. **Default:** `0` + **Default:** `0` * `sourceEnd` {integer} The offset within `buf` at which to stop copying (not - inclusive). Ignored when `sourceStart` is `undefined`. **Default:** [`buf.length`] + inclusive). **Default:** [`buf.length`] * Returns: {integer} The number of bytes copied. Copies data from a region of `buf` to a region in `target` even if the `target` diff --git a/doc/api/child_process.md b/doc/api/child_process.md index a8baf56c69b2ba..06fad013ef508f 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -844,7 +844,7 @@ added: v0.1.90 * `signal` {string} -The `subprocess.kill()` methods sends a signal to the child process. If no +The `subprocess.kill()` method sends a signal to the child process. If no argument is given, the process will be sent the `'SIGTERM'` signal. See signal(7) for a list of available signals. diff --git a/doc/api/console.md b/doc/api/console.md index cc7e8b0022c25b..4d17f34570cebd 100644 --- a/doc/api/console.md +++ b/doc/api/console.md @@ -155,6 +155,76 @@ console.assert(false, 'this message will print, but no error thrown'); console.log('this will also print'); ``` +### console.clear() + + +When `stdout` is a TTY, calling `console.clear()` will attempt to clear the +TTY. When `stdout` is not a TTY, this method does nothing. + +*Note*: The specific operation of `console.clear()` can vary across operating +systems and terminal types. For most Linux operating systems, `console.clear()` +operates similarly to the `clear` shell command. On Windows, `console.clear()` +will clear only the output in the current terminal viewport for the Node.js +binary. + +### console.count([label]) + + +* `label` {string} The display label for the counter. Defaults to `'default'`. + +Maintains an internal counter specific to `label` and outputs to `stdout` the +number of times `console.count()` has been called with the given `label`. + + +```js +> console.count() +default: 1 +undefined +> console.count('default') +default: 2 +undefined +> console.count('abc') +abc: 1 +undefined +> console.count('xyz') +xyz: 1 +undefined +> console.count('abc') +abc: 2 +undefined +> console.count() +default: 3 +undefined +> +``` + +### console.countReset([label = 'default']) + + +* `label` {string} The display label for the counter. Defaults to `'default'`. + +Resets the internal counter specific to `label`. + + +```js +> console.count('abc'); +abc: 1 +undefined +> console.countReset('abc'); +undefined +> console.count('abc'); +abc: 1 +undefined +> +``` + + ### console.dir(obj[, options]) + +* `buffer` {Buffer|Uint8Array} Must be supplied. +* `offset` {number} Defaults to `0`. +* `size` {number} Defaults to `buffer.length - offset`. + +Synchronous version of [`crypto.randomFill()`][]. + +Returns `buffer` + +```js +const buf = Buffer.alloc(10); +console.log(crypto.randomFillSync(buf).toString('hex')); + +crypto.randomFillSync(buf, 5); +console.log(buf.toString('hex')); + +// The above is equivalent to the following: +crypto.randomFillSync(buf, 5, 5); +console.log(buf.toString('hex')); +``` + +### crypto.randomFill(buffer[, offset][, size], callback) + + +* `buffer` {Buffer|Uint8Array} Must be supplied. +* `offset` {number} Defaults to `0`. +* `size` {number} Defaults to `buffer.length - offset`. +* `callback` {Function} `function(err, buf) {}`. + +This function is similar to [`crypto.randomBytes()`][] but requires the first +argument to be a [`Buffer`][] that will be filled. It also +requires that a callback is passed in. + +If the `callback` function is not provided, an error will be thrown. + +```js +const buf = Buffer.alloc(10); +crypto.randomFill(buf, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); + +crypto.randomFill(buf, 5, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); + +// The above is equivalent to the following: +crypto.randomFill(buf, 5, 5, (err, buf) => { + if (err) throw err; + console.log(buf.toString('hex')); +}); +``` + ### crypto.setEngine(engine[, flags]) + +* `multicastInterface` {String} + +*Note: All references to scope in this section are refering to +[IPv6 Zone Indices][], which are defined by [RFC 4007][]. In string form, an IP +with a scope index is written as `'IP%scope'` where scope is an interface name or +interface number.* + +Sets the default outgoing multicast interface of the socket to a chosen +interface or back to system interface selection. The `multicastInterface` must +be a valid string representation of an IP from the socket's family. + +For IPv4 sockets, this should be the IP configured for the desired physical +interface. All packets sent to multicast on the socket will be sent on the +interface determined by the most recent successful use of this call. + +For IPv6 sockets, `multicastInterface` should include a scope to indicate the +interface as in the examples that follow. In IPv6, individual `send` calls can +also use explicit scope in addresses, so only packets sent to a multicast +address without specifying an explicit scope are affected by the most recent +successful use of this call. + +#### Examples: IPv6 Outgoing Multicast Interface + +On most systems, where scope format uses the interface name: + +```js +const socket = dgram.createSocket('udp6'); + +socket.bind(1234, () => { + socket.setMulticastInterface('::%eth1'); +}); +``` + +On Windows, where scope format uses an interface number: + +```js +const socket = dgram.createSocket('udp6'); + +socket.bind(1234, () => { + socket.setMulticastInterface('::%2'); +}); +``` + +#### Example: IPv4 Outgoing Multicast Interface +All systems use an IP of the host on the desired physical interface: +```js +const socket = dgram.createSocket('udp4'); + +socket.bind(1234, () => { + socket.setMulticastInterface('10.0.0.2'); +}); +``` + +#### Call Results + +A call on a socket that is not ready to send or no longer open may throw a *Not +running* [`Error`][]. + +If `multicastInterface` can not be parsed into an IP then an *EINVAL* +[`System Error`][] is thrown. + +On IPv4, if `multicastInterface` is a valid address but does not match any +interface, or if the address does not match the family then +a [`System Error`][] such as `EADDRNOTAVAIL` or `EPROTONOSUP` is thrown. + +On IPv6, most errors with specifying or omiting scope will result in the socket +continuing to use (or returning to) the system's default interface selection. + +A socket's address family's ANY address (IPv4 `'0.0.0.0'` or IPv6 `'::'`) can be +used to return control of the sockets default outgoing interface to the system +for future multicast packets. + + ### socket.setMulticastLoopback(flag) @@ -53,40 +49,43 @@ is not recommended in production environments. Experimental features are not subject to the Node.js Semantic Versioning model. ``` -*Note*: Caution must be used when making use of `Experimental` features, -particularly within modules that may be used as dependencies (or dependencies -of dependencies) within a Node.js application. End users may not be aware that -experimental features are being used, and therefore may experience unexpected -failures or behavioral changes when changes occur. To help avoid such surprises, -`Experimental` features may require a command-line flag to explicitly enable -them, or may cause a process warning to be emitted. By default, such warnings -are printed to `stderr` and may be handled by attaching a listener to the -`process.on('warning')` event. - ```txt Stability: 2 - Stable The API has proven satisfactory. Compatibility with the npm ecosystem is a high priority, and will not be broken unless absolutely necessary. ``` +*Note*: Caution must be used when making use of `Experimental` features, +particularly within modules that may be used as dependencies (or dependencies +of dependencies) within a Node.js application. End users may not be aware that +experimental features are being used, and therefore may experience unexpected +failures or behavior changes when API modifications occur. To help avoid such +surprises, `Experimental` features may require a command-line flag to +explicitly enable them, or may cause a process warning to be emitted. +By default, such warnings are printed to [`stderr`][] and may be handled by +attaching a listener to the [`process.on('warning')`][] event. + ## JSON Output + > Stability: 1 - Experimental -Every HTML file in the markdown has a corresponding JSON file with the -same data. - -This feature was added in Node.js v0.6.12. It is experimental. +Every `.html` document has a corresponding `.json` document presenting +the same information in a structured manner. This feature is +experimental, and added for the benefit of IDEs and other utilities that +wish to do programmatic things with the documentation. ## Syscalls and man pages System calls like open(2) and read(2) define the interface between user programs and the underlying operating system. Node functions which simply wrap a syscall, -like `fs.open()`, will document that. The docs link to the corresponding man +like [`fs.open()`][], will document that. The docs link to the corresponding man pages (short for manual pages) which describe how the syscalls work. -**Note:** some syscalls, like lchown(2), are BSD-specific. That means, for -example, that `fs.lchown()` only works on macOS and other BSD-derived systems, +Some syscalls, like lchown(2), are BSD-specific. That means, for +example, that [`fs.lchown()`][] only works on macOS and other BSD-derived systems, and is not available on Linux. Most Unix syscalls have Windows equivalents, but behavior may differ on Windows @@ -96,3 +95,7 @@ issue 4760](https://github.com/nodejs/node/issues/4760). [submit an issue]: https://github.com/nodejs/node/issues/new [the contributing guide]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md +[`stderr`]: process.html#process_process_stderr +[`process.on('warning')`]: process.html#process_event_warning +[`fs.open()`]: fs.html#fs_fs_open_path_flags_mode_callback +[`fs.lchown()`]: fs.html#fs_fs_lchown_path_uid_gid_callback diff --git a/doc/api/fs.md b/doc/api/fs.md index 5e6ebae8dc5b8c..b47e639e3ca98f 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -510,8 +510,10 @@ added: v0.1.30 * `callback` {Function} * `err` {Error} -Asynchronous chmod(2). No arguments other than a possible exception are given -to the completion callback. +Asynchronously changes the permissions of a file. No arguments other than a +possible exception are given to the completion callback. + +See also: chmod(2) ## fs.chmodSync(path, mode) + +* `socket` {net.Socket} + +Called when `socket` is detached from a request and could be persisted by the +Agent. Default behavior is to: + +```js +socket.unref(); +socket.setKeepAlive(agent.keepAliveMsecs); +``` + +This method can be overridden by a particular `Agent` subclass. If this +method returns a falsy value, the socket will be destroyed instead of persisting +it for use with the next request. + +### agent.reuseSocket(socket, request) + + +* `socket` {net.Socket} +* `request` {http.ClientRequest} + +Called when `socket` is attached to `request` after being persisted because of +the keep-alive options. Default behavior is to: + +```js +socket.ref(); +``` + +This method can be overridden by a particular `Agent` subclass. + ### agent.destroy() + +* {Object} + +Provides general utility methods when interacting with instances of +`Module` -- the `module` variable often seen in file modules. Accessed +via `require('module')`. + +### module.builtinModules + + +* {string[]} + +A list of the names of all modules provided by Node.js. Can be used to verify +if a module is maintained by a third-party module or not. + +[`__dirname`]: #modules_dirname +[`__filename`]: #modules_filename [`Error`]: errors.html#errors_class_error [module resolution]: #modules_all_together diff --git a/doc/api/net.md b/doc/api/net.md index 5c9c3dea6051b4..8e40c2297df77a 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -121,6 +121,8 @@ connections use asynchronous `server.getConnections` instead. added: v0.9.7 --> +* Returns {net.Server} + Asynchronously get the number of concurrent connections on the server. Works when sockets were sent to forks. @@ -575,6 +577,8 @@ server will still send some data. If `data` is specified, it is equivalent to calling `socket.write(data, encoding)` followed by `socket.end()`. +Returns `socket`. + ### socket.localAddress + +* {integer} + +The `process.ppid` property returns the PID of the current parent process. + +```js +console.log(`The parent process is pid ${process.ppid}`); +``` + ## process.release * `options` {Object} @@ -823,6 +827,7 @@ added: v0.11.3 `tls.createSecureContext()`. *Note*: In effect, all [`tls.createSecureContext()`][] options can be provided, but they will be _completely ignored_ unless the `secureContext` option is missing. + * `lookup`: {Function} Custom lookup function. Defaults to [`dns.lookup()`][]. * ...: Optional [`tls.createSecureContext()`][] options can be provided, see the `secureContext` option for more information. * `callback` {Function} @@ -1243,3 +1248,4 @@ where `secure_socket` has the same API as `pair.cleartext`. [modifying the default cipher suite]: #tls_modifying_the_default_tls_cipher_suite [specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html [tls.Server]: #tls_class_tls_server +[`dns.lookup()`]: dns.html#dns_dns_lookup_hostname_options_callback diff --git a/doc/api/tty.md b/doc/api/tty.md index 497f53509d5a16..8b757c0f02751a 100644 --- a/doc/api/tty.md +++ b/doc/api/tty.md @@ -12,9 +12,9 @@ However, it can be accessed using: const tty = require('tty'); ``` -When Node.js detects that it is being run inside a text terminal ("TTY") -context, the `process.stdin` will, by default, be initialized as an instance of -`tty.ReadStream` and both `process.stdout` and `process.stderr` will, by +When Node.js detects that it is being run with a text terminal ("TTY") +attached, [`process.stdin`][] will, by default, be initialized as an instance of +`tty.ReadStream` and both [`process.stdout`][] and [`process.stderr`][] will, by default be instances of `tty.WriteStream`. The preferred method of determining whether Node.js is being run within a TTY context is to check that the value of the `process.stdout.isTTY` property is `true`: @@ -27,15 +27,16 @@ false ``` In most cases, there should be little to no reason for an application to -create instances of the `tty.ReadStream` and `tty.WriteStream` classes. +manually create instances of the `tty.ReadStream` and `tty.WriteStream` +classes. ## Class: tty.ReadStream -The `tty.ReadStream` class is a subclass of `net.Socket` that represents the -readable side of a TTY. In normal circumstances `process.stdin` will be the +The `tty.ReadStream` class is a subclass of [`net.Socket`][] that represents the +readable side of a TTY. In normal circumstances [`process.stdin`][] will be the only `tty.ReadStream` instance in a Node.js process and there should be no reason to create additional instances. @@ -47,6 +48,13 @@ added: v0.7.7 A `boolean` that is `true` if the TTY is currently configured to operate as a raw device. Defaults to `false`. +### readStream.isTTY + + +A `boolean` that is always `true` for `tty.ReadStream` instances. + ### readStream.setRawMode(mode) The `tty.WriteStream` class is a subclass of `net.Socket` that represents the -writable side of a TTY. In normal circumstances, `process.stdout` and -`process.stderr` will be the only `tty.WriteStream` instances created for a +writable side of a TTY. In normal circumstances, [`process.stdout`][] and +[`process.stderr`][] will be the only `tty.WriteStream` instances created for a Node.js process and there should be no reason to create additional instances. ### Event: 'resize' @@ -98,6 +106,13 @@ added: v0.7.7 A `number` specifying the number of columns the TTY currently has. This property is updated whenever the `'resize'` event is emitted. +### writeStream.isTTY + + +A `boolean` that is always `true`. + ### writeStream.rows -For example: `'http:'` +### Class: URL -### urlObject.slashes +Browser-compatible `URL` class, implemented by following the WHATWG URL +Standard. [Examples of parsed URLs][] may be found in the Standard itself. -The `slashes` property is a `boolean` with a value of `true` if two ASCII -forward-slash characters (`/`) are required following the colon in the -`protocol`. +*Note*: In accordance with browser conventions, all properties of `URL` objects +are implemented as getters and setters on the class prototype, rather than as +data properties on the object itself. Thus, unlike [legacy urlObject][]s, using +the `delete` keyword on any properties of `URL` objects (e.g. `delete +myURL.protocol`, `delete myURL.pathname`, etc) has no effect but will still +return `true`. -### urlObject.host +#### Constructor: new URL(input[, base]) -The `host` property is the full lower-cased host portion of the URL, including -the `port` if specified. +* `input` {string} The input URL to parse +* `base` {string|URL} The base URL to resolve against if the `input` is not + absolute. + +Creates a new `URL` object by parsing the `input` relative to the `base`. If +`base` is passed as a string, it will be parsed equivalent to `new URL(base)`. + +```js +const { URL } = require('url'); +const myURL = new URL('/foo', 'https://example.org/'); +// https://example.org/foo +``` + +A `TypeError` will be thrown if the `input` or `base` are not valid URLs. Note +that an effort will be made to coerce the given values into strings. For +instance: + +```js +const { URL } = require('url'); +const myURL = new URL({ toString: () => 'https://example.org/' }); +// https://example.org/ +``` + +Unicode characters appearing within the hostname of `input` will be +automatically converted to ASCII using the [Punycode][] algorithm. + +```js +const { URL } = require('url'); +const myURL = new URL('https://你好你好'); +// https://xn--6qqa088eba/ +``` + +*Note*: This feature is only available if the `node` executable was compiled +with [ICU][] enabled. If not, the domain names are passed through unchanged. + +#### url.hash + +* {string} + +Gets and sets the fragment portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/foo#bar'); +console.log(myURL.hash); +// Prints #bar + +myURL.hash = 'baz'; +console.log(myURL.href); +// Prints https://example.org/foo#baz +``` + +Invalid URL characters included in the value assigned to the `hash` property +are [percent-encoded][]. Note that the selection of which characters to +percent-encode may vary somewhat from what the [`url.parse()`][] and +[`url.format()`][] methods would produce. + +#### url.host + +* {string} + +Gets and sets the host portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org:81/foo'); +console.log(myURL.host); +// Prints example.org:81 + +myURL.host = 'example.com:82'; +console.log(myURL.href); +// Prints https://example.com:82/foo +``` + +Invalid host values assigned to the `host` property are ignored. + +#### url.hostname + +* {string} + +Gets and sets the hostname portion of the URL. The key difference between +`url.host` and `url.hostname` is that `url.hostname` does *not* include the +port. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org:81/foo'); +console.log(myURL.hostname); +// Prints example.org + +myURL.hostname = 'example.com:82'; +console.log(myURL.href); +// Prints https://example.com:81/foo +``` + +Invalid hostname values assigned to the `hostname` property are ignored. + +#### url.href + +* {string} + +Gets and sets the serialized URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/foo'); +console.log(myURL.href); +// Prints https://example.org/foo + +myURL.href = 'https://example.com/bar'; +console.log(myURL.href); +// Prints https://example.com/bar +``` + +Getting the value of the `href` property is equivalent to calling +[`url.toString()`][]. + +Setting the value of this property to a new value is equivalent to creating a +new `URL` object using [`new URL(value)`][`new URL()`]. Each of the `URL` +object's properties will be modified. + +If the value assigned to the `href` property is not a valid URL, a `TypeError` +will be thrown. + +#### url.origin + +* {string} + +Gets the read-only serialization of the URL's origin. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/foo/bar?baz'); +console.log(myURL.origin); +// Prints https://example.org +``` + +```js +const { URL } = require('url'); +const idnURL = new URL('https://你好你好'); +console.log(idnURL.origin); +// Prints https://xn--6qqa088eba + +console.log(idnURL.hostname); +// Prints xn--6qqa088eba +``` + +#### url.password + +* {string} + +Gets and sets the password portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://abc:xyz@example.com'); +console.log(myURL.password); +// Prints xyz + +myURL.password = '123'; +console.log(myURL.href); +// Prints https://abc:123@example.com +``` + +Invalid URL characters included in the value assigned to the `password` property +are [percent-encoded][]. Note that the selection of which characters to +percent-encode may vary somewhat from what the [`url.parse()`][] and +[`url.format()`][] methods would produce. + +#### url.pathname + +* {string} + +Gets and sets the path portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/abc/xyz?123'); +console.log(myURL.pathname); +// Prints /abc/xyz + +myURL.pathname = '/abcdef'; +console.log(myURL.href); +// Prints https://example.org/abcdef?123 +``` + +Invalid URL characters included in the value assigned to the `pathname` +property are [percent-encoded][]. Note that the selection of which characters +to percent-encode may vary somewhat from what the [`url.parse()`][] and +[`url.format()`][] methods would produce. + +#### url.port + +* {string} + +Gets and sets the port portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org:8888'); +console.log(myURL.port); +// Prints 8888 + +// Default ports are automatically transformed to the empty string +// (HTTPS protocol's default port is 443) +myURL.port = '443'; +console.log(myURL.port); +// Prints the empty string +console.log(myURL.href); +// Prints https://example.org/ + +myURL.port = 1234; +console.log(myURL.port); +// Prints 1234 +console.log(myURL.href); +// Prints https://example.org:1234/ + +// Completely invalid port strings are ignored +myURL.port = 'abcd'; +console.log(myURL.port); +// Prints 1234 + +// Leading numbers are treated as a port number +myURL.port = '5678abcd'; +console.log(myURL.port); +// Prints 5678 + +// Non-integers are truncated +myURL.port = 1234.5678; +console.log(myURL.port); +// Prints 1234 + +// Out-of-range numbers are ignored +myURL.port = 1e10; +console.log(myURL.port); +// Prints 1234 +``` + +The port value may be set as either a number or as a String containing a number +in the range `0` to `65535` (inclusive). Setting the value to the default port +of the `URL` objects given `protocol` will result in the `port` value becoming +the empty string (`''`). + +If an invalid string is assigned to the `port` property, but it begins with a +number, the leading number is assigned to `port`. Otherwise, or if the number +lies outside the range denoted above, it is ignored. + +#### url.protocol + +* {string} + +Gets and sets the protocol portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org'); +console.log(myURL.protocol); +// Prints https: + +myURL.protocol = 'ftp'; +console.log(myURL.href); +// Prints ftp://example.org/ +``` + +Invalid URL protocol values assigned to the `protocol` property are ignored. + +#### url.search + +* {string} + +Gets and sets the serialized query portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/abc?123'); +console.log(myURL.search); +// Prints ?123 + +myURL.search = 'abc=xyz'; +console.log(myURL.href); +// Prints https://example.org/abc?abc=xyz +``` + +Any invalid URL characters appearing in the value assigned the `search` +property will be [percent-encoded][]. Note that the selection of which +characters to percent-encode may vary somewhat from what the [`url.parse()`][] +and [`url.format()`][] methods would produce. + +#### url.searchParams + +* {URLSearchParams} + +Gets the [`URLSearchParams`][] object representing the query parameters of the +URL. This property is read-only; to replace the entirety of query parameters of +the URL, use the [`url.search`][] setter. See [`URLSearchParams`][] +documentation for details. + +#### url.username + +* {string} + +Gets and sets the username portion of the URL. + +```js +const { URL } = require('url'); +const myURL = new URL('https://abc:xyz@example.com'); +console.log(myURL.username); +// Prints abc + +myURL.username = '123'; +console.log(myURL.href); +// Prints https://123:xyz@example.com/ +``` + +Any invalid URL characters appearing in the value assigned the `username` +property will be [percent-encoded][]. Note that the selection of which +characters to percent-encode may vary somewhat from what the [`url.parse()`][] +and [`url.format()`][] methods would produce. + +#### url.toString() + +* Returns: {string} + +The `toString()` method on the `URL` object returns the serialized URL. The +value returned is equivalent to that of [`url.href`][] and [`url.toJSON()`][]. + +Because of the need for standard compliance, this method does not allow users +to customize the serialization process of the URL. + +#### url.toJSON() + +* Returns: {string} + +The `toJSON()` method on the `URL` object returns the serialized URL. The +value returned is equivalent to that of [`url.href`][] and +[`url.toString()`][]. + +This method is automatically called when an `URL` object is serialized +with [`JSON.stringify()`][]. + +```js +const { URL } = require('url'); +const myURLs = [ + new URL('https://www.example.com'), + new URL('https://test.example.org') +]; +console.log(JSON.stringify(myURLs)); +// Prints ["https://www.example.com/","https://test.example.org/"] +``` + +### Class: URLSearchParams + + +The `URLSearchParams` API provides read and write access to the query of a +`URL`. The `URLSearchParams` class can also be used standalone with one of the +four following constructors. + +The WHATWG `URLSearchParams` interface and the [`querystring`][] module have +similar purpose, but the purpose of the [`querystring`][] module is more +general, as it allows the customization of delimiter characters (`&` and `=`). +On the other hand, this API is designed purely for URL query strings. + +```js +const { URL, URLSearchParams } = require('url'); + +const myURL = new URL('https://example.org/?abc=123'); +console.log(myURL.searchParams.get('abc')); +// Prints 123 + +myURL.searchParams.append('abc', 'xyz'); +console.log(myURL.href); +// Prints https://example.org/?abc=123&abc=xyz + +myURL.searchParams.delete('abc'); +myURL.searchParams.set('a', 'b'); +console.log(myURL.href); +// Prints https://example.org/?a=b + +const newSearchParams = new URLSearchParams(myURL.searchParams); +// The above is equivalent to +// const newSearchParams = new URLSearchParams(myURL.search); + +newSearchParams.append('a', 'c'); +console.log(myURL.href); +// Prints https://example.org/?a=b +console.log(newSearchParams.toString()); +// Prints a=b&a=c + +// newSearchParams.toString() is implicitly called +myURL.search = newSearchParams; +console.log(myURL.href); +// Prints https://example.org/?a=b&a=c +newSearchParams.delete('a'); +console.log(myURL.href); +// Prints https://example.org/?a=b&a=c +``` + +#### Constructor: new URLSearchParams() + +Instantiate a new empty `URLSearchParams` object. + +#### Constructor: new URLSearchParams(string) + +* `string` {string} A query string + +Parse the `string` as a query string, and use it to instantiate a new +`URLSearchParams` object. A leading `'?'`, if present, is ignored. + +```js +const { URLSearchParams } = require('url'); +let params; + +params = new URLSearchParams('user=abc&query=xyz'); +console.log(params.get('user')); +// Prints 'abc' +console.log(params.toString()); +// Prints 'user=abc&query=xyz' + +params = new URLSearchParams('?user=abc&query=xyz'); +console.log(params.toString()); +// Prints 'user=abc&query=xyz' +``` + +#### Constructor: new URLSearchParams(obj) + + +* `obj` {Object} An object representing a collection of key-value pairs + +Instantiate a new `URLSearchParams` object with a query hash map. The key and +value of each property of `obj` are always coerced to strings. + +*Note*: Unlike [`querystring`][] module, duplicate keys in the form of array +values are not allowed. Arrays are stringified using [`array.toString()`][], +which simply joins all array elements with commas. + +```js +const { URLSearchParams } = require('url'); +const params = new URLSearchParams({ + user: 'abc', + query: ['first', 'second'] +}); +console.log(params.getAll('query')); +// Prints [ 'first,second' ] +console.log(params.toString()); +// Prints 'user=abc&query=first%2Csecond' +``` + +#### Constructor: new URLSearchParams(iterable) + + +* `iterable` {Iterable} An iterable object whose elements are key-value pairs + +Instantiate a new `URLSearchParams` object with an iterable map in a way that +is similar to [`Map`][]'s constructor. `iterable` can be an Array or any +iterable object. That means `iterable` can be another `URLSearchParams`, in +which case the constructor will simply create a clone of the provided +`URLSearchParams`. Elements of `iterable` are key-value pairs, and can +themselves be any iterable object. + +Duplicate keys are allowed. + +```js +const { URLSearchParams } = require('url'); +let params; + +// Using an array +params = new URLSearchParams([ + ['user', 'abc'], + ['query', 'first'], + ['query', 'second'] +]); +console.log(params.toString()); +// Prints 'user=abc&query=first&query=second' + +// Using a Map object +const map = new Map(); +map.set('user', 'abc'); +map.set('query', 'xyz'); +params = new URLSearchParams(map); +console.log(params.toString()); +// Prints 'user=abc&query=xyz' + +// Using a generator function +function* getQueryPairs() { + yield ['user', 'abc']; + yield ['query', 'first']; + yield ['query', 'second']; +} +params = new URLSearchParams(getQueryPairs()); +console.log(params.toString()); +// Prints 'user=abc&query=first&query=second' + +// Each key-value pair must have exactly two elements +new URLSearchParams([ + ['user', 'abc', 'error'] +]); +// Throws TypeError [ERR_INVALID_TUPLE]: +// Each query pair must be an iterable [name, value] tuple +``` + +#### urlSearchParams.append(name, value) + +* `name` {string} +* `value` {string} + +Append a new name-value pair to the query string. + +#### urlSearchParams.delete(name) + +* `name` {string} + +Remove all name-value pairs whose name is `name`. + +#### urlSearchParams.entries() + +* Returns: {Iterator} + +Returns an ES6 Iterator over each of the name-value pairs in the query. +Each item of the iterator is a JavaScript Array. The first item of the Array +is the `name`, the second item of the Array is the `value`. + +Alias for [`urlSearchParams[@@iterator]()`][`urlSearchParams@@iterator()`]. + +#### urlSearchParams.forEach(fn[, thisArg]) + +* `fn` {Function} Function invoked for each name-value pair in the query. +* `thisArg` {Object} Object to be used as `this` value for when `fn` is called + +Iterates over each name-value pair in the query and invokes the given function. + +```js +const { URL } = require('url'); +const myURL = new URL('https://example.org/?a=b&c=d'); +myURL.searchParams.forEach((value, name, searchParams) => { + console.log(name, value, myURL.searchParams === searchParams); +}); +// Prints: +// a b true +// c d true +``` + +#### urlSearchParams.get(name) + +* `name` {string} +* Returns: {string} or `null` if there is no name-value pair with the given + `name`. + +Returns the value of the first name-value pair whose name is `name`. If there +are no such pairs, `null` is returned. + +#### urlSearchParams.getAll(name) + +* `name` {string} +* Returns: {Array} + +Returns the values of all name-value pairs whose name is `name`. If there are +no such pairs, an empty array is returned. + +#### urlSearchParams.has(name) + +* `name` {string} +* Returns: {boolean} + +Returns `true` if there is at least one name-value pair whose name is `name`. + +#### urlSearchParams.keys() + +* Returns: {Iterator} + +Returns an ES6 Iterator over the names of each name-value pair. + +```js +const { URLSearchParams } = require('url'); +const params = new URLSearchParams('foo=bar&foo=baz'); +for (const name of params.keys()) { + console.log(name); +} +// Prints: +// foo +// foo +``` + +#### urlSearchParams.set(name, value) + +* `name` {string} +* `value` {string} + +Sets the value in the `URLSearchParams` object associated with `name` to +`value`. If there are any pre-existing name-value pairs whose names are `name`, +set the first such pair's value to `value` and remove all others. If not, +append the name-value pair to the query string. + +```js +const { URLSearchParams } = require('url'); + +const params = new URLSearchParams(); +params.append('foo', 'bar'); +params.append('foo', 'baz'); +params.append('abc', 'def'); +console.log(params.toString()); +// Prints foo=bar&foo=baz&abc=def + +params.set('foo', 'def'); +params.set('xyz', 'opq'); +console.log(params.toString()); +// Prints foo=def&abc=def&xyz=opq +``` + +#### urlSearchParams.sort() + + +Sort all existing name-value pairs in-place by their names. Sorting is done +with a [stable sorting algorithm][], so relative order between name-value pairs +with the same name is preserved. + +This method can be used, in particular, to increase cache hits. + +```js +const { URLSearchParams } = require('url'); +const params = new URLSearchParams('query[]=abc&type=search&query[]=123'); +params.sort(); +console.log(params.toString()); +// Prints query%5B%5D=abc&query%5B%5D=123&type=search +``` + +#### urlSearchParams.toString() + +* Returns: {string} + +Returns the search parameters serialized as a string, with characters +percent-encoded where necessary. + +#### urlSearchParams.values() + +* Returns: {Iterator} + +Returns an ES6 Iterator over the values of each name-value pair. + +#### urlSearchParams\[@@iterator\]() + +* Returns: {Iterator} -For example: `'host.com:8080'` +Returns an ES6 Iterator over each of the name-value pairs in the query string. +Each item of the iterator is a JavaScript Array. The first item of the Array +is the `name`, the second item of the Array is the `value`. -### urlObject.auth +Alias for [`urlSearchParams.entries()`][]. + +```js +const { URLSearchParams } = require('url'); +const params = new URLSearchParams('foo=bar&xyz=baz'); +for (const [name, value] of params) { + console.log(name, value); +} +// Prints: +// foo bar +// xyz baz +``` + +### url.domainToASCII(domain) + + +* `domain` {string} +* Returns: {string} + +Returns the [Punycode][] ASCII serialization of the `domain`. If `domain` is an +invalid domain, the empty string is returned. + +It performs the inverse operation to [`url.domainToUnicode()`][]. + +```js +const url = require('url'); +console.log(url.domainToASCII('español.com')); +// Prints xn--espaol-zwa.com +console.log(url.domainToASCII('中文.com')); +// Prints xn--fiq228c.com +console.log(url.domainToASCII('xn--iñvalid.com')); +// Prints an empty string +``` + +### url.domainToUnicode(domain) + + +* `domain` {string} +* Returns: {string} + +Returns the Unicode serialization of the `domain`. If `domain` is an invalid +domain, the empty string is returned. + +It performs the inverse operation to [`url.domainToASCII()`][]. + +```js +const url = require('url'); +console.log(url.domainToUnicode('xn--espaol-zwa.com')); +// Prints español.com +console.log(url.domainToUnicode('xn--fiq228c.com')); +// Prints 中文.com +console.log(url.domainToUnicode('xn--iñvalid.com')); +// Prints an empty string +``` + +## Legacy URL API + +### Legacy urlObject + +The legacy urlObject (`require('url').Url`) is created and returned by the +`url.parse()` function. + +#### urlObject.auth The `auth` property is the username and password portion of the URL, also referred to as "userinfo". This string subset follows the `protocol` and @@ -72,12 +823,33 @@ with the `[:{password}]` portion being optional. For example: `'user:pass'` -### urlObject.hostname +#### urlObject.hash + +The `hash` property consists of the "fragment" portion of the URL including +the leading ASCII hash (`#`) character. + +For example: `'#hash'` + +#### urlObject.host + +The `host` property is the full lower-cased host portion of the URL, including +the `port` if specified. + +For example: `'sub.host.com:8080'` + +#### urlObject.hostname The `hostname` property is the lower-cased host name portion of the `host` component *without* the `port` included. -For example: `'host.com'` +For example: `'sub.host.com'` + +#### urlObject.href + +The `href` property is the full URL string that was parsed with both the +`protocol` and `host` components converted to lower-case. + +For example: `'http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash'` ### urlObject.port @@ -126,14 +898,22 @@ For example: `'query=string'` or `{'query': 'string'}` If returned as a string, no decoding of the query string is performed. If returned as an object, both keys and values are decoded. -### urlObject.hash +#### urlObject.search -The `hash` property consists of the "fragment" portion of the URL including -the leading ASCII hash (`#`) character. +The `search` property consists of the entire "query string" portion of the +URL, including the leading ASCII question mark (`?`) character. -For example: `'#hash'` +For example: `'?query=string'` -## url.format(urlObject) +No decoding of the query string is performed. + +#### urlObject.slashes + +The `slashes` property is a `boolean` with a value of `true` if two ASCII +forward-slash characters (`/`) are required following the colon in the +`protocol`. + +### url.format(urlObject) @@ -201,7 +981,7 @@ The formatting process operates as follows: * `result` is returned. -## url.parse(urlString[, parseQueryString[, slashesDenoteHost]]) +### url.parse(urlString[, parseQueryString[, slashesDenoteHost]]) @@ -220,7 +1000,7 @@ added: v0.1.25 The `url.parse()` method takes a URL string, parses it, and returns a URL object. -## url.resolve(from, to) +### url.resolve(from, to) @@ -239,11 +1019,18 @@ url.resolve('http://example.com/', '/one'); // 'http://example.com/one' url.resolve('http://example.com/one', '/two'); // 'http://example.com/two' ``` -## Escaped Characters + +## Percent-Encoding in URLs + +URLs are permitted to only contain a certain range of characters. Any character +falling outside of that range must be encoded. How such characters are encoded, +and which characters to encode depends entirely on where the character is +located within the structure of the URL. -URLs are only permitted to contain a certain range of characters. Spaces (`' '`) -and the following characters will be automatically escaped in the -properties of URL objects: +### Legacy API + +Within the Legacy API, spaces (`' '`) and the following characters will be +automatically escaped in the properties of URL objects: ```txt < > " ` \r \n \t { } | \ ^ ' @@ -252,6 +1039,68 @@ properties of URL objects: For example, the ASCII space character (`' '`) is encoded as `%20`. The ASCII forward slash (`/`) character is encoded as `%3C`. +### WHATWG API + +The [WHATWG URL Standard][] uses a more selective and fine grained approach to +selecting encoded characters than that used by the Legacy API. + +The WHATWG algorithm defines three "percent-encode sets" that describe ranges +of characters that must be percent-encoded: + +* The *C0 control percent-encode set* includes code points in range U+0000 to + U+001F (inclusive) and all code points greater than U+007E. + +* The *path percent-encode set* includes the *C0 control percent-encode set* + and code points U+0020, U+0022, U+0023, U+003C, U+003E, U+003F, U+0060, + U+007B, and U+007D. + +* The *userinfo encode set* includes the *path percent-encode set* and code + points U+002F, U+003A, U+003B, U+003D, U+0040, U+005B, U+005C, U+005D, + U+005E, and U+007C. + +The *userinfo percent-encode set* is used exclusively for username and +passwords encoded within the URL. The *path percent-encode set* is used for the +path of most URLs. The *C0 control percent-encode set* is used for all +other cases, including URL fragments in particular, but also host and path +under certain specific conditions. + +When non-ASCII characters appear within a hostname, the hostname is encoded +using the [Punycode][] algorithm. Note, however, that a hostname *may* contain +*both* Punycode encoded and percent-encoded characters. For example: + +```js +const { URL } = require('url'); +const myURL = new URL('https://%CF%80.com/foo'); +console.log(myURL.href); +// Prints https://xn--1xa.com/foo +console.log(myURL.origin); +// Prints https://π.com +``` + [`Error`]: errors.html#errors_class_error -[`querystring`]: querystring.html +[`JSON.stringify()`]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify +[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map [`TypeError`]: errors.html#errors_class_typeerror +[`URLSearchParams`]: #url_class_urlsearchparams +[`array.toString()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString +[`new URL()`]: #url_constructor_new_url_input_base +[`querystring`]: querystring.html +[`require('url').format()`]: #url_url_format_url_options +[`url.domainToASCII()`]: #url_url_domaintoascii_domain +[`url.domainToUnicode()`]: #url_url_domaintounicode_domain +[`url.format()`]: #url_url_format_urlobject +[`url.href`]: #url_url_href +[`url.parse()`]: #url_url_parse_urlstring_parsequerystring_slashesdenotehost +[`url.search`]: #url_url_search +[`url.toJSON()`]: #url_url_tojson +[`url.toString()`]: #url_url_tostring +[`urlSearchParams.entries()`]: #url_urlsearchparams_entries +[`urlSearchParams@@iterator()`]: #url_urlsearchparams_iterator +[ICU]: intl.html#intl_options_for_building_node_js +[Punycode]: https://tools.ietf.org/html/rfc5891#section-4.4 +[WHATWG URL Standard]: https://url.spec.whatwg.org/ +[WHATWG URL]: #url_the_whatwg_url_api +[examples of parsed URLs]: https://url.spec.whatwg.org/#example-url-parsing +[legacy urlObject]: #url_legacy_urlobject +[percent-encoded]: #whatwg-percent-encoding +[stable sorting algorithm]: https://en.wikipedia.org/wiki/Sorting_algorithm#Stability diff --git a/doc/api/util.md b/doc/api/util.md index e44d38706f2c37..9e29cfc073ccc5 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -105,7 +105,9 @@ Each placeholder token is replaced with the converted value from the corresponding argument. Supported placeholders are: * `%s` - String. -* `%d` - Number (both integer and float). +* `%d` - Number (integer or floating point value). +* `%i` - Integer. +* `%f` - Floating point value. * `%j` - JSON. Replaced with the string `'[Circular]'` if the argument contains circular references. * `%%` - single percent sign (`'%'`). This does not consume an argument. diff --git a/doc/changelogs/CHANGELOG_V6.md b/doc/changelogs/CHANGELOG_V6.md index 5945bcf6d3e4db..9860915026acfe 100644 --- a/doc/changelogs/CHANGELOG_V6.md +++ b/doc/changelogs/CHANGELOG_V6.md @@ -7,6 +7,7 @@ +6.13.0
6.12.3
6.12.2
6.12.1
@@ -58,6 +59,164 @@ [Node.js Long Term Support Plan](https://github.com/nodejs/LTS) and will be supported actively until April 2018 and maintained until April 2019. + +## 2018-02-13, Version 6.13.0 'Boron' (LTS), @MylesBorins + +This LTS release comes with 112 commits, 17 of which are considered Semver-Minor. This includes 32 which are doc related, +31 which are test related, 8 which are build / tool related and 1 commit which updates a dependency. + +### Notable Changes + +* **console**: + - added console.count() and console.clear() (James M Snell) [#12678](https://github.com/nodejs/node/pull/12678) +* **crypto**: + - expose ECDH class (Bryan English) [#8188](https://github.com/nodejs/node/pull/8188) + - added cypto.randomFill() and crypto.randomFillSync() (Evan Lucas) [#10209](https://github.com/nodejs/node/pull/10209) + - warn on invalid authentication tag length (Tobias Nießen) [#17566](https://github.com/nodejs/node/pull/17566) +* **deps**: + - upgrade libuv to 1.16.1 (cjihrig) [#16835](https://github.com/nodejs/node/pull/16835) +* **dgram**: + - added socket.setMulticastInterface() (Will Young) [#7855](https://github.com/nodejs/node/pull/7855) +* **http**: + - add agent.keepSocketAlive and agent.reuseSocket as to allow overridable keep-alive behavior of `Agent` (Fedor Indutny) [#13005](https://github.com/nodejs/node/pull/13005) +* **lib**: + - return this from net.Socket.end() (Sam Roberts) [#13481](https://github.com/nodejs/node/pull/13481) +* **module**: + - add builtinModules api that provides list of all builtin modules in Node (Jon Moss) [#16386](https://github.com/nodejs/node/pull/16386) +* **net**: + - return this from getConnections() (Sam Roberts) [#13553](https://github.com/nodejs/node/pull/13553) +* **promises**: + - more robust stringification for unhandled rejections (Timothy Gu) [#13784](https://github.com/nodejs/node/pull/13784) +* **repl**: + - improve require() autocompletion (Alexey Orlenko) [#14409](https://github.com/nodejs/node/pull/14409) +* **src**: + - add openssl-system-ca-path configure option (Daniel Bevenius) [#16790](https://github.com/nodejs/node/pull/16790) + - add --use-bundled-ca --use-openssl-ca check (Daniel Bevenius) [#12087](https://github.com/nodejs/node/pull/12087) + - add process.ppid (cjihrig) [#16839](https://github.com/nodejs/node/pull/16839) +* **tls**: + - accept `lookup` option for `tls.connect()` (Fedor Indutny) [#12839](https://github.com/nodejs/node/pull/12839) +* **tools, build**: + - a new macOS installer! (JP Wesselink) [#15179](https://github.com/nodejs/node/pull/15179) +* **url**: + - WHATWG URL api support (James M Snell) [#7448](https://github.com/nodejs/node/pull/7448) +* **util**: + - add %i and %f formatting specifiers (Roman Reiss) [#10308](https://github.com/nodejs/node/pull/10308) + +### Commits + +* [[`6f33953d90`](https://github.com/nodejs/node/commit/6f33953d90)] - **benchmark**: fix timeout in write-stream-throughput (Anatoli Papirovski) [#17958](https://github.com/nodejs/node/pull/17958) +* [[`ce136392fb`](https://github.com/nodejs/node/commit/ce136392fb)] - **(SEMVER-MINOR)** **console**: add console.count() and console.clear() (James M Snell) [#12678](https://github.com/nodejs/node/pull/12678) +* [[`691cd5a3d1`](https://github.com/nodejs/node/commit/691cd5a3d1)] - **crypto**: warn on invalid authentication tag length (Tobias Nießen) [#17566](https://github.com/nodejs/node/pull/17566) +* [[`4b4e4db1c1`](https://github.com/nodejs/node/commit/4b4e4db1c1)] - **crypto**: add ocsp_request ClientHelloParser::Reset (Daniel Bevenius) [#17753](https://github.com/nodejs/node/pull/17753) +* [[`c377d2299a`](https://github.com/nodejs/node/commit/c377d2299a)] - **crypto**: remove unused header in clienthello.h (Daniel Bevenius) [#17752](https://github.com/nodejs/node/pull/17752) +* [[`ddd9d85681`](https://github.com/nodejs/node/commit/ddd9d85681)] - **crypto**: remove BIO_set_shutdown (Daniel Bevenius) [#17542](https://github.com/nodejs/node/pull/17542) +* [[`f3b3437e48`](https://github.com/nodejs/node/commit/f3b3437e48)] - **(SEMVER-MINOR)** **crypto**: expose ECDH class (Bryan English) [#8188](https://github.com/nodejs/node/pull/8188) +* [[`6f62f83468`](https://github.com/nodejs/node/commit/6f62f83468)] - **(SEMVER-MINOR)** **crypto**: add randomFill and randomFillSync (Evan Lucas) [#10209](https://github.com/nodejs/node/pull/10209) +* [[`a1d7469aef`](https://github.com/nodejs/node/commit/a1d7469aef)] - **(SEMVER-MINOR)** **deps**: upgrade libuv to 1.16.1 (cjihrig) [#16835](https://github.com/nodejs/node/pull/16835) +* [[`8f2e52abd7`](https://github.com/nodejs/node/commit/8f2e52abd7)] - **(SEMVER-MINOR)** **dgram**: added setMulticastInterface() (Will Young) [#7855](https://github.com/nodejs/node/pull/7855) +* [[`1b689863ee`](https://github.com/nodejs/node/commit/1b689863ee)] - **doc**: remove x86 from os.arch() options (Gibson Fahnestock) [#17899](https://github.com/nodejs/node/pull/17899) +* [[`8f80548b7f`](https://github.com/nodejs/node/commit/8f80548b7f)] - **doc**: move matthewloring to emeriti (Rich Trott) [#17998](https://github.com/nodejs/node/pull/17998) +* [[`15d0ed5f33`](https://github.com/nodejs/node/commit/15d0ed5f33)] - **doc**: move joshgav to TSC emeriti list (Rich Trott) [#17953](https://github.com/nodejs/node/pull/17953) +* [[`12db4d97b2`](https://github.com/nodejs/node/commit/12db4d97b2)] - **doc**: improve security section of README.md (Rich Trott) [#17929](https://github.com/nodejs/node/pull/17929) +* [[`b79189b9b6`](https://github.com/nodejs/node/commit/b79189b9b6)] - **doc**: copy-edit COLLABORATOR_GUIDE.md (Rich Trott) [#17922](https://github.com/nodejs/node/pull/17922) +* [[`7628640db6`](https://github.com/nodejs/node/commit/7628640db6)] - **doc**: improve alt text (Rich Trott) [#17922](https://github.com/nodejs/node/pull/17922) +* [[`bb022dbb96`](https://github.com/nodejs/node/commit/bb022dbb96)] - **doc**: fix spelling of contributors (Rich Trott) [#17922](https://github.com/nodejs/node/pull/17922) +* [[`21c5d820bb`](https://github.com/nodejs/node/commit/21c5d820bb)] - **doc**: add references to PR communication articles (Salame William) [#17902](https://github.com/nodejs/node/pull/17902) +* [[`3c3a631643`](https://github.com/nodejs/node/commit/3c3a631643)] - **doc**: fix typo (Tobias Nießen) [#17900](https://github.com/nodejs/node/pull/17900) +* [[`5b00ee31ee`](https://github.com/nodejs/node/commit/5b00ee31ee)] - **doc**: use my legal name in README (Timothy Gu) [#17894](https://github.com/nodejs/node/pull/17894) +* [[`0ce48f9094`](https://github.com/nodejs/node/commit/0ce48f9094)] - **doc**: use dashes instead of asterisks (Ruben Bridgewater) [#17722](https://github.com/nodejs/node/pull/17722) +* [[`f6b4aa62bc`](https://github.com/nodejs/node/commit/f6b4aa62bc)] - **doc**: update AUTHORS list (Ruben Bridgewater) [#17805](https://github.com/nodejs/node/pull/17805) +* [[`653c026578`](https://github.com/nodejs/node/commit/653c026578)] - **doc**: add starkwang to collaborators (Weijia Wang) [#17847](https://github.com/nodejs/node/pull/17847) +* [[`68164145de`](https://github.com/nodejs/node/commit/68164145de)] - **doc**: improve fs api descriptions (Evan Lucas) [#17679](https://github.com/nodejs/node/pull/17679) +* [[`722640f562`](https://github.com/nodejs/node/commit/722640f562)] - **doc**: instructions on how to make membership public (Michael Dawson) [#17688](https://github.com/nodejs/node/pull/17688) +* [[`1553c7326c`](https://github.com/nodejs/node/commit/1553c7326c)] - **doc**: removed extra explanation in api/buffer.md (Waleed Ashraf) [#17796](https://github.com/nodejs/node/pull/17796) +* [[`22607951b8`](https://github.com/nodejs/node/commit/22607951b8)] - **doc**: use american spelling as per style guide (sreepurnajasti) [#17818](https://github.com/nodejs/node/pull/17818) +* [[`d85840dd8f`](https://github.com/nodejs/node/commit/d85840dd8f)] - **doc**: require CI status indicator in PRs (Nikolai Vavilov) [#17151](https://github.com/nodejs/node/pull/17151) +* [[`5cc6dd6295`](https://github.com/nodejs/node/commit/5cc6dd6295)] - **doc**: remove duplicate the from onboarding.md (sreepurnajasti) [#17733](https://github.com/nodejs/node/pull/17733) +* [[`a6f7ba4f09`](https://github.com/nodejs/node/commit/a6f7ba4f09)] - **doc**: fix typo in README.md (Weijia Wang) [#17729](https://github.com/nodejs/node/pull/17729) +* [[`df48a5ded8`](https://github.com/nodejs/node/commit/df48a5ded8)] - **doc**: fix typo in child_process.md (Rich Trott) [#17727](https://github.com/nodejs/node/pull/17727) +* [[`4cba4324ff`](https://github.com/nodejs/node/commit/4cba4324ff)] - **doc**: improve release guide (Evan Lucas) [#17677](https://github.com/nodejs/node/pull/17677) +* [[`423ef3ddbf`](https://github.com/nodejs/node/commit/423ef3ddbf)] - **doc**: not all example code can be run without 1:1 (Jeremiah Senkpiel) [#17702](https://github.com/nodejs/node/pull/17702) +* [[`c683efbf6d`](https://github.com/nodejs/node/commit/c683efbf6d)] - **doc**: adjust TTY wording & add inter-doc links (Jeremiah Senkpiel) [#17702](https://github.com/nodejs/node/pull/17702) +* [[`14ffddd989`](https://github.com/nodejs/node/commit/14ffddd989)] - **doc**: add isTTY property documentation (SonaySevik) [#16828](https://github.com/nodejs/node/pull/16828) +* [[`9c8d0366b3`](https://github.com/nodejs/node/commit/9c8d0366b3)] - **doc**: fix fs.existsSync description (Jeremiah Senkpiel) [#17702](https://github.com/nodejs/node/pull/17702) +* [[`6abd4599af`](https://github.com/nodejs/node/commit/6abd4599af)] - **doc**: improve documentation.md (Jeremiah Senkpiel) [#17702](https://github.com/nodejs/node/pull/17702) +* [[`d0b89a12ec`](https://github.com/nodejs/node/commit/d0b89a12ec)] - **doc**: add countdown module to writing tests guide (Bamieh) [#17201](https://github.com/nodejs/node/pull/17201) +* [[`1eac4055f0`](https://github.com/nodejs/node/commit/1eac4055f0)] - **doc**: include Daniel Bevenius as a TSC member (Rich Trott) [#17652](https://github.com/nodejs/node/pull/17652) +* [[`83fe79c558`](https://github.com/nodejs/node/commit/83fe79c558)] - **doc**: correct pbkdf2 salt length recommendation (Will Clark) [#17524](https://github.com/nodejs/node/pull/17524) +* [[`43a2bc040f`](https://github.com/nodejs/node/commit/43a2bc040f)] - **doc**: improve randomfill and fix broken link (Sakthipriyan Vairamani (thefourtheye)) [#12541](https://github.com/nodejs/node/pull/12541) +* [[`ef0213c0b8`](https://github.com/nodejs/node/commit/ef0213c0b8)] - **doc**: move Code of Conduct to admin repo (Myles Borins) [#17301](https://github.com/nodejs/node/pull/17301) +* [[`e16d01fc94`](https://github.com/nodejs/node/commit/e16d01fc94)] - **gitignore**: ignore *.VC.db files (Tobias Nießen) [#17898](https://github.com/nodejs/node/pull/17898) +* [[`1390c280bc`](https://github.com/nodejs/node/commit/1390c280bc)] - **(SEMVER-MINOR)** **http**: overridable keep-alive behavior of `Agent` (Fedor Indutny) [#13005](https://github.com/nodejs/node/pull/13005) +* [[`063c4fa345`](https://github.com/nodejs/node/commit/063c4fa345)] - **(SEMVER-MINOR)** **lib**: return this from net.Socket.end() (Sam Roberts) [#13481](https://github.com/nodejs/node/pull/13481) +* [[`cdf4a9c394`](https://github.com/nodejs/node/commit/cdf4a9c394)] - **(SEMVER-MINOR)** **module**: add builtinModules (Jon Moss) [#16386](https://github.com/nodejs/node/pull/16386) +* [[`ffc1444117`](https://github.com/nodejs/node/commit/ffc1444117)] - **net**: remove ADDRCONFIG DNS hint on Windows (Bartosz Sosnowski) [#17662](https://github.com/nodejs/node/pull/17662) +* [[`6a27774882`](https://github.com/nodejs/node/commit/6a27774882)] - **(SEMVER-MINOR)** **net**: return this from getConnections() (Sam Roberts) [#13553](https://github.com/nodejs/node/pull/13553) +* [[`a09e2fd43b`](https://github.com/nodejs/node/commit/a09e2fd43b)] - **net**: fix timeout with null handle (Anatoli Papirovski) [#16489](https://github.com/nodejs/node/pull/16489) +* [[`a301c1a0e0`](https://github.com/nodejs/node/commit/a301c1a0e0)] - **net**: fix timeouts during long writes (Anatoli Papirovski) [#15791](https://github.com/nodejs/node/pull/15791) +* [[`c64a73ba6c`](https://github.com/nodejs/node/commit/c64a73ba6c)] - **promises**: more robust stringification (Timothy Gu) [#13784](https://github.com/nodejs/node/pull/13784) +* [[`3b9fea0782`](https://github.com/nodejs/node/commit/3b9fea0782)] - **(SEMVER-MINOR)** **repl**: improve require() autocompletion (Alexey Orlenko) [#14409](https://github.com/nodejs/node/pull/14409) +* [[`9181fbb699`](https://github.com/nodejs/node/commit/9181fbb699)] - **src**: dumb down code by removing std::move (Anna Henningsen) [#18324](https://github.com/nodejs/node/pull/18324) +* [[`57865a9213`](https://github.com/nodejs/node/commit/57865a9213)] - **src**: use correct OOB check for IPv6 parsing (Anna Henningsen) [#17470](https://github.com/nodejs/node/pull/17470) +* [[`f306d3eb7a`](https://github.com/nodejs/node/commit/f306d3eb7a)] - **src**: make url host a proper C++ class (Anna Henningsen) [#17470](https://github.com/nodejs/node/pull/17470) +* [[`1976c7c7a5`](https://github.com/nodejs/node/commit/1976c7c7a5)] - **src**: move url internals into anonymous namespace (Anna Henningsen) [#17470](https://github.com/nodejs/node/pull/17470) +* [[`d66f469931`](https://github.com/nodejs/node/commit/d66f469931)] - **src**: minor cleanups to node_url.cc (Anna Henningsen) [#17470](https://github.com/nodejs/node/pull/17470) +* [[`979af518c1`](https://github.com/nodejs/node/commit/979af518c1)] - **src**: remove nonexistent method from header file (Anna Henningsen) [#17748](https://github.com/nodejs/node/pull/17748) +* [[`2268d00e38`](https://github.com/nodejs/node/commit/2268d00e38)] - **(SEMVER-MINOR)** **src**: add openssl-system-ca-path configure option (Daniel Bevenius) [#16790](https://github.com/nodejs/node/pull/16790) +* [[`a6d2384c9a`](https://github.com/nodejs/node/commit/a6d2384c9a)] - **src**: clean up MaybeStackBuffer (Timothy Gu) [#11464](https://github.com/nodejs/node/pull/11464) +* [[`9f3b4ad5bd`](https://github.com/nodejs/node/commit/9f3b4ad5bd)] - **src**: fix incorrect macro comment (Daniel Bevenius) [#12688](https://github.com/nodejs/node/pull/12688) +* [[`2b29cea1b4`](https://github.com/nodejs/node/commit/2b29cea1b4)] - **src**: guard bundled_ca/openssl_ca with HAVE_OPENSSL (Daniel Bevenius) [#12302](https://github.com/nodejs/node/pull/12302) +* [[`758dc81e8d`](https://github.com/nodejs/node/commit/758dc81e8d)] - **(SEMVER-MAJOR)** **src**: add --use-bundled-ca --use-openssl-ca check (Daniel Bevenius) [#12087](https://github.com/nodejs/node/pull/12087) +* [[`2d4fca2c41`](https://github.com/nodejs/node/commit/2d4fca2c41)] - **(SEMVER-MINOR)** **src**: add process.ppid (cjihrig) [#16839](https://github.com/nodejs/node/pull/16839) +* [[`b6ce918e0a`](https://github.com/nodejs/node/commit/b6ce918e0a)] - **stream**: fix disparity between buffer and the count (jlvivero) [#15661](https://github.com/nodejs/node/pull/15661) +* [[`f82065fbe1`](https://github.com/nodejs/node/commit/f82065fbe1)] - **test**: make test-cli-syntax engine agnostic (Rich Trott) [#16272](https://github.com/nodejs/node/pull/16272) +* [[`a4e2ced73b`](https://github.com/nodejs/node/commit/a4e2ced73b)] - **test**: decrease duration of test-cli-syntax (Evan Lucas) [#14187](https://github.com/nodejs/node/pull/14187) +* [[`734ce678f4`](https://github.com/nodejs/node/commit/734ce678f4)] - **test**: use valid authentication tag length (Tobias Nießen) [#17566](https://github.com/nodejs/node/pull/17566) +* [[`694828df0e`](https://github.com/nodejs/node/commit/694828df0e)] - **test**: mark test-inspector-stop-profile-after-done flaky (Myles Borins) [#18491](https://github.com/nodejs/node/pull/18491) +* [[`5668403ddb`](https://github.com/nodejs/node/commit/5668403ddb)] - **test**: improve flaky test-listen-fd-ebadf.js (Rich Trott) [#17797](https://github.com/nodejs/node/pull/17797) +* [[`fce10f722d`](https://github.com/nodejs/node/commit/fce10f722d)] - **test**: fix test-tls-server-verify.js on Windows CI (Rich Trott) [#18382](https://github.com/nodejs/node/pull/18382) +* [[`4473c6c807`](https://github.com/nodejs/node/commit/4473c6c807)] - **test**: fix flaky test-http-pipeline-flood (Anatoli Papirovski) [#17955](https://github.com/nodejs/node/pull/17955) +* [[`001b67296e`](https://github.com/nodejs/node/commit/001b67296e)] - **test**: rename regression tests (Tobias Nießen) [#17948](https://github.com/nodejs/node/pull/17948) +* [[`0c3f23ef59`](https://github.com/nodejs/node/commit/0c3f23ef59)] - **test**: fix flaky test-pipe-unref (Anatoli Papirovski) [#17950](https://github.com/nodejs/node/pull/17950) +* [[`9e760285de`](https://github.com/nodejs/node/commit/9e760285de)] - **test**: fix crypto test case to use correct encoding (Tobias Nießen) [#17956](https://github.com/nodejs/node/pull/17956) +* [[`1c4aa61388`](https://github.com/nodejs/node/commit/1c4aa61388)] - **test**: simplify test-buffer-slice.js (Weijia Wang) [#17962](https://github.com/nodejs/node/pull/17962) +* [[`2c554a9d2b`](https://github.com/nodejs/node/commit/2c554a9d2b)] - **test**: improve to use template string (sreepurnajasti) [#17895](https://github.com/nodejs/node/pull/17895) +* [[`8c1f41fc11`](https://github.com/nodejs/node/commit/8c1f41fc11)] - **test**: make test-tls-invoke-queued use public API (Anna Henningsen) [#17864](https://github.com/nodejs/node/pull/17864) +* [[`b3e625d67a`](https://github.com/nodejs/node/commit/b3e625d67a)] - **test**: refactor test-tls-securepair-fiftharg (Anna Henningsen) [#17836](https://github.com/nodejs/node/pull/17836) +* [[`038e52627f`](https://github.com/nodejs/node/commit/038e52627f)] - **test**: remove undefined function (Rich Trott) [#17845](https://github.com/nodejs/node/pull/17845) +* [[`5314754685`](https://github.com/nodejs/node/commit/5314754685)] - **test**: use common module API in test-child-process-exec-stdout-stderr-data-string (sreepurnajasti) [#17751](https://github.com/nodejs/node/pull/17751) +* [[`f291bc1d98`](https://github.com/nodejs/node/commit/f291bc1d98)] - **test**: refactor test-repl-definecommand (Rich Trott) [#17795](https://github.com/nodejs/node/pull/17795) +* [[`cb7854354f`](https://github.com/nodejs/node/commit/cb7854354f)] - **test**: change callback function to arrow function (rt33) [#17734](https://github.com/nodejs/node/pull/17734) +* [[`bdb535c731`](https://github.com/nodejs/node/commit/bdb535c731)] - **test**: Use countdown in test file (sreepurnajasti) [#17646](https://github.com/nodejs/node/pull/17646) +* [[`31c5db6c03`](https://github.com/nodejs/node/commit/31c5db6c03)] - **test**: update test-http-content-length to use countdown (Bamieh) [#17201](https://github.com/nodejs/node/pull/17201) +* [[`cc03470b82`](https://github.com/nodejs/node/commit/cc03470b82)] - **test**: change callback function to arrow function (routerman) [#17697](https://github.com/nodejs/node/pull/17697) +* [[`81e6569990`](https://github.com/nodejs/node/commit/81e6569990)] - **test**: change callback function to arrow function (you12724) [#17698](https://github.com/nodejs/node/pull/17698) +* [[`2d77241f33`](https://github.com/nodejs/node/commit/2d77241f33)] - **test**: change callback function to arrow function (Shinya Kanamaru) [#17699](https://github.com/nodejs/node/pull/17699) +* [[`af3e074249`](https://github.com/nodejs/node/commit/af3e074249)] - **(SEMVER-MINOR)** **test**: add `makeDuplexPair()` helper (Anna Henningsen) [#16269](https://github.com/nodejs/node/pull/16269) +* [[`fb0bd8a584`](https://github.com/nodejs/node/commit/fb0bd8a584)] - **test**: fix flaky test-child-process-pass-fd (Rich Trott) [#17598](https://github.com/nodejs/node/pull/17598) +* [[`b3b245665e`](https://github.com/nodejs/node/commit/b3b245665e)] - **test**: add test description to fs.readFile tests (Jamie Davis) [#17610](https://github.com/nodejs/node/pull/17610) +* [[`5f7944842a`](https://github.com/nodejs/node/commit/5f7944842a)] - **test**: fix truncation of argv (Daniel Bevenius) [#12110](https://github.com/nodejs/node/pull/12110) +* [[`699c6638c3`](https://github.com/nodejs/node/commit/699c6638c3)] - **test**: add common.hasIntl (James M Snell) [#9246](https://github.com/nodejs/node/pull/9246) +* [[`365dba2195`](https://github.com/nodejs/node/commit/365dba2195)] - **test**: fix flaky test-crypto-classes.js (Bryan English) [#15662](https://github.com/nodejs/node/pull/15662) +* [[`d29a6202e7`](https://github.com/nodejs/node/commit/d29a6202e7)] - **(SEMVER-MINOR)** **test**: crypto createClass instanceof Class (Bryan English) [#8188](https://github.com/nodejs/node/pull/8188) +* [[`7b801b5f83`](https://github.com/nodejs/node/commit/7b801b5f83)] - **test**: don't skip when common.mustCall() is pending (cjihrig) [#15421](https://github.com/nodejs/node/pull/15421) +* [[`4f6dd9649f`](https://github.com/nodejs/node/commit/4f6dd9649f)] - **test,doc**: do not indicate that non-functions "return" values (Rich Trott) [#17267](https://github.com/nodejs/node/pull/17267) +* [[`a08925dcbd`](https://github.com/nodejs/node/commit/a08925dcbd)] - **tls**: comment about old-style errors (xortiz) [#17759](https://github.com/nodejs/node/pull/17759) +* [[`56e1586608`](https://github.com/nodejs/node/commit/56e1586608)] - **tls**: unconsume stream on destroy (Anna Henningsen) [#17478](https://github.com/nodejs/node/pull/17478) +* [[`00b279087e`](https://github.com/nodejs/node/commit/00b279087e)] - **(SEMVER-MINOR)** **tls**: accept `lookup` option for `tls.connect()` (Fedor Indutny) [#12839](https://github.com/nodejs/node/pull/12839) +* [[`521dc2511f`](https://github.com/nodejs/node/commit/521dc2511f)] - **tls**: properly track writeQueueSize during writes (Anatoli Papirovski) [#15791](https://github.com/nodejs/node/pull/15791) +* [[`51bfd32922`](https://github.com/nodejs/node/commit/51bfd32922)] - **tools**: do not override V8's gitignore (Yang Guo) [#18010](https://github.com/nodejs/node/pull/18010) +* [[`32f528a92e`](https://github.com/nodejs/node/commit/32f528a92e)] - **tools**: fix AttributeError: __exit__ on Python 2.6 (Dmitriy Kasyanov) [#17663](https://github.com/nodejs/node/pull/17663) +* [[`6187aec242`](https://github.com/nodejs/node/commit/6187aec242)] - **tools**: autofixer for lowercase-name-for-primitive (Shobhit Chittora) [#17715](https://github.com/nodejs/node/pull/17715) +* [[`928b7c87cd`](https://github.com/nodejs/node/commit/928b7c87cd)] - **tools**: simplify lowercase-name-for-primitive rule (cjihrig) [#17653](https://github.com/nodejs/node/pull/17653) +* [[`7821a4c899`](https://github.com/nodejs/node/commit/7821a4c899)] - **tools**: add lowercase-name-for-primitive eslint rule (Weijia Wang) [#17568](https://github.com/nodejs/node/pull/17568) +* [[`1d706026a7`](https://github.com/nodejs/node/commit/1d706026a7)] - **tools**: make doc tool a bit more readable (Tobias Nießen) [#17125](https://github.com/nodejs/node/pull/17125) +* [[`b8a5d6dbbc`](https://github.com/nodejs/node/commit/b8a5d6dbbc)] - **tools**: remove useless function declaration (Tobias Nießen) [#17125](https://github.com/nodejs/node/pull/17125) +* [[`18803bc409`](https://github.com/nodejs/node/commit/18803bc409)] - **(SEMVER-MINOR)** **tools, build**: refactor macOS installer (JP Wesselink) [#15179](https://github.com/nodejs/node/pull/15179) +* [[`24def19417`](https://github.com/nodejs/node/commit/24def19417)] - **(SEMVER-MINOR)** **url**: adding WHATWG URL support (James M Snell) [#7448](https://github.com/nodejs/node/pull/7448) +* [[`60b10f0896`](https://github.com/nodejs/node/commit/60b10f0896)] - **url**: update IDNA handling (Timothy Gu) [#13362](https://github.com/nodejs/node/pull/13362) +* [[`7af1ad0ec1`](https://github.com/nodejs/node/commit/7af1ad0ec1)] - **(SEMVER-MINOR)** **util**: add %i and %f formatting specifiers (Roman Reiss) [#10308](https://github.com/nodejs/node/pull/10308) + ## 2018-01-02, Version 6.12.3 'Boron' (LTS), @MylesBorins diff --git a/doc/guides/backporting-to-release-lines.md b/doc/guides/backporting-to-release-lines.md index 07da834e14d0ce..751df9137d2fc4 100644 --- a/doc/guides/backporting-to-release-lines.md +++ b/doc/guides/backporting-to-release-lines.md @@ -76,7 +76,7 @@ hint: and commit the result with 'git commit' 2. Include the backport target in the pull request title in the following format — `[v6.x backport] `. Example: `[v6.x backport] process: improve performance of nextTick` - 3. Check the checkbox labelled "Allow edits from maintainers". + 3. Check the checkbox labeled "Allow edits from maintainers". 4. In the description add a reference to the original PR 5. Run a [`node-test-pull-request`][] CI job (with `REBASE_ONTO` set to the default ``) diff --git a/doc/guides/writing-tests.md b/doc/guides/writing-tests.md index 64181586b539c4..aac098640aec9e 100644 --- a/doc/guides/writing-tests.md +++ b/doc/guides/writing-tests.md @@ -133,11 +133,15 @@ platforms. ### The *common* API -Make use of the helpers from the `common` module as much as possible. +Make use of the helpers from the `common` module as much as possible. Please refer +to the [common file documentation](https://github.com/nodejs/node/tree/master/test/common) +for the full details of the helpers. -One interesting case is `common.mustCall`. The use of `common.mustCall` may -avoid the use of extra variables and the corresponding assertions. Let's explain -this with a real test from the test suite. +#### common.mustCall + +One interesting case is `common.mustCall`. The use of `common.mustCall` may avoid +the use of extra variables and the corresponding assertions. Let's explain this +with a real test from the test suite. ```javascript 'use strict'; @@ -189,6 +193,23 @@ const server = http.createServer(common.mustCall(function(req, res) { }); ``` +#### Countdown Module + +The common [Countdown module](https://github.com/nodejs/node/tree/master/test/common#countdown-module) provides a simple countdown mechanism for tests that +require a particular action to be taken after a given number of completed tasks +(for instance, shutting down an HTTP server after a specific number of requests). + +```javascript +const Countdown = require('../common/countdown'); + +const countdown = new Countdown(2, function() { + console.log('.'); +}); + +countdown.dec(); +countdown.dec(); // The countdown callback will be invoked now. +``` + ### Flags @@ -212,8 +233,8 @@ const freelist = require('internal/freelist'); When writing assertions, prefer the strict versions: -* `assert.strictEqual()` over `assert.equal()` -* `assert.deepStrictEqual()` over `assert.deepEqual()` +- `assert.strictEqual()` over `assert.equal()` +- `assert.deepStrictEqual()` over `assert.deepEqual()` When using `assert.throws()`, if possible, provide the full error message: @@ -237,9 +258,9 @@ available features in each release. For example: -* `let` and `const` over `var` -* Template literals over string concatenation -* Arrow functions when appropriate +- `let` and `const` over `var` +- Template literals over string concatenation +- Arrow functions when appropriate ## Naming Test Files diff --git a/doc/onboarding.md b/doc/onboarding.md index 09cbb432a2bb85..af6407c4164455 100644 --- a/doc/onboarding.md +++ b/doc/onboarding.md @@ -38,6 +38,10 @@ onboarding session. * Branches in the nodejs/node repository are only for release lines * [See "Updating Node.js from Upstream"](./onboarding-extras.md#updating-nodejs-from-upstream) * Make a new branch for each PR you submit. + * Membership: Consider making your membership in the Node.js GitHub organization + public. This makes it easier to identify Collaborators. Instructions on how to + do that are available at + [Publicizing or hiding organization membership](https://help.github.com/articles/publicizing-or-hiding-organization-membership/). * Notifications: * Use [https://github.com/notifications](https://github.com/notifications) or set up email @@ -107,7 +111,7 @@ onboarding session. (especially if it just has nits left). * Approving a change * Collaborators indicate that they have reviewed and approve of the - the changes in a pull request using Github’s approval interface + changes in a pull request using Github’s approval interface * Some people like to comment `LGTM` (“Looks Good To Me”) * You have the authority to approve any other collaborator’s work. * You cannot approve your own pull requests. @@ -178,6 +182,6 @@ onboarding session. accommodation, transportation, visa fees etc. if needed. Check out the [summit](https://github.com/nodejs/summit) repository for details. -[Code of Conduct]: https://github.com/nodejs/TSC/blob/master/CODE_OF_CONDUCT.md +[Code of Conduct]: https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md [`core-validate-commit`]: https://github.com/evanlucas/core-validate-commit [`node-core-utils`]: https://github.com/nodejs/node-core-utils diff --git a/doc/releases.md b/doc/releases.md index 339ee291508554..92054e4c3a6e0f 100644 --- a/doc/releases.md +++ b/doc/releases.md @@ -157,6 +157,15 @@ were first added in this version. The relevant commits should already include `sed -i "s/REPLACEME/$VERSION/g" doc/api/*.md` or `perl -pi -e "s/REPLACEME/$VERSION/g" doc/api/*.md`. +*Note*: `$VERSION` should be prefixed with a `v` + +If this release includes any new deprecations it is necessary to ensure that +those are assigned a proper static deprecation code. These are listed in the +docs (see `doc/api/deprecations.md`) and in the source as `DEP00XX`. The code +must be assigned a number (e.g. `DEP0012`). Note that this assignment should +occur when the PR is landed, but a check will be made when the release built +is run. + ### 4. Create Release Commit The `CHANGELOG.md`, `doc/changelogs/CHANGELOG_*.md`, `src/node_version.h`, and @@ -243,8 +252,8 @@ $ git push On release proposal branch, edit `src/node_version.h` again and: -* Increment `NODE_PATCH_VERSION` by one -* Change `NODE_VERSION_IS_RELEASE` back to `0` +- Increment `NODE_PATCH_VERSION` by one +- Change `NODE_VERSION_IS_RELEASE` back to `0` Commit this change with the following commit message format: diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml index 24f54e682636ee..aaebadca125814 100644 --- a/lib/.eslintrc.yaml +++ b/lib/.eslintrc.yaml @@ -3,3 +3,4 @@ rules: require-buffer: error buffer-constructor: error no-let-in-for-declaration: error + lowercase-name-for-primitive: error diff --git a/lib/_http_agent.js b/lib/_http_agent.js index 8b79d14ff12166..c92242e38bf8c1 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -67,14 +67,15 @@ function Agent(options) { if (count > self.maxSockets || freeLen >= self.maxFreeSockets) { socket.destroy(); - } else { + } else if (self.keepSocketAlive(socket)) { freeSockets = freeSockets || []; self.freeSockets[name] = freeSockets; - socket.setKeepAlive(true, self.keepAliveMsecs); - socket.unref(); socket._httpMessage = null; self.removeSocket(socket, options); freeSockets.push(socket); + } else { + // Implementation doesn't want to keep socket alive + socket.destroy(); } } else { socket.destroy(); @@ -142,13 +143,12 @@ Agent.prototype.addRequest = function(req, options) { if (freeLen) { // we have a free socket, so use that. var socket = this.freeSockets[name].shift(); - debug('have free socket'); // don't leak if (!this.freeSockets[name].length) delete this.freeSockets[name]; - socket.ref(); + this.reuseSocket(socket, req); req.onSocket(socket); this.sockets[name].push(socket); } else if (sockLen < this.maxSockets) { @@ -279,7 +279,19 @@ Agent.prototype.removeSocket = function removeSocket(s, options) { } }; -Agent.prototype.destroy = function() { +Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) { + socket.setKeepAlive(true, this.keepAliveMsecs); + socket.unref(); + + return true; +}; + +Agent.prototype.reuseSocket = function reuseSocket(socket, req) { + debug('have free socket'); + socket.ref(); +}; + +Agent.prototype.destroy = function destroy() { var sets = [this.freeSockets, this.sockets]; for (var s = 0; s < sets.length; s++) { var set = sets[s]; diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index 8251e8e219a519..250bd65f872a78 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -429,6 +429,7 @@ function clearBuffer(stream, state) { } else { state.corkedRequestsFree = new CorkedRequest(state); } + state.bufferedRequestCount = 0; } else { // Slow case, write chunks one-by-one while (entry) { @@ -439,6 +440,7 @@ function clearBuffer(stream, state) { doWrite(stream, state, false, len, chunk, encoding, cb); entry = entry.next; + state.bufferedRequestCount--; // if we didn't call the onwrite immediately, then // it means that we need to wait until it does. // also, that means that the chunk and cb are currently @@ -452,7 +454,6 @@ function clearBuffer(stream, state) { state.lastBufferedRequest = null; } - state.bufferedRequestCount = 0; state.bufferedRequest = entry; state.bufferProcessing = false; } diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index 87ccb081eea8ef..363589f0533110 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -608,6 +608,8 @@ function onhandshakestart() { // state machine and OpenSSL is not re-entrant. We cannot allow the user's // callback to destroy the connection right now, it would crash and burn. setImmediate(function() { + // Old-style error is not being migrated to the newer style + // internal/errors.js because _tls_legacy.js has been deprecated. var err = new Error('TLS session renegotiation attack detected'); if (self.cleartext) self.cleartext.emit('error', err); }); diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index c435d792e0a2cc..14ddac0544d4a4 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -401,9 +401,8 @@ TLSSocket.prototype._init = function(socket, wrap) { var ssl = this._handle; // lib/net.js expect this value to be non-zero if write hasn't been flushed - // immediately - // TODO(indutny): revise this solution, it might be 1 before handshake and - // represent real writeQueueSize during regular writes. + // immediately. After the handshake is done this will represent the actual + // write queue size ssl.writeQueueSize = 1; this.server = options.server; @@ -1058,7 +1057,8 @@ exports.connect = function(/* [port,] [host,] [options,] [cb] */) { port: options.port, host: options.host, family: options.family, - localAddress: options.localAddress + localAddress: options.localAddress, + lookup: options.lookup }; } socket.connect(connect_opt, function() { diff --git a/lib/console.js b/lib/console.js index 2359cb36e011d0..00f1ee51672586 100644 --- a/lib/console.js +++ b/lib/console.js @@ -1,6 +1,7 @@ 'use strict'; const util = require('util'); +const kCounts = Symbol('counts'); function Console(stdout, stderr) { if (!(this instanceof Console)) { @@ -27,6 +28,8 @@ function Console(stdout, stderr) { prop.value = new Map(); Object.defineProperty(this, '_times', prop); + this[kCounts] = new Map(); + // bind the prototype functions to this Console instance var keys = Object.keys(Console.prototype); for (var v = 0; v < keys.length; v++) { @@ -96,6 +99,42 @@ Console.prototype.assert = function(expression, ...args) { } }; +// Defined by: https://console.spec.whatwg.org/#clear +Console.prototype.clear = function clear() { + // It only makes sense to clear if _stdout is a TTY. + // Otherwise, do nothing. + if (this._stdout.isTTY) { + // The require is here intentionally to avoid readline being + // required too early when console is first loaded. + const { cursorTo, clearScreenDown } = require('readline'); + cursorTo(this._stdout, 0, 0); + clearScreenDown(this._stdout); + } +}; + +// Defined by: https://console.spec.whatwg.org/#count +Console.prototype.count = function count(label = 'default') { + // Ensures that label is a string, and only things that can be + // coerced to strings. e.g. Symbol is not allowed + label = `${label}`; + const counts = this[kCounts]; + let count = counts.get(label); + if (count === undefined) + count = 1; + else + count++; + counts.set(label, count); + this.log(`${label}: ${count}`); +}; + +// Not yet defined by the https://console.spec.whatwg.org, but +// proposed to be added and currently implemented by Edge. Having +// the ability to reset counters is important to help prevent +// the counter from being a memory leak. +Console.prototype.countReset = function countReset(label = 'default') { + const counts = this[kCounts]; + counts.delete(`${label}`); +}; module.exports = new Console(process.stdout, process.stderr); module.exports.Console = Console; diff --git a/lib/crypto.js b/lib/crypto.js index f4eeb7baa3aad5..abc02b8558f5c7 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -19,9 +19,11 @@ const setFipsCrypto = binding.setFipsCrypto; const timingSafeEqual = binding.timingSafeEqual; const Buffer = require('buffer').Buffer; +const kBufferMaxLength = require('buffer').kMaxLength; const stream = require('stream'); const util = require('util'); const LazyTransform = require('internal/streams/lazy_transform'); +const isArrayBufferView = internalUtil.isArrayBufferView; const DH_GENERATOR = 2; @@ -44,7 +46,6 @@ function toBuf(str, encoding) { } exports._toBuf = toBuf; - const assert = require('assert'); const StringDecoder = require('string_decoder').StringDecoder; @@ -534,17 +535,16 @@ DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { }; +exports.createECDH = exports.ECDH = ECDH; function ECDH(curve) { + if (!(this instanceof ECDH)) + return new ECDH(curve); if (typeof curve !== 'string') throw new TypeError('"curve" argument should be a string'); this._handle = new binding.ECDH(curve); } -exports.createECDH = function createECDH(curve) { - return new ECDH(curve); -}; - ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; @@ -677,6 +677,74 @@ exports.setEngine = function setEngine(id, flags) { return binding.setEngine(id, flags); }; +const kMaxUint32 = Math.pow(2, 32) - 1; + +function randomFillSync(buf, offset = 0, size) { + if (!isArrayBufferView(buf)) { + throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); + } + + assertOffset(offset, buf.length); + + if (size === undefined) size = buf.length - offset; + + assertSize(size, offset, buf.length); + + return binding.randomFill(buf, offset, size); +} +exports.randomFillSync = randomFillSync; + +function randomFill(buf, offset, size, cb) { + if (!isArrayBufferView(buf)) { + throw new TypeError('"buf" argument must be a Buffer or Uint8Array'); + } + + if (typeof offset === 'function') { + cb = offset; + offset = 0; + size = buf.length; + } else if (typeof size === 'function') { + cb = size; + size = buf.length - offset; + } else if (typeof cb !== 'function') { + throw new TypeError('"cb" argument must be a function'); + } + + assertOffset(offset, buf.length); + assertSize(size, offset, buf.length); + + return binding.randomFill(buf, offset, size, cb); +} +exports.randomFill = randomFill; + +function assertOffset(offset, length) { + if (typeof offset !== 'number' || offset !== offset) { + throw new TypeError('offset must be a number'); + } + + if (offset > kMaxUint32 || offset < 0) { + throw new TypeError('offset must be a uint32'); + } + + if (offset > kBufferMaxLength || offset > length) { + throw new RangeError('offset out of range'); + } +} + +function assertSize(size, offset, length) { + if (typeof size !== 'number' || size !== size) { + throw new TypeError('size must be a number'); + } + + if (size > kMaxUint32 || size < 0) { + throw new TypeError('size must be a uint32'); + } + + if (size + offset > length || size > kBufferMaxLength) { + throw new RangeError('buffer too small'); + } +} + exports.randomBytes = exports.pseudoRandomBytes = randomBytes; exports.rng = exports.prng = randomBytes; diff --git a/lib/dgram.js b/lib/dgram.js index d38687ab6a7aa8..f4cabdbc5d1b95 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -493,6 +493,19 @@ Socket.prototype.setMulticastLoopback = function(arg) { }; +Socket.prototype.setMulticastInterface = function(interfaceAddress) { + this._healthCheck(); + + if (typeof interfaceAddress !== 'string') { + throw new TypeError('"interfaceAddress" argument must be a string'); + } + + const err = this._handle.setMulticastInterface(interfaceAddress); + if (err) { + throw errnoException(err, 'setMulticastInterface'); + } +}; + Socket.prototype.addMembership = function(multicastAddress, interfaceAddress) { this._healthCheck(); diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 310dc9dd029375..4b5a500adc598e 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -62,6 +62,10 @@ _process.setupRawDebug(); + // Ensure setURLConstructor() is called before the native + // URL::ToObject() method is used. + NativeModule.require('internal/url'); + Object.defineProperty(process, 'argv0', { enumerable: true, configurable: false, diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index d976b9d38a9050..54a3ff41ec40c1 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -1,5 +1,7 @@ 'use strict'; +const { safeToString } = process.binding('util'); + const promiseRejectEvent = process._promiseRejectEvent; const hasBeenNotifiedProperty = new WeakMap(); const promiseToGuidProperty = new WeakMap(); @@ -64,12 +66,17 @@ function setupPromises(scheduleMicrotasks) { hasBeenNotifiedProperty.set(promise, true); const uid = promiseToGuidProperty.get(promise); if (!process.emit('unhandledRejection', reason, promise)) { - const warning = new Error('Unhandled promise rejection ' + - `(rejection id: ${uid}): ${reason}`); + const warning = new Error( + `Unhandled promise rejection (rejection id: ${uid}): ` + + safeToString(reason)); warning.name = 'UnhandledPromiseRejectionWarning'; warning.id = uid; - if (reason instanceof Error) { - warning.stack = reason.stack; + try { + if (reason instanceof Error) { + warning.stack = reason.stack; + } + } catch (err) { + // ignored } process.emitWarning(warning); } else { diff --git a/lib/internal/querystring.js b/lib/internal/querystring.js new file mode 100644 index 00000000000000..d1684418097100 --- /dev/null +++ b/lib/internal/querystring.js @@ -0,0 +1,29 @@ +'use strict'; + +const hexTable = new Array(256); +for (var i = 0; i < 256; ++i) + hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); + +const isHexTable = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ... + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // ... 256 +]; + +module.exports = { + hexTable, + isHexTable +}; diff --git a/lib/internal/url.js b/lib/internal/url.js new file mode 100644 index 00000000000000..91a03ec99a87fd --- /dev/null +++ b/lib/internal/url.js @@ -0,0 +1,1428 @@ +'use strict'; + +const util = require('util'); +const { + hexTable, + isHexTable +} = require('internal/querystring'); + +const { getConstructorOf } = require('internal/util'); +const querystring = require('querystring'); + +const { platform } = process; +const isWindows = platform === 'win32'; + +const { + domainToASCII: _domainToASCII, + domainToUnicode: _domainToUnicode, + encodeAuth, + toUSVString: _toUSVString, + parse: _parse, + setURLConstructor, + URL_FLAGS_CANNOT_BE_BASE, + URL_FLAGS_HAS_FRAGMENT, + URL_FLAGS_HAS_HOST, + URL_FLAGS_HAS_PASSWORD, + URL_FLAGS_HAS_PATH, + URL_FLAGS_HAS_QUERY, + URL_FLAGS_HAS_USERNAME, + URL_FLAGS_SPECIAL, + kFragment, + kHost, + kHostname, + kPathStart, + kPort, + kQuery, + kSchemeStart +} = process.binding('url'); + +const context = Symbol('context'); +const cannotBeBase = Symbol('cannot-be-base'); +const cannotHaveUsernamePasswordPort = + Symbol('cannot-have-username-password-port'); +const special = Symbol('special'); +const searchParams = Symbol('query'); +const kFormat = Symbol('format'); + +// https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object +const IteratorPrototype = Object.getPrototypeOf( + Object.getPrototypeOf([][Symbol.iterator]()) +); + +const unpairedSurrogateRe = + /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/; +function toUSVString(val) { + const str = `${val}`; + // As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are + // slower than `unpairedSurrogateRe.exec()`. + const match = unpairedSurrogateRe.exec(str); + if (!match) + return str; + return _toUSVString(str, match.index); +} + +// Refs: https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque +const kOpaqueOrigin = 'null'; + +// Refs: https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin +function serializeTupleOrigin(scheme, host, port) { + return `${scheme}//${host}${port === null ? '' : `:${port}`}`; +} + +// This class provides the internal state of a URL object. An instance of this +// class is stored in every URL object and is accessed internally by setters +// and getters. It roughly corresponds to the concept of a URL record in the +// URL Standard, with a few differences. It is also the object transported to +// the C++ binding. +// Refs: https://url.spec.whatwg.org/#concept-url +class URLContext { + constructor() { + this.flags = 0; + this.scheme = ':'; + this.username = ''; + this.password = ''; + this.host = null; + this.port = null; + this.path = []; + this.query = null; + this.fragment = null; + } +} + +class URLSearchParams { + // URL Standard says the default value is '', but as undefined and '' have + // the same result, undefined is used to prevent unnecessary parsing. + // Default parameter is necessary to keep URLSearchParams.length === 0 in + // accordance with Web IDL spec. + constructor(init = undefined) { + if (init === null || init === undefined) { + this[searchParams] = []; + } else if ((typeof init === 'object' && init !== null) || + typeof init === 'function') { + const method = init[Symbol.iterator]; + if (method === this[Symbol.iterator]) { + // While the spec does not have this branch, we can use it as a + // shortcut to avoid having to go through the costly generic iterator. + const childParams = init[searchParams]; + this[searchParams] = childParams.slice(); + } else if (method !== null && method !== undefined) { + if (typeof method !== 'function') { + throw new TypeError('Query pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if ((typeof pair !== 'object' && typeof pair !== 'function') || + pair === null || + typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError( + 'Each query pair must be an iterable [name, value] tuple'); + } + const convertedPair = []; + for (const element of pair) + convertedPair.push(toUSVString(element)); + pairs.push(convertedPair); + } + + this[searchParams] = []; + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError( + 'Each query pair must be an iterable [name, value] tuple'); + } + this[searchParams].push(pair[0], pair[1]); + } + } else { + // record + // Need to use reflection APIs for full spec compliance. + this[searchParams] = []; + const keys = Reflect.ownKeys(init); + for (var i = 0; i < keys.length; i++) { + const key = keys[i]; + const desc = Reflect.getOwnPropertyDescriptor(init, key); + if (desc !== undefined && desc.enumerable) { + const typedKey = toUSVString(key); + const typedValue = toUSVString(init[key]); + this[searchParams].push(typedKey, typedValue); + } + } + } + } else { + // USVString + init = toUSVString(init); + if (init[0] === '?') init = init.slice(1); + initSearchParams(this, init); + } + + // "associated url object" + this[context] = null; + } + + [util.inspect.custom](recurseTimes, ctx) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + + if (typeof recurseTimes === 'number' && recurseTimes < 0) + return ctx.stylize('[Object]', 'special'); + + var separator = ', '; + var innerOpts = util._extend({}, ctx); + if (recurseTimes !== null) { + innerOpts.depth = recurseTimes - 1; + } + var innerInspect = (v) => util.inspect(v, innerOpts); + + var list = this[searchParams]; + var output = []; + for (var i = 0; i < list.length; i += 2) + output.push(`${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`); + + var colorRe = /\u001b\[\d\d?m/g; + var length = output.reduce( + (prev, cur) => prev + cur.replace(colorRe, '').length + separator.length, + -separator.length + ); + if (length > ctx.breakLength) { + return `${this.constructor.name} {\n ${output.join(',\n ')} }`; + } else if (output.length) { + return `${this.constructor.name} { ${output.join(separator)} }`; + } else { + return `${this.constructor.name} {}`; + } + } +} + +function onParseComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + var ctx = this[context]; + ctx.flags = flags; + ctx.scheme = protocol; + ctx.username = (flags & URL_FLAGS_HAS_USERNAME) !== 0 ? username : ''; + ctx.password = (flags & URL_FLAGS_HAS_PASSWORD) !== 0 ? password : ''; + ctx.port = port; + ctx.path = (flags & URL_FLAGS_HAS_PATH) !== 0 ? path : []; + ctx.query = query; + ctx.fragment = fragment; + ctx.host = host; + if (!this[searchParams]) { // invoked from URL constructor + this[searchParams] = new URLSearchParams(); + this[searchParams][context] = this; + } + initSearchParams(this[searchParams], query); +} + +function onParseError(flags, input) { + const error = new TypeError(`Invalid URL: ${input}`); + error.input = input; + throw error; +} + +// Reused by URL constructor and URL#href setter. +function parse(url, input, base) { + const base_context = base ? base[context] : undefined; + url[context] = new URLContext(); + _parse(input.trim(), -1, base_context, undefined, + onParseComplete.bind(url), onParseError); +} + +function onParseProtocolComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + const ctx = this[context]; + if ((flags & URL_FLAGS_SPECIAL) !== 0) { + ctx.flags |= URL_FLAGS_SPECIAL; + } else { + ctx.flags &= ~URL_FLAGS_SPECIAL; + } + ctx.scheme = protocol; + ctx.port = port; +} + +function onParseHostComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + const ctx = this[context]; + if ((flags & URL_FLAGS_HAS_HOST) !== 0) { + ctx.host = host; + ctx.flags |= URL_FLAGS_HAS_HOST; + } else { + ctx.host = null; + ctx.flags &= ~URL_FLAGS_HAS_HOST; + } + if (port !== null) + ctx.port = port; +} + +function onParseHostnameComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + const ctx = this[context]; + if ((flags & URL_FLAGS_HAS_HOST) !== 0) { + ctx.host = host; + ctx.flags |= URL_FLAGS_HAS_HOST; + } else { + ctx.host = null; + ctx.flags &= ~URL_FLAGS_HAS_HOST; + } +} + +function onParsePortComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + this[context].port = port; +} + +function onParsePathComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + const ctx = this[context]; + if ((flags & URL_FLAGS_HAS_PATH) !== 0) { + ctx.path = path; + ctx.flags |= URL_FLAGS_HAS_PATH; + } else { + ctx.path = []; + ctx.flags &= ~URL_FLAGS_HAS_PATH; + } + + // The C++ binding may set host to empty string. + if ((flags & URL_FLAGS_HAS_HOST) !== 0) { + ctx.host = host; + ctx.flags |= URL_FLAGS_HAS_HOST; + } +} + +function onParseSearchComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + this[context].query = query; +} + +function onParseHashComplete(flags, protocol, username, password, + host, port, path, query, fragment) { + this[context].fragment = fragment; +} + +class URL { + constructor(input, base) { + // toUSVString is not needed. + input = `${input}`; + if (base !== undefined && + (!base[searchParams] || !base[searchParams][searchParams])) { + base = new URL(base); + } + parse(this, input, base); + } + + get [special]() { + return (this[context].flags & URL_FLAGS_SPECIAL) !== 0; + } + + get [cannotBeBase]() { + return (this[context].flags & URL_FLAGS_CANNOT_BE_BASE) !== 0; + } + + // https://url.spec.whatwg.org/#cannot-have-a-username-password-port + get [cannotHaveUsernamePasswordPort]() { + const { host, scheme } = this[context]; + return ((host == null || host === '') || + this[cannotBeBase] || + scheme === 'file:'); + } + + [util.inspect.custom](depth, opts) { + if (this == null || + Object.getPrototypeOf(this[context]) !== URLContext.prototype) { + throw new TypeError('Value of "this" must be of type URL'); + } + + if (typeof depth === 'number' && depth < 0) + return opts.stylize('[Object]', 'special'); + + var ctor = getConstructorOf(this); + + var obj = Object.create({ + constructor: ctor === null ? URL : ctor + }); + + obj.href = this.href; + obj.origin = this.origin; + obj.protocol = this.protocol; + obj.username = this.username; + obj.password = this.password; + obj.host = this.host; + obj.hostname = this.hostname; + obj.port = this.port; + obj.pathname = this.pathname; + obj.search = this.search; + obj.searchParams = this.searchParams; + obj.hash = this.hash; + + if (opts.showHidden) { + obj.cannotBeBase = this[cannotBeBase]; + obj.special = this[special]; + obj[context] = this[context]; + } + + return util.inspect(obj, opts); + } +} + +Object.defineProperties(URL.prototype, { + [kFormat]: { + enumerable: false, + configurable: false, + // eslint-disable-next-line func-name-matching + value: function format(options) { + if (options && typeof options !== 'object') + throw new TypeError('The "options" argument must be of type Object'); + options = util._extend({ + fragment: true, + unicode: false, + search: true, + auth: true + }, options); + const ctx = this[context]; + var ret = ctx.scheme; + if (ctx.host !== null) { + ret += '//'; + const has_username = ctx.username !== ''; + const has_password = ctx.password !== ''; + if (options.auth && (has_username || has_password)) { + if (has_username) + ret += ctx.username; + if (has_password) + ret += `:${ctx.password}`; + ret += '@'; + } + ret += options.unicode ? + domainToUnicode(this.host) : this.host; + } else if (ctx.scheme === 'file:') { + ret += '//'; + } + if (this.pathname) + ret += this.pathname; + if (options.search && ctx.query !== null) + ret += `?${ctx.query}`; + if (options.fragment && ctx.fragment !== null) + ret += `#${ctx.fragment}`; + return ret; + } + }, + [Symbol.toStringTag]: { + configurable: true, + value: 'URL' + }, + toString: { + // https://heycam.github.io/webidl/#es-stringifier + writable: true, + enumerable: true, + configurable: true, + // eslint-disable-next-line func-name-matching + value: function toString() { + return this[kFormat]({}); + } + }, + href: { + enumerable: true, + configurable: true, + get() { + return this[kFormat]({}); + }, + set(input) { + // toUSVString is not needed. + input = `${input}`; + parse(this, input); + } + }, + origin: { // readonly + enumerable: true, + configurable: true, + get() { + // Refs: https://url.spec.whatwg.org/#concept-url-origin + const ctx = this[context]; + switch (ctx.scheme) { + case 'blob:': + if (ctx.path.length > 0) { + try { + return (new URL(ctx.path[0])).origin; + } catch (err) { + // fall through... do nothing + } + } + return kOpaqueOrigin; + case 'ftp:': + case 'gopher:': + case 'http:': + case 'https:': + case 'ws:': + case 'wss:': + return serializeTupleOrigin(ctx.scheme, ctx.host, ctx.port); + } + return kOpaqueOrigin; + } + }, + protocol: { + enumerable: true, + configurable: true, + get() { + return this[context].scheme; + }, + set(scheme) { + // toUSVString is not needed. + scheme = `${scheme}`; + if (scheme.length === 0) + return; + const ctx = this[context]; + if (ctx.scheme === 'file:' && + (ctx.host === '' || ctx.host === null)) { + return; + } + _parse(scheme, kSchemeStart, null, ctx, + onParseProtocolComplete.bind(this)); + } + }, + username: { + enumerable: true, + configurable: true, + get() { + return this[context].username; + }, + set(username) { + // toUSVString is not needed. + username = `${username}`; + if (this[cannotHaveUsernamePasswordPort]) + return; + const ctx = this[context]; + if (username === '') { + ctx.username = ''; + ctx.flags &= ~URL_FLAGS_HAS_USERNAME; + return; + } + ctx.username = encodeAuth(username); + ctx.flags |= URL_FLAGS_HAS_USERNAME; + } + }, + password: { + enumerable: true, + configurable: true, + get() { + return this[context].password; + }, + set(password) { + // toUSVString is not needed. + password = `${password}`; + if (this[cannotHaveUsernamePasswordPort]) + return; + const ctx = this[context]; + if (password === '') { + ctx.password = ''; + ctx.flags &= ~URL_FLAGS_HAS_PASSWORD; + return; + } + ctx.password = encodeAuth(password); + ctx.flags |= URL_FLAGS_HAS_PASSWORD; + } + }, + host: { + enumerable: true, + configurable: true, + get() { + const ctx = this[context]; + var ret = ctx.host || ''; + if (ctx.port !== null) + ret += `:${ctx.port}`; + return ret; + }, + set(host) { + const ctx = this[context]; + // toUSVString is not needed. + host = `${host}`; + if (this[cannotBeBase]) { + // Cannot set the host if cannot-be-base is set + return; + } + _parse(host, kHost, null, ctx, onParseHostComplete.bind(this)); + } + }, + hostname: { + enumerable: true, + configurable: true, + get() { + return this[context].host || ''; + }, + set(host) { + const ctx = this[context]; + // toUSVString is not needed. + host = `${host}`; + if (this[cannotBeBase]) { + // Cannot set the host if cannot-be-base is set + return; + } + _parse(host, kHostname, null, ctx, onParseHostnameComplete.bind(this)); + } + }, + port: { + enumerable: true, + configurable: true, + get() { + const port = this[context].port; + return port === null ? '' : String(port); + }, + set(port) { + // toUSVString is not needed. + port = `${port}`; + if (this[cannotHaveUsernamePasswordPort]) + return; + const ctx = this[context]; + if (port === '') { + ctx.port = null; + return; + } + _parse(port, kPort, null, ctx, onParsePortComplete.bind(this)); + } + }, + pathname: { + enumerable: true, + configurable: true, + get() { + const ctx = this[context]; + if (this[cannotBeBase]) + return ctx.path[0]; + if (ctx.path.length === 0) + return ''; + return `/${ctx.path.join('/')}`; + }, + set(path) { + // toUSVString is not needed. + path = `${path}`; + if (this[cannotBeBase]) + return; + _parse(path, kPathStart, null, this[context], + onParsePathComplete.bind(this)); + } + }, + search: { + enumerable: true, + configurable: true, + get() { + const { query } = this[context]; + if (query === null || query === '') + return ''; + return `?${query}`; + }, + set(search) { + const ctx = this[context]; + search = toUSVString(search); + if (search === '') { + ctx.query = null; + ctx.flags &= ~URL_FLAGS_HAS_QUERY; + } else { + if (search[0] === '?') search = search.slice(1); + ctx.query = ''; + ctx.flags |= URL_FLAGS_HAS_QUERY; + if (search) { + _parse(search, kQuery, null, ctx, onParseSearchComplete.bind(this)); + } + } + initSearchParams(this[searchParams], search); + } + }, + searchParams: { // readonly + enumerable: true, + configurable: true, + get() { + return this[searchParams]; + } + }, + hash: { + enumerable: true, + configurable: true, + get() { + const { fragment } = this[context]; + if (fragment === null || fragment === '') + return ''; + return `#${fragment}`; + }, + set(hash) { + const ctx = this[context]; + // toUSVString is not needed. + hash = `${hash}`; + if (!hash) { + ctx.fragment = null; + ctx.flags &= ~URL_FLAGS_HAS_FRAGMENT; + return; + } + if (hash[0] === '#') hash = hash.slice(1); + ctx.fragment = ''; + ctx.flags |= URL_FLAGS_HAS_FRAGMENT; + _parse(hash, kFragment, null, ctx, onParseHashComplete.bind(this)); + } + }, + toJSON: { + writable: true, + enumerable: true, + configurable: true, + // eslint-disable-next-line func-name-matching + value: function toJSON() { + return this[kFormat]({}); + } + } +}); + +function update(url, params) { + if (!url) + return; + + const ctx = url[context]; + const serializedParams = params.toString(); + if (serializedParams) { + ctx.query = serializedParams; + ctx.flags |= URL_FLAGS_HAS_QUERY; + } else { + ctx.query = null; + ctx.flags &= ~URL_FLAGS_HAS_QUERY; + } +} + +function initSearchParams(url, init) { + if (!init) { + url[searchParams] = []; + return; + } + url[searchParams] = parseParams(init); +} + +// application/x-www-form-urlencoded parser +// Ref: https://url.spec.whatwg.org/#concept-urlencoded-parser +function parseParams(qs) { + const out = []; + var pairStart = 0; + var lastPos = 0; + var seenSep = false; + var buf = ''; + var encoded = false; + var encodeCheck = 0; + var i; + for (i = 0; i < qs.length; ++i) { + const code = qs.charCodeAt(i); + + // Try matching key/value pair separator + if (code === 38/*&*/) { + if (pairStart === i) { + // We saw an empty substring between pair separators + lastPos = pairStart = i + 1; + continue; + } + + if (lastPos < i) + buf += qs.slice(lastPos, i); + if (encoded) + buf = querystring.unescape(buf); + out.push(buf); + + // If `buf` is the key, add an empty value. + if (!seenSep) + out.push(''); + + seenSep = false; + buf = ''; + encoded = false; + encodeCheck = 0; + lastPos = pairStart = i + 1; + continue; + } + + // Try matching key/value separator (e.g. '=') if we haven't already + if (!seenSep && code === 61/*=*/) { + // Key/value separator match! + if (lastPos < i) + buf += qs.slice(lastPos, i); + if (encoded) + buf = querystring.unescape(buf); + out.push(buf); + + seenSep = true; + buf = ''; + encoded = false; + encodeCheck = 0; + lastPos = i + 1; + continue; + } + + // Handle + and percent decoding. + if (code === 43/*+*/) { + if (lastPos < i) + buf += qs.slice(lastPos, i); + buf += ' '; + lastPos = i + 1; + } else if (!encoded) { + // Try to match an (valid) encoded byte (once) to minimize unnecessary + // calls to string decoding functions + if (code === 37/*%*/) { + encodeCheck = 1; + } else if (encodeCheck > 0) { + // eslint-disable-next-line no-extra-boolean-cast + if (!!isHexTable[code]) { + if (++encodeCheck === 3) + encoded = true; + } else { + encodeCheck = 0; + } + } + } + } + + // Deal with any leftover key or value data + + // There is a trailing &. No more processing is needed. + if (pairStart === i) + return out; + + if (lastPos < i) + buf += qs.slice(lastPos, i); + if (encoded) + buf = querystring.unescape(buf); + out.push(buf); + + // If `buf` is the key, add an empty value. + if (!seenSep) + out.push(''); + + return out; +} + +// Adapted from querystring's implementation. +// Ref: https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer +const noEscape = [ +//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 // 0x70 - 0x7F +]; + +// Special version of hexTable that uses `+` for U+0020 SPACE. +const paramHexTable = hexTable.slice(); +paramHexTable[0x20] = '+'; + +function escapeParam(str) { + const len = str.length; + if (len === 0) + return ''; + + var out = ''; + var lastPos = 0; + + for (var i = 0; i < len; i++) { + var c = str.charCodeAt(i); + + // ASCII + if (c < 0x80) { + if (noEscape[c] === 1) + continue; + if (lastPos < i) + out += str.slice(lastPos, i); + lastPos = i + 1; + out += paramHexTable[c]; + continue; + } + + if (lastPos < i) + out += str.slice(lastPos, i); + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1; + out += paramHexTable[0xC0 | (c >> 6)] + + paramHexTable[0x80 | (c & 0x3F)]; + continue; + } + if (c < 0xD800 || c >= 0xE000) { + lastPos = i + 1; + out += paramHexTable[0xE0 | (c >> 12)] + + paramHexTable[0x80 | ((c >> 6) & 0x3F)] + + paramHexTable[0x80 | (c & 0x3F)]; + continue; + } + // Surrogate pair + ++i; + var c2; + if (i < len) + c2 = str.charCodeAt(i) & 0x3FF; + else { + // This branch should never happen because all URLSearchParams entries + // should already be converted to USVString. But, included for + // completion's sake anyway. + c2 = 0; + } + lastPos = i + 1; + c = 0x10000 + (((c & 0x3FF) << 10) | c2); + out += paramHexTable[0xF0 | (c >> 18)] + + paramHexTable[0x80 | ((c >> 12) & 0x3F)] + + paramHexTable[0x80 | ((c >> 6) & 0x3F)] + + paramHexTable[0x80 | (c & 0x3F)]; + } + if (lastPos === 0) + return str; + if (lastPos < len) + return out + str.slice(lastPos); + return out; +} + +// application/x-www-form-urlencoded serializer +// Ref: https://url.spec.whatwg.org/#concept-urlencoded-serializer +function serializeParams(array) { + const len = array.length; + if (len === 0) + return ''; + + var output = `${escapeParam(array[0])}=${escapeParam(array[1])}`; + for (var i = 2; i < len; i += 2) + output += `&${escapeParam(array[i])}=${escapeParam(array[i + 1])}`; + return output; +} + +// Mainly to mitigate func-name-matching ESLint rule +function defineIDLClass(proto, classStr, obj) { + // https://heycam.github.io/webidl/#dfn-class-string + Object.defineProperty(proto, Symbol.toStringTag, { + writable: false, + enumerable: false, + configurable: true, + value: classStr + }); + + // https://heycam.github.io/webidl/#es-operations + for (const key of Object.keys(obj)) { + Object.defineProperty(proto, key, { + writable: true, + enumerable: true, + configurable: true, + value: obj[key] + }); + } + for (const key of Object.getOwnPropertySymbols(obj)) { + Object.defineProperty(proto, key, { + writable: true, + enumerable: false, + configurable: true, + value: obj[key] + }); + } +} + +// for merge sort +function merge(out, start, mid, end, lBuffer, rBuffer) { + const sizeLeft = mid - start; + const sizeRight = end - mid; + var l, r, o; + + for (l = 0; l < sizeLeft; l++) + lBuffer[l] = out[start + l]; + for (r = 0; r < sizeRight; r++) + rBuffer[r] = out[mid + r]; + + l = 0; + r = 0; + o = start; + while (l < sizeLeft && r < sizeRight) { + if (lBuffer[l] <= rBuffer[r]) { + out[o++] = lBuffer[l++]; + out[o++] = lBuffer[l++]; + } else { + out[o++] = rBuffer[r++]; + out[o++] = rBuffer[r++]; + } + } + while (l < sizeLeft) + out[o++] = lBuffer[l++]; + while (r < sizeRight) + out[o++] = rBuffer[r++]; +} + +defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { + append(name, value) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 2) { + throw new TypeError('The "name" and "value" arguments must be specified'); + } + + name = toUSVString(name); + value = toUSVString(value); + this[searchParams].push(name, value); + update(this[context], this); + }, + + delete(name) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The "name" argument must be specified'); + } + + const list = this[searchParams]; + name = toUSVString(name); + for (var i = 0; i < list.length;) { + const cur = list[i]; + if (cur === name) { + list.splice(i, 2); + } else { + i += 2; + } + } + update(this[context], this); + }, + + get(name) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The "name" argument must be specified'); + } + + const list = this[searchParams]; + name = toUSVString(name); + for (var i = 0; i < list.length; i += 2) { + if (list[i] === name) { + return list[i + 1]; + } + } + return null; + }, + + getAll(name) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The "name" argument must be specified'); + } + + const list = this[searchParams]; + const values = []; + name = toUSVString(name); + for (var i = 0; i < list.length; i += 2) { + if (list[i] === name) { + values.push(list[i + 1]); + } + } + return values; + }, + + has(name) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The "name" argument must be specified'); + } + + const list = this[searchParams]; + name = toUSVString(name); + for (var i = 0; i < list.length; i += 2) { + if (list[i] === name) { + return true; + } + } + return false; + }, + + set(name, value) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (arguments.length < 2) { + throw new TypeError('The "name" and "value" arguments must be specified'); + } + + const list = this[searchParams]; + name = toUSVString(name); + value = toUSVString(value); + + // If there are any name-value pairs whose name is `name`, in `list`, set + // the value of the first such name-value pair to `value` and remove the + // others. + var found = false; + for (var i = 0; i < list.length;) { + const cur = list[i]; + if (cur === name) { + if (!found) { + list[i + 1] = value; + found = true; + i += 2; + } else { + list.splice(i, 2); + } + } else { + i += 2; + } + } + + // Otherwise, append a new name-value pair whose name is `name` and value + // is `value`, to `list`. + if (!found) { + list.push(name, value); + } + + update(this[context], this); + }, + + sort() { + const a = this[searchParams]; + const len = a.length; + + if (len <= 2) { + // Nothing needs to be done. + } else if (len < 100) { + // 100 is found through testing. + // Simple stable in-place insertion sort + // Derived from v8/src/js/array.js + for (var i = 2; i < len; i += 2) { + var curKey = a[i]; + var curVal = a[i + 1]; + var j; + for (j = i - 2; j >= 0; j -= 2) { + if (a[j] > curKey) { + a[j + 2] = a[j]; + a[j + 3] = a[j + 1]; + } else { + break; + } + } + a[j + 2] = curKey; + a[j + 3] = curVal; + } + } else { + // Bottom-up iterative stable merge sort + const lBuffer = new Array(len); + const rBuffer = new Array(len); + for (var step = 2; step < len; step *= 2) { + for (var start = 0; start < len - 2; start += 2 * step) { + var mid = start + step; + var end = mid + step; + end = end < len ? end : len; + if (mid > end) + continue; + merge(a, start, mid, end, lBuffer, rBuffer); + } + } + } + + update(this[context], this); + }, + + // https://heycam.github.io/webidl/#es-iterators + // Define entries here rather than [Symbol.iterator] as the function name + // must be set to `entries`. + entries() { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + + return createSearchParamsIterator(this, 'key+value'); + }, + + forEach(callback, thisArg = undefined) { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + if (typeof callback !== 'function') { + throw new TypeError('Callback must be a function'); + } + + let list = this[searchParams]; + + var i = 0; + while (i < list.length) { + const key = list[i]; + const value = list[i + 1]; + callback.call(thisArg, value, key, this); + // in case the URL object's `search` is updated + list = this[searchParams]; + i += 2; + } + }, + + // https://heycam.github.io/webidl/#es-iterable + keys() { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + + return createSearchParamsIterator(this, 'key'); + }, + + values() { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + + return createSearchParamsIterator(this, 'value'); + }, + + // https://heycam.github.io/webidl/#es-stringifier + // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior + toString() { + if (!this || !this[searchParams] || this[searchParams][searchParams]) { + throw new TypeError('Value of "this" must be of type URLSearchParams'); + } + + return serializeParams(this[searchParams]); + } +}); + +// https://heycam.github.io/webidl/#es-iterable-entries +Object.defineProperty(URLSearchParams.prototype, Symbol.iterator, { + writable: true, + configurable: true, + value: URLSearchParams.prototype.entries +}); + +// https://heycam.github.io/webidl/#dfn-default-iterator-object +function createSearchParamsIterator(target, kind) { + const iterator = Object.create(URLSearchParamsIteratorPrototype); + iterator[context] = { + target, + kind, + index: 0 + }; + return iterator; +} + +// https://heycam.github.io/webidl/#dfn-iterator-prototype-object +const URLSearchParamsIteratorPrototype = Object.create(IteratorPrototype); + +defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', { + next() { + if (!this || + Object.getPrototypeOf(this) !== URLSearchParamsIteratorPrototype) { + throw new TypeError( + 'Value of "this" must be of type URLSearchParamsIterator'); + } + + const { + target, + kind, + index + } = this[context]; + const values = target[searchParams]; + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + const name = values[index]; + const value = values[index + 1]; + this[context].index = index + 2; + + let result; + if (kind === 'key') { + result = name; + } else if (kind === 'value') { + result = value; + } else { + result = [name, value]; + } + + return { + value: result, + done: false + }; + }, + [util.inspect.custom](recurseTimes, ctx) { + if (this == null || this[context] == null || this[context].target == null) + throw new TypeError( + 'Value of "this" must be of type URLSearchParamsIterator'); + + if (typeof recurseTimes === 'number' && recurseTimes < 0) + return ctx.stylize('[Object]', 'special'); + + const innerOpts = util._extend({}, ctx); + if (recurseTimes !== null) { + innerOpts.depth = recurseTimes - 1; + } + const { + target, + kind, + index + } = this[context]; + const output = target[searchParams].slice(index).reduce((prev, cur, i) => { + const key = i % 2 === 0; + if (kind === 'key' && key) { + prev.push(cur); + } else if (kind === 'value' && !key) { + prev.push(cur); + } else if (kind === 'key+value' && !key) { + prev.push([target[searchParams][index + i - 1], cur]); + } + return prev; + }, []); + const breakLn = util.inspect(output, innerOpts).includes('\n'); + const outputStrs = output.map((p) => util.inspect(p, innerOpts)); + let outputStr; + if (breakLn) { + outputStr = `\n ${outputStrs.join(',\n ')}`; + } else { + outputStr = ` ${outputStrs.join(', ')}`; + } + return `${this[Symbol.toStringTag]} {${outputStr} }`; + } +}); + +function domainToASCII(domain) { + if (arguments.length < 1) + throw new TypeError('The "domain" argument must be specified'); + + // toUSVString is not needed. + return _domainToASCII(`${domain}`); +} + +function domainToUnicode(domain) { + if (arguments.length < 1) + throw new TypeError('The "domain" argument must be specified'); + + // toUSVString is not needed. + return _domainToUnicode(`${domain}`); +} + +// Utility function that converts a URL object into an ordinary +// options object as expected by the http.request and https.request +// APIs. +function urlToOptions(url) { + var options = { + protocol: url.protocol, + hostname: url.hostname, + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname}${url.search}`, + href: url.href + }; + if (url.port !== '') { + options.port = Number(url.port); + } + if (url.username || url.password) { + options.auth = `${url.username}:${url.password}`; + } + return options; +} + +function getPathFromURLWin32(url) { + var hostname = url.hostname; + var pathname = url.pathname; + for (var n = 0; n < pathname.length; n++) { + if (pathname[n] === '%') { + var third = pathname.codePointAt(n + 2) | 0x20; + if ((pathname[n + 1] === '2' && third === 102) || // 2f 2F / + (pathname[n + 1] === '5' && third === 99)) { // 5c 5C \ + return new TypeError( + 'File URL path must not include encoded \\ or / characters'); + } + } + } + pathname = decodeURIComponent(pathname); + if (hostname !== '') { + // If hostname is set, then we have a UNC path + // Pass the hostname through domainToUnicode just in case + // it is an IDN using punycode encoding. We do not need to worry + // about percent encoding because the URL parser will have + // already taken care of that for us. Note that this only + // causes IDNs with an appropriate `xn--` prefix to be decoded. + return `//${domainToUnicode(hostname)}${pathname}`; + } else { + // Otherwise, it's a local path that requires a drive letter + var letter = pathname.codePointAt(1) | 0x20; + var sep = pathname[2]; + if (letter < 97 || letter > 122 || // a..z A..Z + (sep !== ':')) { + return new TypeError('File URL path must be absolute'); + } + return pathname.slice(1); + } +} + +function getPathFromURLPosix(url) { + if (url.hostname !== '') { + return new TypeError( + `File URL host must be "localhost" or empty on ${platform}`); + } + var pathname = url.pathname; + for (var n = 0; n < pathname.length; n++) { + if (pathname[n] === '%') { + var third = pathname.codePointAt(n + 2) | 0x20; + if (pathname[n + 1] === '2' && third === 102) { + return new TypeError( + 'File URL path must not include encoded / characters'); + } + } + } + return decodeURIComponent(pathname); +} + +function getPathFromURL(path) { + if (path == null || !path[searchParams] || + !path[searchParams][searchParams]) { + return path; + } + if (path.protocol !== 'file:') + return new TypeError('The URL must be of scheme file'); + return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path); +} + +// We percent-encode % character when converting from file path to URL, +// as this is the only character that won't be percent encoded by +// default URL percent encoding when pathname is set. +const percentRegEx = /%/g; +function getURLFromFilePath(filepath) { + const tmp = new URL('file://'); + if (filepath.includes('%')) + filepath = filepath.replace(percentRegEx, '%25'); + tmp.pathname = filepath; + return tmp; +} + +function NativeURL(ctx) { + this[context] = ctx; +} +NativeURL.prototype = URL.prototype; + +function constructUrl(flags, protocol, username, password, + host, port, path, query, fragment) { + var ctx = new URLContext(); + ctx.flags = flags; + ctx.scheme = protocol; + ctx.username = (flags & URL_FLAGS_HAS_USERNAME) !== 0 ? username : ''; + ctx.password = (flags & URL_FLAGS_HAS_PASSWORD) !== 0 ? password : ''; + ctx.port = port; + ctx.path = (flags & URL_FLAGS_HAS_PATH) !== 0 ? path : []; + ctx.query = query; + ctx.fragment = fragment; + ctx.host = host; + const url = new NativeURL(ctx); + url[searchParams] = new URLSearchParams(); + url[searchParams][context] = url; + initSearchParams(url[searchParams], query); + return url; +} +setURLConstructor(constructUrl); + +module.exports = { + toUSVString, + getPathFromURL, + getURLFromFilePath, + URL, + URLSearchParams, + domainToASCII, + domainToUnicode, + urlToOptions, + formatSymbol: kFormat, + searchParamsSymbol: searchParams +}; diff --git a/lib/internal/util.js b/lib/internal/util.js index 58aa011c08c6b9..befd176af51da8 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -6,6 +6,21 @@ const prefix = `(${process.release.name}:${process.pid}) `; exports.getHiddenValue = binding.getHiddenValue; exports.setHiddenValue = binding.setHiddenValue; +exports.getConstructorOf = function getConstructorOf(obj) { + while (obj) { + var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); + if (descriptor !== undefined && + typeof descriptor.value === 'function' && + descriptor.value.name !== '') { + return descriptor.value; + } + + obj = Object.getPrototypeOf(obj); + } + + return null; +}; + // The `buffer` module uses this. Defining it here instead of in the public // `util` module makes it accessible without having to `require('util')` there. exports.customInspectSymbol = Symbol('util.inspect.custom'); @@ -183,3 +198,7 @@ exports.toLength = function toLength(argument) { const len = toInteger(argument); return len <= 0 ? 0 : Math.min(len, Number.MAX_SAFE_INTEGER); }; + +// Cached to make sure no userland code can tamper with it. +const isArrayBufferView = ArrayBuffer.isView; +exports.isArrayBufferView = isArrayBufferView; diff --git a/lib/module.js b/lib/module.js index 537a2dcafb31d0..3b258f051bd4ca 100644 --- a/lib/module.js +++ b/lib/module.js @@ -48,9 +48,16 @@ function Module(id, parent) { } module.exports = Module; +const builtinModules = Object.keys(NativeModule._source) + .filter(NativeModule.nonInternalExists); + +Object.freeze(builtinModules); +Module.builtinModules = builtinModules; + Module._cache = {}; Module._pathCache = {}; Module._extensions = {}; + var modulePaths = []; Module.globalPaths = []; diff --git a/lib/net.js b/lib/net.js index 50c4a5cf3fd148..ac036534d6b402 100644 --- a/lib/net.js +++ b/lib/net.js @@ -334,6 +334,16 @@ Socket.prototype.setTimeout = function(msecs, callback) { Socket.prototype._onTimeout = function() { + if (this._handle) { + // `.prevWriteQueueSize` !== `.updateWriteQueueSize()` means there is + // an active write in progress, so we suppress the timeout. + const prevWriteQueueSize = this._handle.writeQueueSize; + if (prevWriteQueueSize > 0 && + prevWriteQueueSize !== this._handle.updateWriteQueueSize()) { + this._unrefTimer(); + return; + } + } debug('_onTimeout'); this.emit('timeout'); }; @@ -434,6 +444,8 @@ Socket.prototype.end = function(data, encoding) { this.read(0); else maybeDestroy(this); + + return this; }; @@ -983,7 +995,10 @@ function lookupAndConnect(self, options) { hints: options.hints || 0 }; - if (dnsopts.family !== 4 && dnsopts.family !== 6 && dnsopts.hints === 0) { + if (process.platform !== 'win32' && + dnsopts.family !== 4 && + dnsopts.family !== 6 && + dnsopts.hints === 0) { dnsopts.hints = dns.ADDRCONFIG; } @@ -1475,7 +1490,8 @@ Server.prototype.getConnections = function(cb) { } if (!this._usingSlaves) { - return end(null, this._connections); + end(null, this._connections); + return this; } // Poll slaves @@ -1495,6 +1511,8 @@ Server.prototype.getConnections = function(cb) { this._slaves.forEach(function(slave) { slave.getConnections(oncount); }); + + return this; }; diff --git a/lib/querystring.js b/lib/querystring.js index 5ccb5fa77b320f..668628cdc386f6 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -1,5 +1,6 @@ 'use strict'; +const { hexTable } = require('internal/querystring'); const QueryString = module.exports = { unescapeBuffer, // `unescape()` is a JS global, so we need to use a different local name @@ -116,10 +117,6 @@ function qsUnescape(s, decodeSpaces) { } -const hexTable = []; -for (var i = 0; i < 256; ++i) - hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); - // These characters do not need escaping when generating query strings: // ! - . _ ~ // ' ( ) * diff --git a/lib/repl.js b/lib/repl.js index f841b3e3eff34b..2287af180899ce 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -870,7 +870,18 @@ function complete(line, callback) { filter = match[1]; var dir, files, f, name, base, ext, abs, subfiles, s; group = []; - var paths = module.paths.concat(Module.globalPaths); + let paths = []; + + if (completeOn === '.') { + group = ['./', '../']; + } else if (completeOn === '..') { + group = ['../']; + } else if (/^\.\.?\//.test(completeOn)) { + paths = [process.cwd()]; + } else { + paths = module.paths.concat(Module.globalPaths); + } + for (i = 0; i < paths.length; i++) { dir = path.resolve(paths[i], subdir); try { diff --git a/lib/url.js b/lib/url.js index 214c8a93204e70..980a2b2a1f00f2 100644 --- a/lib/url.js +++ b/lib/url.js @@ -3,12 +3,17 @@ const { toASCII } = process.binding('config').hasIntl ? process.binding('icu') : require('punycode'); -exports.parse = urlParse; -exports.resolve = urlResolve; -exports.resolveObject = urlResolveObject; -exports.format = urlFormat; +const { hexTable } = require('internal/querystring'); -exports.Url = Url; +// WHATWG URL implementation provided by internal/url +const { + URL, + URLSearchParams, + domainToASCII, + domainToUnicode +} = require('internal/url'); + +// Original url.parse() API function Url() { this.protocol = null; @@ -311,7 +316,10 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { // It only converts parts of the domain name that // have non-ASCII characters, i.e. it doesn't matter if // you call it with a domain that already is ASCII-only. - this.hostname = toASCII(this.hostname); + + // Use lenient mode (`true`) to try to support even non-compliant + // URLs. + this.hostname = toASCII(this.hostname, true); } var p = this.port ? ':' + this.port : ''; @@ -939,10 +947,6 @@ function spliceOne(list, index) { list.pop(); } -var hexTable = new Array(256); -for (var i = 0; i < 256; ++i) - hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); - // These characters do not need escaping: // ! - . _ ~ // ' ( ) * : @@ -1014,3 +1018,18 @@ function encodeAuth(str) { return out + str.slice(lastPos); return out; } + +module.exports = { + // Original API + Url, + parse: urlParse, + resolve: urlResolve, + resolveObject: urlResolveObject, + format: urlFormat, + + // WHATWG API + URL, + URLSearchParams, + domainToASCII, + domainToUnicode +}; diff --git a/lib/util.js b/lib/util.js index 349186ad48b172..8c476d59e2034b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -91,6 +91,22 @@ exports.format = function(f) { str += Number(arguments[a++]); lastPos = i = i + 2; continue; + case 105: // 'i' + if (a >= argLen) + break; + if (lastPos < i) + str += f.slice(lastPos, i); + str += parseInt(arguments[a++]); + lastPos = i = i + 2; + continue; + case 102: // 'f' + if (a >= argLen) + break; + if (lastPos < i) + str += f.slice(lastPos, i); + str += parseFloat(arguments[a++]); + lastPos = i = i + 2; + continue; case 106: // 'j' if (a >= argLen) break; @@ -263,22 +279,6 @@ function arrayToHash(array) { } -function getConstructorOf(obj) { - while (obj) { - var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); - if (descriptor !== undefined && - typeof descriptor.value === 'function' && - descriptor.value.name !== '') { - return descriptor.value; - } - - obj = Object.getPrototypeOf(obj); - } - - return null; -} - - function ensureDebugIsInitialized() { if (Debug === undefined) { const runInDebugContext = require('vm').runInDebugContext; @@ -438,12 +438,12 @@ function formatValue(ctx, value, recurseTimes) { // Can't do the same for DataView because it has a non-primitive // .buffer property that we need to recurse for. if (binding.isArrayBuffer(value) || binding.isSharedArrayBuffer(value)) { - return `${getConstructorOf(value).name}` + + return `${internalUtil.getConstructorOf(value).name}` + ` { byteLength: ${formatNumber(ctx, value.byteLength)} }`; } } - var constructor = getConstructorOf(value); + var constructor = internalUtil.getConstructorOf(value); var base = '', empty = false, braces; var formatter = formatObject; diff --git a/node.gyp b/node.gyp index 1b17d991ae3244..8a7af9bfd1e50a 100644 --- a/node.gyp +++ b/node.gyp @@ -86,9 +86,11 @@ 'lib/internal/process/stdio.js', 'lib/internal/process/warning.js', 'lib/internal/process.js', + 'lib/internal/querystring.js', 'lib/internal/readline.js', 'lib/internal/repl.js', 'lib/internal/socket_list.js', + 'lib/internal/url.js', 'lib/internal/util.js', 'lib/internal/v8_prof_polyfill.js', 'lib/internal/v8_prof_processor.js', @@ -162,6 +164,7 @@ 'src/node_main.cc', 'src/node_os.cc', 'src/node_revert.cc', + 'src/node_url.cc', 'src/node_util.cc', 'src/node_v8.cc', 'src/node_stat_watcher.cc', @@ -231,18 +234,28 @@ '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc', ], + 'variables': { + 'openssl_system_ca_path%': '', + }, + 'defines': [ 'NODE_ARCH="<(target_arch)"', 'NODE_PLATFORM="<(OS)"', 'NODE_WANT_INTERNALS=1', # Warn when using deprecated V8 APIs. 'V8_DEPRECATION_WARNINGS=1', + 'NODE_OPENSSL_SYSTEM_CERT_PATH="<(openssl_system_ca_path)"', ], 'conditions': [ [ 'node_shared=="true" and node_module_version!="" and OS!="win"', { 'product_extension': '<(shlib_suffix)', }] ], + 'direct_dependent_settings': { + 'defines': [ + 'NODE_OPENSSL_SYSTEM_CERT_PATH="<(openssl_system_ca_path)"', + ], + }, }, { 'target_name': 'mkssldef', @@ -588,6 +601,7 @@ '<(OBJ_PATH)/node.<(OBJ_SUFFIX)', '<(OBJ_PATH)/node_buffer.<(OBJ_SUFFIX)', '<(OBJ_PATH)/node_i18n.<(OBJ_SUFFIX)', + '<(OBJ_PATH)/node_url.<(OBJ_SUFFIX)', '<(OBJ_PATH)/debug-agent.<(OBJ_SUFFIX)', '<(OBJ_PATH)/util.<(OBJ_SUFFIX)', '<(OBJ_PATH)/string_bytes.<(OBJ_SUFFIX)', @@ -611,6 +625,7 @@ 'sources': [ 'test/cctest/test_base64.cc', 'test/cctest/test_util.cc', + 'test/cctest/test_url.cc' ], 'sources!': [ diff --git a/src/env.h b/src/env.h index 940c97fe4ddbf1..c4489857161b75 100644 --- a/src/env.h +++ b/src/env.h @@ -252,6 +252,7 @@ namespace node { V(tls_wrap_constructor_template, v8::FunctionTemplate) \ V(tty_constructor_template, v8::FunctionTemplate) \ V(udp_constructor_function, v8::Function) \ + V(url_constructor_function, v8::Function) \ V(write_wrap_constructor_function, v8::Function) \ class Environment; diff --git a/src/node.cc b/src/node.cc index 20deda5034f5d1..8ab9e4d1f45f51 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2950,6 +2950,12 @@ static void EnvEnumerator(const PropertyCallbackInfo& info) { } +static void GetParentProcessId(Local property, + const PropertyCallbackInfo& info) { + info.GetReturnValue().Set(Integer::New(info.GetIsolate(), uv_os_getppid())); +} + + static Local GetFeatures(Environment* env) { EscapableHandleScope scope(env->isolate()); @@ -3305,6 +3311,9 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "pid", Integer::New(env->isolate(), getpid())); READONLY_PROPERTY(process, "features", GetFeatures(env)); + process->SetAccessor(FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"), + GetParentProcessId); + auto need_immediate_callback_string = FIXED_ONE_BYTE_STRING(env->isolate(), "_needImmediateCallback"); CHECK(process->SetAccessor(env->context(), need_immediate_callback_string, @@ -3878,6 +3887,10 @@ static void ParseArgs(int* argc, const char** new_exec_argv = new const char*[nargs]; const char** new_v8_argv = new const char*[nargs]; const char** new_argv = new const char*[nargs]; +#if HAVE_OPENSSL + bool use_bundled_ca = false; + bool use_openssl_ca = false; +#endif // HAVE_OPENSSL for (unsigned int i = 0; i < nargs; ++i) { new_exec_argv[i] = nullptr; @@ -3983,7 +3996,9 @@ static void ParseArgs(int* argc, default_cipher_list = arg + 18; } else if (strncmp(arg, "--use-openssl-ca", 16) == 0) { ssl_openssl_cert_store = true; + use_openssl_ca = true; } else if (strncmp(arg, "--use-bundled-ca", 16) == 0) { + use_bundled_ca = true; ssl_openssl_cert_store = false; #if NODE_FIPS_MODE } else if (strcmp(arg, "--enable-fips") == 0) { @@ -4018,6 +4033,16 @@ static void ParseArgs(int* argc, index += args_consumed; } +#if HAVE_OPENSSL + if (use_openssl_ca && use_bundled_ca) { + fprintf(stderr, + "%s: either --use-openssl-ca or --use-bundled-ca can be used, " + "not both\n", + argv[0]); + exit(9); + } +#endif + // Copy remaining arguments. const unsigned int args_left = nargs - index; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index af892d4367c5da..a439063c47bc10 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -124,6 +124,8 @@ static const char* const root_certs[] = { #include "node_root_certs.h" // NOLINT(build/include_order) }; +static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH; + static std::string extra_root_certs_file; // NOLINT(runtime/string) static X509_STORE* root_cert_store; @@ -724,6 +726,9 @@ static X509_STORE* NewRootCertStore() { } X509_STORE* store = X509_STORE_new(); + if (*system_cert_path != '\0') { + X509_STORE_load_locations(store, system_cert_path, nullptr); + } if (ssl_openssl_cert_store) { X509_STORE_set_default_paths(store); } else { @@ -3520,8 +3525,16 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + // Restrict GCM tag lengths according to NIST 800-38d, page 9. + unsigned int tag_len = Buffer::Length(buf); + if (tag_len > 16 || (tag_len < 12 && tag_len != 8 && tag_len != 4)) { + ProcessEmitWarning(cipher->env(), + "Permitting authentication tag lengths of %u bytes is deprecated. " + "Valid GCM tag lengths are 4, 8, 12, 13, 14, 15, 16.", + tag_len); + } - if (!cipher->SetAuthTag(Buffer::Data(buf), Buffer::Length(buf))) + if (!cipher->SetAuthTag(Buffer::Data(buf), tag_len)) env->ThrowError("Attempting to set auth tag in unsupported state"); } @@ -5595,11 +5608,18 @@ void PBKDF2(const FunctionCallbackInfo& args) { // Only instantiate within a valid HandleScope. class RandomBytesRequest : public AsyncWrap { public: - RandomBytesRequest(Environment* env, Local object, size_t size) + enum FreeMode { FREE_DATA, DONT_FREE_DATA }; + + RandomBytesRequest(Environment* env, + Local object, + size_t size, + char* data, + FreeMode free_mode) : AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO), error_(0), size_(size), - data_(node::Malloc(size)) { + data_(data), + free_mode_(free_mode) { Wrap(object, this); } @@ -5620,9 +5640,15 @@ class RandomBytesRequest : public AsyncWrap { return data_; } + inline void set_data(char* data) { + data_ = data; + } + inline void release() { - free(data_); size_ = 0; + if (free_mode_ == FREE_DATA) { + free(data_); + } } inline void return_memory(char** d, size_t* len) { @@ -5648,6 +5674,7 @@ class RandomBytesRequest : public AsyncWrap { unsigned long error_; // NOLINT(runtime/int) size_t size_; char* data_; + const FreeMode free_mode_; }; @@ -5686,7 +5713,18 @@ void RandomBytesCheck(RandomBytesRequest* req, Local argv[2]) { size_t size; req->return_memory(&data, &size); argv[0] = Null(req->env()->isolate()); - argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked(); + Local buffer = + req->object()->Get(req->env()->context(), + req->env()->buffer_string()).ToLocalChecked(); + + if (buffer->IsUint8Array()) { + CHECK_LE(req->size(), Buffer::Length(buffer)); + char* buf = Buffer::Data(buffer); + memcpy(buf, data, req->size()); + argv[1] = buffer; + } else { + argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked(); + } } } @@ -5705,11 +5743,22 @@ void RandomBytesAfter(uv_work_t* work_req, int status) { } +void RandomBytesProcessSync(Environment* env, + RandomBytesRequest* req, + Local argv[2]) { + env->PrintSyncTrace(); + RandomBytesWork(req->work_req()); + RandomBytesCheck(req, argv); + delete req; + + if (!argv[0]->IsNull()) + env->isolate()->ThrowException(argv[0]); +} + + void RandomBytes(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - // maybe allow a buffer to write to? cuts down on object creation - // when generating random data in a loop if (!args[0]->IsUint32()) { return env->ThrowTypeError("size must be a number >= 0"); } @@ -5719,7 +5768,13 @@ void RandomBytes(const FunctionCallbackInfo& args) { return env->ThrowRangeError("size is not a valid Smi"); Local obj = env->NewInternalFieldObject(); - RandomBytesRequest* req = new RandomBytesRequest(env, obj, size); + char* data = node::Malloc(size); + RandomBytesRequest* req = + new RandomBytesRequest(env, + obj, + size, + data, + RandomBytesRequest::FREE_DATA); if (args[1]->IsFunction()) { obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), args[1]); @@ -5732,15 +5787,55 @@ void RandomBytes(const FunctionCallbackInfo& args) { RandomBytesAfter); args.GetReturnValue().Set(obj); } else { - env->PrintSyncTrace(); Local argv[2]; - RandomBytesWork(req->work_req()); - RandomBytesCheck(req, argv); - delete req; + RandomBytesProcessSync(env, req, argv); + if (argv[0]->IsNull()) + args.GetReturnValue().Set(argv[1]); + } +} - if (!argv[0]->IsNull()) - env->isolate()->ThrowException(argv[0]); - else + +void RandomBytesBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsUint8Array()); + CHECK(args[1]->IsUint32()); + CHECK(args[2]->IsUint32()); + + int64_t offset = args[1]->IntegerValue(); + int64_t size = args[2]->IntegerValue(); + + Local obj = env->NewInternalFieldObject(); + obj->Set(env->context(), env->buffer_string(), args[0]).FromJust(); + char* data = Buffer::Data(args[0]); + data += offset; + + RandomBytesRequest* req = + new RandomBytesRequest(env, + obj, + size, + data, + RandomBytesRequest::DONT_FREE_DATA); + if (args[3]->IsFunction()) { + obj->Set(env->context(), + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), + args[3]).FromJust(); + + if (env->in_domain()) { + obj->Set(env->context(), + env->domain_string(), + env->domain_array()->Get(0)).FromJust(); + } + + uv_queue_work(env->event_loop(), + req->work_req(), + RandomBytesWork, + RandomBytesAfter); + args.GetReturnValue().Set(obj); + } else { + Local argv[2]; + RandomBytesProcessSync(env, req, argv); + if (argv[0]->IsNull()) args.GetReturnValue().Set(argv[1]); } } @@ -6168,6 +6263,7 @@ void InitCrypto(Local target, env->SetMethod(target, "setFipsCrypto", SetFipsCrypto); env->SetMethod(target, "PBKDF2", PBKDF2); env->SetMethod(target, "randomBytes", RandomBytes); + env->SetMethod(target, "randomFill", RandomBytesBuffer); env->SetMethod(target, "timingSafeEqual", TimingSafeEqual); env->SetMethod(target, "getSSLCiphers", GetSSLCiphers); env->SetMethod(target, "getCiphers", GetCiphers); diff --git a/src/node_crypto_bio.cc b/src/node_crypto_bio.cc index bfb29366e8c58d..7ddfe2904f0ef3 100644 --- a/src/node_crypto_bio.cc +++ b/src/node_crypto_bio.cc @@ -51,8 +51,6 @@ void NodeBIO::AssignEnvironment(Environment* env) { int NodeBIO::New(BIO* bio) { bio->ptr = new NodeBIO(); - // XXX Why am I doing it?! - bio->shutdown = 1; bio->init = 1; bio->num = -1; diff --git a/src/node_crypto_clienthello-inl.h b/src/node_crypto_clienthello-inl.h index ed89ffa99666b2..7acf9a6c791250 100644 --- a/src/node_crypto_clienthello-inl.h +++ b/src/node_crypto_clienthello-inl.h @@ -19,6 +19,7 @@ inline void ClientHelloParser::Reset() { tls_ticket_ = nullptr; servername_size_ = 0; servername_ = nullptr; + ocsp_request_ = 0; } inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb, diff --git a/src/node_crypto_clienthello.h b/src/node_crypto_clienthello.h index ee9cce1848a363..066951f8fc3392 100644 --- a/src/node_crypto_clienthello.h +++ b/src/node_crypto_clienthello.h @@ -5,7 +5,6 @@ #include // size_t #include -#include // nullptr namespace node { namespace crypto { diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 8e32f1fe08177b..c013027981755f 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -77,12 +77,11 @@ bool InitializeICUDirectory(const std::string& path) { } } -static int32_t ToUnicode(MaybeStackBuffer* buf, - const char* input, - size_t length) { +int32_t ToUnicode(MaybeStackBuffer* buf, + const char* input, + size_t length) { UErrorCode status = U_ZERO_ERROR; - uint32_t options = UIDNA_DEFAULT; - options |= UIDNA_NONTRANSITIONAL_TO_UNICODE; + uint32_t options = UIDNA_NONTRANSITIONAL_TO_UNICODE; UIDNA* uidna = uidna_openUTS46(options, &status); if (U_FAILURE(status)) return -1; @@ -90,33 +89,52 @@ static int32_t ToUnicode(MaybeStackBuffer* buf, int32_t len = uidna_nameToUnicodeUTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); + // Do not check info.errors like we do with ToASCII since ToUnicode always + // returns a string, despite any possible errors that may have occurred. + if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; buf->AllocateSufficientStorage(len); len = uidna_nameToUnicodeUTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); } - if (U_FAILURE(status)) + // info.errors is ignored as UTS #46 ToUnicode always produces a Unicode + // string, regardless of whether an error occurred. + + if (U_FAILURE(status)) { len = -1; + buf->SetLength(0); + } else { + buf->SetLength(len); + } uidna_close(uidna); return len; } -static int32_t ToASCII(MaybeStackBuffer* buf, - const char* input, - size_t length) { +int32_t ToASCII(MaybeStackBuffer* buf, + const char* input, + size_t length, + enum idna_mode mode) { UErrorCode status = U_ZERO_ERROR; - uint32_t options = UIDNA_DEFAULT; - options |= UIDNA_NONTRANSITIONAL_TO_ASCII; + uint32_t options = // CheckHyphens = false; handled later + UIDNA_CHECK_BIDI | // CheckBidi = true + UIDNA_CHECK_CONTEXTJ | // CheckJoiners = true + UIDNA_NONTRANSITIONAL_TO_ASCII; // Nontransitional_Processing + if (mode == IDNA_STRICT) { + options |= UIDNA_USE_STD3_RULES; // UseSTD3ASCIIRules = beStrict + // VerifyDnsLength = beStrict; + // handled later + } + UIDNA* uidna = uidna_openUTS46(options, &status); if (U_FAILURE(status)) return -1; @@ -124,7 +142,7 @@ static int32_t ToASCII(MaybeStackBuffer* buf, int32_t len = uidna_nameToASCII_UTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); @@ -133,13 +151,45 @@ static int32_t ToASCII(MaybeStackBuffer* buf, buf->AllocateSufficientStorage(len); len = uidna_nameToASCII_UTF8(uidna, input, length, - **buf, buf->length(), + **buf, buf->capacity(), &info, &status); } - if (U_FAILURE(status)) + // In UTS #46 which specifies ToASCII, certain error conditions are + // configurable through options, and the WHATWG URL Standard promptly elects + // to disable some of them to accommodate for real-world use cases. + // Unfortunately, ICU4C's IDNA module does not support disabling some of + // these options through `options` above, and thus continues throwing + // unnecessary errors. To counter this situation, we just filter out the + // errors that may have happened afterwards, before deciding whether to + // return an error from this function. + + // CheckHyphens = false + // (Specified in the current UTS #46 draft rev. 18.) + // Refs: + // - https://github.com/whatwg/url/issues/53 + // - https://github.com/whatwg/url/pull/309 + // - http://www.unicode.org/review/pri317/ + // - http://www.unicode.org/reports/tr46/tr46-18.html + // - https://www.icann.org/news/announcement-2000-01-07-en + info.errors &= ~UIDNA_ERROR_HYPHEN_3_4; + info.errors &= ~UIDNA_ERROR_LEADING_HYPHEN; + info.errors &= ~UIDNA_ERROR_TRAILING_HYPHEN; + + if (mode != IDNA_STRICT) { + // VerifyDnsLength = beStrict + info.errors &= ~UIDNA_ERROR_EMPTY_LABEL; + info.errors &= ~UIDNA_ERROR_LABEL_TOO_LONG; + info.errors &= ~UIDNA_ERROR_DOMAIN_NAME_TOO_LONG; + } + + if (U_FAILURE(status) || (mode != IDNA_LENIENT && info.errors != 0)) { len = -1; + buf->SetLength(0); + } else { + buf->SetLength(len); + } uidna_close(uidna); return len; @@ -169,8 +219,12 @@ static void ToASCII(const FunctionCallbackInfo& args) { CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Utf8Value val(env->isolate(), args[0]); + // optional arg + bool lenient = args[1]->BooleanValue(env->context()).FromJust(); + enum idna_mode mode = lenient ? IDNA_LENIENT : IDNA_DEFAULT; + MaybeStackBuffer buf; - int32_t len = ToASCII(&buf, *val, val.length()); + int32_t len = ToASCII(&buf, *val, val.length(), mode); if (len < 0) { return env->ThrowError("Cannot convert name to ASCII"); diff --git a/src/node_i18n.h b/src/node_i18n.h index ff9e87cea7fe83..30acefd1a8a996 100644 --- a/src/node_i18n.h +++ b/src/node_i18n.h @@ -16,6 +16,30 @@ namespace i18n { bool InitializeICUDirectory(const std::string& path); +enum idna_mode { + // Default mode for maximum compatibility. + IDNA_DEFAULT, + // Ignore all errors in IDNA conversion, if possible. + IDNA_LENIENT, + // Enforce STD3 rules (UseSTD3ASCIIRules) and DNS length restrictions + // (VerifyDnsLength). Corresponds to `beStrict` flag in the "domain to ASCII" + // algorithm. + IDNA_STRICT +}; + +// Implements the WHATWG URL Standard "domain to ASCII" algorithm. +// https://url.spec.whatwg.org/#concept-domain-to-ascii +int32_t ToASCII(MaybeStackBuffer* buf, + const char* input, + size_t length, + enum idna_mode mode = IDNA_DEFAULT); + +// Implements the WHATWG URL Standard "domain to Unicode" algorithm. +// https://url.spec.whatwg.org/#concept-domain-to-unicode +int32_t ToUnicode(MaybeStackBuffer* buf, + const char* input, + size_t length); + } // namespace i18n } // namespace node diff --git a/src/node_url.cc b/src/node_url.cc new file mode 100644 index 00000000000000..0e5c395b130d52 --- /dev/null +++ b/src/node_url.cc @@ -0,0 +1,2246 @@ +#include "node_url.h" +#include "node_internals.h" +#include "base-object-inl.h" +#include "node_i18n.h" + +#include +#include +#include +#include + +namespace node { + +using v8::Array; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Null; +using v8::Object; +using v8::String; +using v8::TryCatch; +using v8::Undefined; +using v8::Value; + +#define GET(env, obj, name) \ + obj->Get(env->context(), \ + OneByteString(env->isolate(), name)).ToLocalChecked() + +#define GET_AND_SET(env, obj, name, data, flag) \ + { \ + Local val = GET(env, obj, #name); \ + if (val->IsString()) { \ + Utf8Value value(env->isolate(), val.As()); \ + data->name = *value; \ + data->flags |= flag; \ + } \ + } + +#define UTF8STRING(isolate, str) \ + String::NewFromUtf8(isolate, str.c_str(), v8::NewStringType::kNormal) \ + .ToLocalChecked() + +namespace url { + +namespace { + +// https://url.spec.whatwg.org/#eof-code-point +const char kEOL = -1; + +// Used in ToUSVString(). +const char16_t kUnicodeReplacementCharacter = 0xFFFD; + +// https://url.spec.whatwg.org/#concept-host +class URLHost { + public: + ~URLHost(); + + void ParseIPv4Host(const char* input, size_t length, bool* is_ipv4); + void ParseIPv6Host(const char* input, size_t length); + void ParseOpaqueHost(const char* input, size_t length); + void ParseHost(const char* input, + size_t length, + bool is_special, + bool unicode = false); + + inline bool ParsingFailed() const { return type_ == HostType::H_FAILED; } + std::string ToString() const; + + private: + enum class HostType { + H_FAILED, + H_DOMAIN, + H_IPV4, + H_IPV6, + H_OPAQUE, + }; + + union Value { + std::string domain; + uint32_t ipv4; + uint16_t ipv6[8]; + std::string opaque; + + ~Value() {} + Value() : ipv4(0) {} + }; + + Value value_; + HostType type_ = HostType::H_FAILED; + + // Setting the string members of the union with = is brittle because + // it relies on them being initialized to a state that requires no + // destruction of old data. + // For a long time, that worked well enough because ParseIPv6Host() happens + // to zero-fill `value_`, but that really is relying on standard library + // internals too much. + // These helpers are the easiest solution but we might want to consider + // just not forcing strings into an union. + inline void SetOpaque(std::string* string) { + type_ = HostType::H_OPAQUE; + new(&value_.opaque) std::string(); + value_.opaque.swap(*string); + } + + inline void SetDomain(std::string* string) { + type_ = HostType::H_DOMAIN; + new(&value_.domain) std::string(); + value_.domain.swap(*string); + } +}; + +URLHost::~URLHost() { + using string = std::string; + switch (type_) { + case HostType::H_DOMAIN: value_.domain.~string(); break; + case HostType::H_OPAQUE: value_.opaque.~string(); break; + default: break; + } +} + +#define ARGS(XX) \ + XX(ARG_FLAGS) \ + XX(ARG_PROTOCOL) \ + XX(ARG_USERNAME) \ + XX(ARG_PASSWORD) \ + XX(ARG_HOST) \ + XX(ARG_PORT) \ + XX(ARG_PATH) \ + XX(ARG_QUERY) \ + XX(ARG_FRAGMENT) + +#define ERR_ARGS(XX) \ + XX(ERR_ARG_FLAGS) \ + XX(ERR_ARG_INPUT) \ + +enum url_cb_args { +#define XX(name) name, + ARGS(XX) +#undef XX +}; + +enum url_error_cb_args { +#define XX(name) name, + ERR_ARGS(XX) +#undef XX +}; + +#define CHAR_TEST(bits, name, expr) \ + template \ + inline bool name(const T ch) { \ + static_assert(sizeof(ch) >= (bits) / 8, \ + "Character must be wider than " #bits " bits"); \ + return (expr); \ + } + +#define TWO_CHAR_STRING_TEST(bits, name, expr) \ + template \ + inline bool name(const T ch1, const T ch2) { \ + static_assert(sizeof(ch1) >= (bits) / 8, \ + "Character must be wider than " #bits " bits"); \ + return (expr); \ + } \ + template \ + inline bool name(const std::basic_string& str) { \ + static_assert(sizeof(str[0]) >= (bits) / 8, \ + "Character must be wider than " #bits " bits"); \ + return str.length() >= 2 && name(str[0], str[1]); \ + } + +// https://infra.spec.whatwg.org/#ascii-tab-or-newline +CHAR_TEST(8, IsASCIITabOrNewline, (ch == '\t' || ch == '\n' || ch == '\r')) + +// https://infra.spec.whatwg.org/#c0-control-or-space +CHAR_TEST(8, IsC0ControlOrSpace, (ch >= '\0' && ch <= ' ')) + +// https://infra.spec.whatwg.org/#ascii-digit +CHAR_TEST(8, IsASCIIDigit, (ch >= '0' && ch <= '9')) + +// https://infra.spec.whatwg.org/#ascii-hex-digit +CHAR_TEST(8, IsASCIIHexDigit, (IsASCIIDigit(ch) || + (ch >= 'A' && ch <= 'F') || + (ch >= 'a' && ch <= 'f'))) + +// https://infra.spec.whatwg.org/#ascii-alpha +CHAR_TEST(8, IsASCIIAlpha, ((ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z'))) + +// https://infra.spec.whatwg.org/#ascii-alphanumeric +CHAR_TEST(8, IsASCIIAlphanumeric, (IsASCIIDigit(ch) || IsASCIIAlpha(ch))) + +// https://infra.spec.whatwg.org/#ascii-lowercase +template +inline T ASCIILowercase(T ch) { + return IsASCIIAlpha(ch) ? (ch | 0x20) : ch; +} + +// https://url.spec.whatwg.org/#forbidden-host-code-point +CHAR_TEST(8, IsForbiddenHostCodePoint, + ch == '\0' || ch == '\t' || ch == '\n' || ch == '\r' || + ch == ' ' || ch == '#' || ch == '%' || ch == '/' || + ch == ':' || ch == '?' || ch == '@' || ch == '[' || + ch == '\\' || ch == ']') + +// https://url.spec.whatwg.org/#windows-drive-letter +TWO_CHAR_STRING_TEST(8, IsWindowsDriveLetter, + (IsASCIIAlpha(ch1) && (ch2 == ':' || ch2 == '|'))) + +// https://url.spec.whatwg.org/#normalized-windows-drive-letter +TWO_CHAR_STRING_TEST(8, IsNormalizedWindowsDriveLetter, + (IsASCIIAlpha(ch1) && ch2 == ':')) + +// If a UTF-16 character is a low/trailing surrogate. +CHAR_TEST(16, IsUnicodeTrail, (ch & 0xFC00) == 0xDC00) + +// If a UTF-16 character is a surrogate. +CHAR_TEST(16, IsUnicodeSurrogate, (ch & 0xF800) == 0xD800) + +// If a UTF-16 surrogate is a low/trailing one. +CHAR_TEST(16, IsUnicodeSurrogateTrail, (ch & 0x400) != 0) + +#undef CHAR_TEST +#undef TWO_CHAR_STRING_TEST + +const char* hex[256] = { + "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", + "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", + "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", + "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", + "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", + "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F", + "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", + "%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", + "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", + "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F", + "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", + "%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F", + "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", + "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F", + "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", + "%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F", + "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", + "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", + "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", + "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", + "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", + "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", + "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", + "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", + "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", + "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", + "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", + "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", + "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", + "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", + "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", + "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" +}; + +const uint8_t C0_CONTROL_ENCODE_SET[32] = { + // 00 01 02 03 04 05 06 07 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 08 09 0A 0B 0C 0D 0E 0F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 10 11 12 13 14 15 16 17 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 18 19 1A 1B 1C 1D 1E 1F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 20 21 22 23 24 25 26 27 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 28 29 2A 2B 2C 2D 2E 2F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 30 31 32 33 34 35 36 37 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 38 39 3A 3B 3C 3D 3E 3F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 40 41 42 43 44 45 46 47 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 48 49 4A 4B 4C 4D 4E 4F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 50 51 52 53 54 55 56 57 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 58 59 5A 5B 5C 5D 5E 5F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 60 61 62 63 64 65 66 67 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 68 69 6A 6B 6C 6D 6E 6F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 70 71 72 73 74 75 76 77 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 78 79 7A 7B 7C 7D 7E 7F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, + // 80 81 82 83 84 85 86 87 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 88 89 8A 8B 8C 8D 8E 8F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 90 91 92 93 94 95 96 97 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 98 99 9A 9B 9C 9D 9E 9F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A0 A1 A2 A3 A4 A5 A6 A7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A8 A9 AA AB AC AD AE AF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B0 B1 B2 B3 B4 B5 B6 B7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B8 B9 BA BB BC BD BE BF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C0 C1 C2 C3 C4 C5 C6 C7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C8 C9 CA CB CC CD CE CF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D0 D1 D2 D3 D4 D5 D6 D7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D8 D9 DA DB DC DD DE DF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E0 E1 E2 E3 E4 E5 E6 E7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E8 E9 EA EB EC ED EE EF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F0 F1 F2 F3 F4 F5 F6 F7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F8 F9 FA FB FC FD FE FF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 +}; + +const uint8_t PATH_ENCODE_SET[32] = { + // 00 01 02 03 04 05 06 07 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 08 09 0A 0B 0C 0D 0E 0F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 10 11 12 13 14 15 16 17 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 18 19 1A 1B 1C 1D 1E 1F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 20 21 22 23 24 25 26 27 + 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, + // 28 29 2A 2B 2C 2D 2E 2F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 30 31 32 33 34 35 36 37 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 38 39 3A 3B 3C 3D 3E 3F + 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x80, + // 40 41 42 43 44 45 46 47 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 48 49 4A 4B 4C 4D 4E 4F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 50 51 52 53 54 55 56 57 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 58 59 5A 5B 5C 5D 5E 5F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 60 61 62 63 64 65 66 67 + 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 68 69 6A 6B 6C 6D 6E 6F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 70 71 72 73 74 75 76 77 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 78 79 7A 7B 7C 7D 7E 7F + 0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x00 | 0x80, + // 80 81 82 83 84 85 86 87 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 88 89 8A 8B 8C 8D 8E 8F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 90 91 92 93 94 95 96 97 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 98 99 9A 9B 9C 9D 9E 9F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A0 A1 A2 A3 A4 A5 A6 A7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A8 A9 AA AB AC AD AE AF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B0 B1 B2 B3 B4 B5 B6 B7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B8 B9 BA BB BC BD BE BF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C0 C1 C2 C3 C4 C5 C6 C7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C8 C9 CA CB CC CD CE CF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D0 D1 D2 D3 D4 D5 D6 D7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D8 D9 DA DB DC DD DE DF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E0 E1 E2 E3 E4 E5 E6 E7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E8 E9 EA EB EC ED EE EF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F0 F1 F2 F3 F4 F5 F6 F7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F8 F9 FA FB FC FD FE FF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 +}; + +const uint8_t USERINFO_ENCODE_SET[32] = { + // 00 01 02 03 04 05 06 07 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 08 09 0A 0B 0C 0D 0E 0F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 10 11 12 13 14 15 16 17 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 18 19 1A 1B 1C 1D 1E 1F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 20 21 22 23 24 25 26 27 + 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, + // 28 29 2A 2B 2C 2D 2E 2F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, + // 30 31 32 33 34 35 36 37 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 38 39 3A 3B 3C 3D 3E 3F + 0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 40 41 42 43 44 45 46 47 + 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 48 49 4A 4B 4C 4D 4E 4F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 50 51 52 53 54 55 56 57 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 58 59 5A 5B 5C 5D 5E 5F + 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00, + // 60 61 62 63 64 65 66 67 + 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 68 69 6A 6B 6C 6D 6E 6F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 70 71 72 73 74 75 76 77 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 78 79 7A 7B 7C 7D 7E 7F + 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x00 | 0x80, + // 80 81 82 83 84 85 86 87 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 88 89 8A 8B 8C 8D 8E 8F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 90 91 92 93 94 95 96 97 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 98 99 9A 9B 9C 9D 9E 9F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A0 A1 A2 A3 A4 A5 A6 A7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A8 A9 AA AB AC AD AE AF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B0 B1 B2 B3 B4 B5 B6 B7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B8 B9 BA BB BC BD BE BF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C0 C1 C2 C3 C4 C5 C6 C7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C8 C9 CA CB CC CD CE CF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D0 D1 D2 D3 D4 D5 D6 D7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D8 D9 DA DB DC DD DE DF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E0 E1 E2 E3 E4 E5 E6 E7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E8 E9 EA EB EC ED EE EF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F0 F1 F2 F3 F4 F5 F6 F7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F8 F9 FA FB FC FD FE FF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 +}; + +const uint8_t QUERY_ENCODE_SET[32] = { + // 00 01 02 03 04 05 06 07 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 08 09 0A 0B 0C 0D 0E 0F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 10 11 12 13 14 15 16 17 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 18 19 1A 1B 1C 1D 1E 1F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 20 21 22 23 24 25 26 27 + 0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00, + // 28 29 2A 2B 2C 2D 2E 2F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 30 31 32 33 34 35 36 37 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 38 39 3A 3B 3C 3D 3E 3F + 0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00, + // 40 41 42 43 44 45 46 47 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 48 49 4A 4B 4C 4D 4E 4F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 50 51 52 53 54 55 56 57 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 58 59 5A 5B 5C 5D 5E 5F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 60 61 62 63 64 65 66 67 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 68 69 6A 6B 6C 6D 6E 6F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 70 71 72 73 74 75 76 77 + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + // 78 79 7A 7B 7C 7D 7E 7F + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80, + // 80 81 82 83 84 85 86 87 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 88 89 8A 8B 8C 8D 8E 8F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 90 91 92 93 94 95 96 97 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // 98 99 9A 9B 9C 9D 9E 9F + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A0 A1 A2 A3 A4 A5 A6 A7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // A8 A9 AA AB AC AD AE AF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B0 B1 B2 B3 B4 B5 B6 B7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // B8 B9 BA BB BC BD BE BF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C0 C1 C2 C3 C4 C5 C6 C7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // C8 C9 CA CB CC CD CE CF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D0 D1 D2 D3 D4 D5 D6 D7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // D8 D9 DA DB DC DD DE DF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E0 E1 E2 E3 E4 E5 E6 E7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // E8 E9 EA EB EC ED EE EF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F0 F1 F2 F3 F4 F5 F6 F7 + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80, + // F8 F9 FA FB FC FD FE FF + 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 +}; + +inline bool BitAt(const uint8_t a[], const uint8_t i) { + return !!(a[i >> 3] & (1 << (i & 7))); +} + +// Appends ch to str. If ch position in encode_set is set, the ch will +// be percent-encoded then appended. +inline void AppendOrEscape(std::string* str, + const unsigned char ch, + const uint8_t encode_set[]) { + if (BitAt(encode_set, ch)) + *str += hex[ch]; + else + *str += ch; +} + +template +inline unsigned hex2bin(const T ch) { + if (ch >= '0' && ch <= '9') + return ch - '0'; + if (ch >= 'A' && ch <= 'F') + return 10 + (ch - 'A'); + if (ch >= 'a' && ch <= 'f') + return 10 + (ch - 'a'); + return static_cast(-1); +} + +inline std::string PercentDecode(const char* input, size_t len) { + std::string dest; + if (len == 0) + return dest; + dest.reserve(len); + const char* pointer = input; + const char* end = input + len; + + while (pointer < end) { + const char ch = pointer[0]; + const size_t remaining = end - pointer - 1; + if (ch != '%' || remaining < 2 || + (ch == '%' && + (!IsASCIIHexDigit(pointer[1]) || + !IsASCIIHexDigit(pointer[2])))) { + dest += ch; + pointer++; + continue; + } else { + unsigned a = hex2bin(pointer[1]); + unsigned b = hex2bin(pointer[2]); + char c = static_cast(a * 16 + b); + dest += c; + pointer += 3; + } + } + return dest; +} + +#define SPECIALS(XX) \ + XX("ftp:", 21) \ + XX("file:", -1) \ + XX("gopher:", 70) \ + XX("http:", 80) \ + XX("https:", 443) \ + XX("ws:", 80) \ + XX("wss:", 443) + +inline bool IsSpecial(std::string scheme) { +#define XX(name, _) if (scheme == name) return true; + SPECIALS(XX); +#undef XX + return false; +} + +// https://url.spec.whatwg.org/#start-with-a-windows-drive-letter +inline bool StartsWithWindowsDriveLetter(const char* p, const char* end) { + const size_t length = end - p; + return length >= 2 && + IsWindowsDriveLetter(p[0], p[1]) && + (length == 2 || + p[2] == '/' || + p[2] == '\\' || + p[2] == '?' || + p[2] == '#'); +} + +inline int NormalizePort(std::string scheme, int p) { +#define XX(name, port) if (scheme == name && p == port) return -1; + SPECIALS(XX); +#undef XX + return p; +} + +#if defined(NODE_HAVE_I18N_SUPPORT) +inline bool ToUnicode(const std::string& input, std::string* output) { + MaybeStackBuffer buf; + if (i18n::ToUnicode(&buf, input.c_str(), input.length()) < 0) + return false; + output->assign(*buf, buf.length()); + return true; +} + +inline bool ToASCII(const std::string& input, std::string* output) { + MaybeStackBuffer buf; + if (i18n::ToASCII(&buf, input.c_str(), input.length()) < 0) + return false; + output->assign(*buf, buf.length()); + return true; +} +#else +// Intentional non-ops if ICU is not present. +inline bool ToUnicode(const std::string& input, std::string* output) { + *output = input; + return true; +} + +inline bool ToASCII(const std::string& input, std::string* output) { + *output = input; + return true; +} +#endif + +void URLHost::ParseIPv6Host(const char* input, size_t length) { + CHECK_EQ(type_, HostType::H_FAILED); + for (unsigned n = 0; n < 8; n++) + value_.ipv6[n] = 0; + uint16_t* piece_pointer = &value_.ipv6[0]; + uint16_t* const buffer_end = piece_pointer + 8; + uint16_t* compress_pointer = nullptr; + const char* pointer = input; + const char* end = pointer + length; + unsigned value, len, swaps, numbers_seen; + char ch = pointer < end ? pointer[0] : kEOL; + if (ch == ':') { + if (length < 2 || pointer[1] != ':') + return; + pointer += 2; + ch = pointer < end ? pointer[0] : kEOL; + piece_pointer++; + compress_pointer = piece_pointer; + } + while (ch != kEOL) { + if (piece_pointer >= buffer_end) + return; + if (ch == ':') { + if (compress_pointer != nullptr) + return; + pointer++; + ch = pointer < end ? pointer[0] : kEOL; + piece_pointer++; + compress_pointer = piece_pointer; + continue; + } + value = 0; + len = 0; + while (len < 4 && IsASCIIHexDigit(ch)) { + value = value * 0x10 + hex2bin(ch); + pointer++; + ch = pointer < end ? pointer[0] : kEOL; + len++; + } + switch (ch) { + case '.': + if (len == 0) + return; + pointer -= len; + ch = pointer < end ? pointer[0] : kEOL; + if (piece_pointer > buffer_end - 2) + return; + numbers_seen = 0; + while (ch != kEOL) { + value = 0xffffffff; + if (numbers_seen > 0) { + if (ch == '.' && numbers_seen < 4) { + pointer++; + ch = pointer < end ? pointer[0] : kEOL; + } else { + return; + } + } + if (!IsASCIIDigit(ch)) + return; + while (IsASCIIDigit(ch)) { + unsigned number = ch - '0'; + if (value == 0xffffffff) { + value = number; + } else if (value == 0) { + return; + } else { + value = value * 10 + number; + } + if (value > 255) + return; + pointer++; + ch = pointer < end ? pointer[0] : kEOL; + } + *piece_pointer = *piece_pointer * 0x100 + value; + numbers_seen++; + if (numbers_seen == 2 || numbers_seen == 4) + piece_pointer++; + } + if (numbers_seen != 4) + return; + continue; + case ':': + pointer++; + ch = pointer < end ? pointer[0] : kEOL; + if (ch == kEOL) + return; + break; + case kEOL: + break; + default: + return; + } + *piece_pointer = value; + piece_pointer++; + } + + if (compress_pointer != nullptr) { + swaps = piece_pointer - compress_pointer; + piece_pointer = buffer_end - 1; + while (piece_pointer != &value_.ipv6[0] && swaps > 0) { + uint16_t temp = *piece_pointer; + uint16_t* swap_piece = compress_pointer + swaps - 1; + *piece_pointer = *swap_piece; + *swap_piece = temp; + piece_pointer--; + swaps--; + } + } else if (compress_pointer == nullptr && + piece_pointer != buffer_end) { + return; + } + type_ = HostType::H_IPV6; +} + +inline int64_t ParseNumber(const char* start, const char* end) { + unsigned R = 10; + if (end - start >= 2 && start[0] == '0' && (start[1] | 0x20) == 'x') { + start += 2; + R = 16; + } + if (end - start == 0) { + return 0; + } else if (R == 10 && end - start > 1 && start[0] == '0') { + start++; + R = 8; + } + const char* p = start; + + while (p < end) { + const char ch = p[0]; + switch (R) { + case 8: + if (ch < '0' || ch > '7') + return -1; + break; + case 10: + if (!IsASCIIDigit(ch)) + return -1; + break; + case 16: + if (!IsASCIIHexDigit(ch)) + return -1; + break; + } + p++; + } + return strtoll(start, NULL, R); +} + +void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) { + CHECK_EQ(type_, HostType::H_FAILED); + *is_ipv4 = false; + const char* pointer = input; + const char* mark = input; + const char* end = pointer + length; + int parts = 0; + uint32_t val = 0; + uint64_t numbers[4]; + int tooBigNumbers = 0; + if (length == 0) + return; + + while (pointer <= end) { + const char ch = pointer < end ? pointer[0] : kEOL; + const int remaining = end - pointer - 1; + if (ch == '.' || ch == kEOL) { + if (++parts > 4) + return; + if (pointer == mark) + return; + int64_t n = ParseNumber(mark, pointer); + if (n < 0) + return; + + if (n > 255) { + tooBigNumbers++; + } + numbers[parts - 1] = n; + mark = pointer + 1; + if (ch == '.' && remaining == 0) + break; + } + pointer++; + } + CHECK_GT(parts, 0); + *is_ipv4 = true; + + // If any but the last item in numbers is greater than 255, return failure. + // If the last item in numbers is greater than or equal to + // 256^(5 - the number of items in numbers), return failure. + if (tooBigNumbers > 1 || + (tooBigNumbers == 1 && numbers[parts - 1] <= 255) || + numbers[parts - 1] >= pow(256, static_cast(5 - parts))) { + return; + } + + type_ = HostType::H_IPV4; + val = numbers[parts - 1]; + for (int n = 0; n < parts - 1; n++) { + double b = 3 - n; + val += numbers[n] * pow(256, b); + } + + value_.ipv4 = val; +} + +void URLHost::ParseOpaqueHost(const char* input, size_t length) { + CHECK_EQ(type_, HostType::H_FAILED); + std::string output; + output.reserve(length * 3); + for (size_t i = 0; i < length; i++) { + const char ch = input[i]; + if (ch != '%' && IsForbiddenHostCodePoint(ch)) { + return; + } else { + AppendOrEscape(&output, ch, C0_CONTROL_ENCODE_SET); + } + } + + SetOpaque(&output); +} + +void URLHost::ParseHost(const char* input, + size_t length, + bool is_special, + bool unicode) { + CHECK_EQ(type_, HostType::H_FAILED); + const char* pointer = input; + + if (length == 0) + return; + + if (pointer[0] == '[') { + if (pointer[length - 1] != ']') + return; + return ParseIPv6Host(++pointer, length - 2); + } + + if (!is_special) + return ParseOpaqueHost(input, length); + + // First, we have to percent decode + std::string decoded = PercentDecode(input, length); + + // Then we have to punycode toASCII + if (!ToASCII(decoded, &decoded)) + return; + + // If any of the following characters are still present, we have to fail + for (size_t n = 0; n < decoded.size(); n++) { + const char ch = decoded[n]; + if (IsForbiddenHostCodePoint(ch)) { + return; + } + } + + // Check to see if it's an IPv4 IP address + bool is_ipv4; + ParseIPv4Host(decoded.c_str(), decoded.length(), &is_ipv4); + if (is_ipv4) + return; + + // If the unicode flag is set, run the result through punycode ToUnicode + if (unicode && !ToUnicode(decoded, &decoded)) + return; + + // It's not an IPv4 or IPv6 address, it must be a domain + SetDomain(&decoded); +} + +// Locates the longest sequence of 0 segments in an IPv6 address +// in order to use the :: compression when serializing +template +inline T* FindLongestZeroSequence(T* values, size_t len) { + T* start = values; + T* end = start + len; + T* result = nullptr; + + T* current = nullptr; + unsigned counter = 0, longest = 1; + + while (start < end) { + if (*start == 0) { + if (current == nullptr) + current = start; + counter++; + } else { + if (counter > longest) { + longest = counter; + result = current; + } + counter = 0; + current = nullptr; + } + start++; + } + if (counter > longest) + result = current; + return result; +} + +std::string URLHost::ToString() const { + std::string dest; + switch (type_) { + case HostType::H_DOMAIN: + return value_.domain; + break; + case HostType::H_OPAQUE: + return value_.opaque; + break; + case HostType::H_IPV4: { + dest.reserve(15); + uint32_t value = value_.ipv4; + for (int n = 0; n < 4; n++) { + char buf[4]; + snprintf(buf, sizeof(buf), "%d", value % 256); + dest.insert(0, buf); + if (n < 3) + dest.insert(0, 1, '.'); + value /= 256; + } + break; + } + case HostType::H_IPV6: { + dest.reserve(41); + dest += '['; + const uint16_t* start = &value_.ipv6[0]; + const uint16_t* compress_pointer = + FindLongestZeroSequence(start, 8); + bool ignore0 = false; + for (int n = 0; n <= 7; n++) { + const uint16_t* piece = &value_.ipv6[n]; + if (ignore0 && *piece == 0) + continue; + else if (ignore0) + ignore0 = false; + if (compress_pointer == piece) { + dest += n == 0 ? "::" : ":"; + ignore0 = true; + continue; + } + char buf[5]; + snprintf(buf, sizeof(buf), "%x", *piece); + dest += buf; + if (n < 7) + dest += ':'; + } + dest += ']'; + break; + } + case HostType::H_FAILED: + break; + } + return dest; +} + +bool ParseHost(const std::string& input, + std::string* output, + bool is_special, + bool unicode = false) { + if (input.length() == 0) { + output->clear(); + return true; + } + URLHost host; + host.ParseHost(input.c_str(), input.length(), is_special, unicode); + if (host.ParsingFailed()) + return false; + *output = host.ToString(); + return true; +} + +inline void Copy(Environment* env, + Local ary, + std::vector* vec) { + const int32_t len = ary->Length(); + if (len == 0) + return; // nothing to copy + vec->reserve(len); + for (int32_t n = 0; n < len; n++) { + Local val = ary->Get(env->context(), n).ToLocalChecked(); + if (val->IsString()) { + Utf8Value value(env->isolate(), val.As()); + vec->push_back(std::string(*value, value.length())); + } + } +} + +inline Local Copy(Environment* env, + const std::vector& vec) { + Isolate* isolate = env->isolate(); + Local ary = Array::New(isolate, vec.size()); + for (size_t n = 0; n < vec.size(); n++) + ary->Set(env->context(), n, UTF8STRING(isolate, vec[n])).FromJust(); + return ary; +} + +inline void HarvestBase(Environment* env, + struct url_data* base, + Local base_obj) { + Local context = env->context(); + Local flags = GET(env, base_obj, "flags"); + if (flags->IsInt32()) + base->flags = flags->Int32Value(context).FromJust(); + + Local scheme = GET(env, base_obj, "scheme"); + base->scheme = Utf8Value(env->isolate(), scheme).out(); + + GET_AND_SET(env, base_obj, username, base, URL_FLAGS_HAS_USERNAME); + GET_AND_SET(env, base_obj, password, base, URL_FLAGS_HAS_PASSWORD); + GET_AND_SET(env, base_obj, host, base, URL_FLAGS_HAS_HOST); + GET_AND_SET(env, base_obj, query, base, URL_FLAGS_HAS_QUERY); + GET_AND_SET(env, base_obj, fragment, base, URL_FLAGS_HAS_FRAGMENT); + Local port = GET(env, base_obj, "port"); + if (port->IsInt32()) + base->port = port->Int32Value(context).FromJust(); + Local path = GET(env, base_obj, "path"); + if (path->IsArray()) { + base->flags |= URL_FLAGS_HAS_PATH; + Copy(env, path.As(), &(base->path)); + } +} + +inline void HarvestContext(Environment* env, + struct url_data* context, + Local context_obj) { + Local flags = GET(env, context_obj, "flags"); + if (flags->IsInt32()) { + int32_t _flags = flags->Int32Value(env->context()).FromJust(); + if (_flags & URL_FLAGS_SPECIAL) + context->flags |= URL_FLAGS_SPECIAL; + if (_flags & URL_FLAGS_CANNOT_BE_BASE) + context->flags |= URL_FLAGS_CANNOT_BE_BASE; + if (_flags & URL_FLAGS_HAS_USERNAME) + context->flags |= URL_FLAGS_HAS_USERNAME; + if (_flags & URL_FLAGS_HAS_PASSWORD) + context->flags |= URL_FLAGS_HAS_PASSWORD; + if (_flags & URL_FLAGS_HAS_HOST) + context->flags |= URL_FLAGS_HAS_HOST; + } + Local scheme = GET(env, context_obj, "scheme"); + if (scheme->IsString()) { + Utf8Value value(env->isolate(), scheme); + context->scheme.assign(*value, value.length()); + } + Local port = GET(env, context_obj, "port"); + if (port->IsInt32()) + context->port = port->Int32Value(env->context()).FromJust(); + if (context->flags & URL_FLAGS_HAS_USERNAME) { + Local username = GET(env, context_obj, "username"); + CHECK(username->IsString()); + Utf8Value value(env->isolate(), username); + context->username.assign(*value, value.length()); + } + if (context->flags & URL_FLAGS_HAS_PASSWORD) { + Local password = GET(env, context_obj, "password"); + CHECK(password->IsString()); + Utf8Value value(env->isolate(), password); + context->password.assign(*value, value.length()); + } + Local host = GET(env, context_obj, "host"); + if (host->IsString()) { + Utf8Value value(env->isolate(), host); + context->host.assign(*value, value.length()); + } +} + +// Single dot segment can be ".", "%2e", or "%2E" +inline bool IsSingleDotSegment(const std::string& str) { + switch (str.size()) { + case 1: + return str == "."; + case 3: + return str[0] == '%' && + str[1] == '2' && + ASCIILowercase(str[2]) == 'e'; + default: + return false; + } +} + +// Double dot segment can be: +// "..", ".%2e", ".%2E", "%2e.", "%2E.", +// "%2e%2e", "%2E%2E", "%2e%2E", or "%2E%2e" +inline bool IsDoubleDotSegment(const std::string& str) { + switch (str.size()) { + case 2: + return str == ".."; + case 4: + if (str[0] != '.' && str[0] != '%') + return false; + return ((str[0] == '.' && + str[1] == '%' && + str[2] == '2' && + ASCIILowercase(str[3]) == 'e') || + (str[0] == '%' && + str[1] == '2' && + ASCIILowercase(str[2]) == 'e' && + str[3] == '.')); + case 6: + return (str[0] == '%' && + str[1] == '2' && + ASCIILowercase(str[2]) == 'e' && + str[3] == '%' && + str[4] == '2' && + ASCIILowercase(str[5]) == 'e'); + default: + return false; + } +} + +inline void ShortenUrlPath(struct url_data* url) { + if (url->path.empty()) return; + if (url->path.size() == 1 && url->scheme == "file:" && + IsNormalizedWindowsDriveLetter(url->path[0])) return; + url->path.pop_back(); +} + +} // anonymous namespace + +void URL::Parse(const char* input, + size_t len, + enum url_parse_state state_override, + struct url_data* url, + bool has_url, + const struct url_data* base, + bool has_base) { + const char* p = input; + const char* end = input + len; + + if (!has_url) { + for (const char* ptr = p; ptr < end; ptr++) { + if (IsC0ControlOrSpace(*ptr)) + p++; + else + break; + } + for (const char* ptr = end - 1; ptr >= p; ptr--) { + if (IsC0ControlOrSpace(*ptr)) + end--; + else + break; + } + len = end - p; + } + + std::string whitespace_stripped; + whitespace_stripped.reserve(len); + for (const char* ptr = p; ptr < end; ptr++) + if (!IsASCIITabOrNewline(*ptr)) + whitespace_stripped += *ptr; + + input = whitespace_stripped.c_str(); + len = whitespace_stripped.size(); + p = input; + end = input + len; + + bool atflag = false; + bool sbflag = false; + bool uflag = false; + + std::string buffer; + url->scheme.reserve(len); + url->username.reserve(len); + url->password.reserve(len); + url->host.reserve(len); + url->path.reserve(len); + url->query.reserve(len); + url->fragment.reserve(len); + buffer.reserve(len); + + // Set the initial parse state. + const bool has_state_override = state_override != kUnknownState; + enum url_parse_state state = has_state_override ? state_override : + kSchemeStart; + + if (state < kSchemeStart || state > kFragment) { + url->flags |= URL_FLAGS_INVALID_PARSE_STATE; + return; + } + + while (p <= end) { + const char ch = p < end ? p[0] : kEOL; + bool special = (url->flags & URL_FLAGS_SPECIAL); + bool cannot_be_base; + const bool special_back_slash = (special && ch == '\\'); + + switch (state) { + case kSchemeStart: + if (IsASCIIAlpha(ch)) { + buffer += ASCIILowercase(ch); + state = kScheme; + } else if (!has_state_override) { + state = kNoScheme; + continue; + } else { + url->flags |= URL_FLAGS_FAILED; + return; + } + break; + case kScheme: + if (IsASCIIAlphanumeric(ch) || ch == '+' || ch == '-' || ch == '.') { + buffer += ASCIILowercase(ch); + } else if (ch == ':' || (has_state_override && ch == kEOL)) { + if (has_state_override && buffer.size() == 0) { + url->flags |= URL_FLAGS_TERMINATED; + return; + } + buffer += ':'; + + bool new_is_special = IsSpecial(buffer); + + if (has_state_override) { + if ((special != new_is_special) || + ((buffer == "file:") && + ((url->flags & URL_FLAGS_HAS_USERNAME) || + (url->flags & URL_FLAGS_HAS_PASSWORD) || + (url->port != -1)))) { + url->flags |= URL_FLAGS_TERMINATED; + return; + } + + // File scheme && (host == empty or null) check left to JS-land + // as it can be done before even entering C++ binding. + } + + url->scheme = buffer; + url->port = NormalizePort(url->scheme, url->port); + if (new_is_special) { + url->flags |= URL_FLAGS_SPECIAL; + special = true; + } else { + url->flags &= ~URL_FLAGS_SPECIAL; + special = false; + } + buffer.clear(); + if (has_state_override) + return; + if (url->scheme == "file:") { + state = kFile; + } else if (special && + has_base && + url->scheme == base->scheme) { + state = kSpecialRelativeOrAuthority; + } else if (special) { + state = kSpecialAuthoritySlashes; + } else if (p[1] == '/') { + state = kPathOrAuthority; + p++; + } else { + url->flags |= URL_FLAGS_CANNOT_BE_BASE; + url->flags |= URL_FLAGS_HAS_PATH; + url->path.push_back(""); + state = kCannotBeBase; + } + } else if (!has_state_override) { + buffer.clear(); + state = kNoScheme; + p = input; + continue; + } else { + url->flags |= URL_FLAGS_FAILED; + return; + } + break; + case kNoScheme: + cannot_be_base = has_base && (base->flags & URL_FLAGS_CANNOT_BE_BASE); + if (!has_base || (cannot_be_base && ch != '#')) { + url->flags |= URL_FLAGS_FAILED; + return; + } else if (cannot_be_base && ch == '#') { + url->scheme = base->scheme; + if (IsSpecial(url->scheme)) { + url->flags |= URL_FLAGS_SPECIAL; + special = true; + } else { + url->flags &= ~URL_FLAGS_SPECIAL; + special = false; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + if (base->flags & URL_FLAGS_HAS_QUERY) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = base->query; + } + if (base->flags & URL_FLAGS_HAS_FRAGMENT) { + url->flags |= URL_FLAGS_HAS_FRAGMENT; + url->fragment = base->fragment; + } + url->flags |= URL_FLAGS_CANNOT_BE_BASE; + state = kFragment; + } else if (has_base && + base->scheme != "file:") { + state = kRelative; + continue; + } else { + url->scheme = "file:"; + url->flags |= URL_FLAGS_SPECIAL; + special = true; + state = kFile; + continue; + } + break; + case kSpecialRelativeOrAuthority: + if (ch == '/' && p[1] == '/') { + state = kSpecialAuthorityIgnoreSlashes; + p++; + } else { + state = kRelative; + continue; + } + break; + case kPathOrAuthority: + if (ch == '/') { + state = kAuthority; + } else { + state = kPath; + continue; + } + break; + case kRelative: + url->scheme = base->scheme; + if (IsSpecial(url->scheme)) { + url->flags |= URL_FLAGS_SPECIAL; + special = true; + } else { + url->flags &= ~URL_FLAGS_SPECIAL; + special = false; + } + switch (ch) { + case kEOL: + if (base->flags & URL_FLAGS_HAS_USERNAME) { + url->flags |= URL_FLAGS_HAS_USERNAME; + url->username = base->username; + } + if (base->flags & URL_FLAGS_HAS_PASSWORD) { + url->flags |= URL_FLAGS_HAS_PASSWORD; + url->password = base->password; + } + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_QUERY) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = base->query; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + url->port = base->port; + break; + case '/': + state = kRelativeSlash; + break; + case '?': + if (base->flags & URL_FLAGS_HAS_USERNAME) { + url->flags |= URL_FLAGS_HAS_USERNAME; + url->username = base->username; + } + if (base->flags & URL_FLAGS_HAS_PASSWORD) { + url->flags |= URL_FLAGS_HAS_PASSWORD; + url->password = base->password; + } + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + url->port = base->port; + state = kQuery; + break; + case '#': + if (base->flags & URL_FLAGS_HAS_USERNAME) { + url->flags |= URL_FLAGS_HAS_USERNAME; + url->username = base->username; + } + if (base->flags & URL_FLAGS_HAS_PASSWORD) { + url->flags |= URL_FLAGS_HAS_PASSWORD; + url->password = base->password; + } + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_QUERY) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = base->query; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + url->port = base->port; + state = kFragment; + break; + default: + if (special_back_slash) { + state = kRelativeSlash; + } else { + if (base->flags & URL_FLAGS_HAS_USERNAME) { + url->flags |= URL_FLAGS_HAS_USERNAME; + url->username = base->username; + } + if (base->flags & URL_FLAGS_HAS_PASSWORD) { + url->flags |= URL_FLAGS_HAS_PASSWORD; + url->password = base->password; + } + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + ShortenUrlPath(url); + } + url->port = base->port; + state = kPath; + continue; + } + } + break; + case kRelativeSlash: + if (IsSpecial(url->scheme) && (ch == '/' || ch == '\\')) { + state = kSpecialAuthorityIgnoreSlashes; + } else if (ch == '/') { + state = kAuthority; + } else { + if (base->flags & URL_FLAGS_HAS_USERNAME) { + url->flags |= URL_FLAGS_HAS_USERNAME; + url->username = base->username; + } + if (base->flags & URL_FLAGS_HAS_PASSWORD) { + url->flags |= URL_FLAGS_HAS_PASSWORD; + url->password = base->password; + } + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + url->port = base->port; + state = kPath; + continue; + } + break; + case kSpecialAuthoritySlashes: + state = kSpecialAuthorityIgnoreSlashes; + if (ch == '/' && p[1] == '/') { + p++; + } else { + continue; + } + break; + case kSpecialAuthorityIgnoreSlashes: + if (ch != '/' && ch != '\\') { + state = kAuthority; + continue; + } + break; + case kAuthority: + if (ch == '@') { + if (atflag) { + buffer.reserve(buffer.size() + 3); + buffer.insert(0, "%40"); + } + atflag = true; + const size_t blen = buffer.size(); + if (blen > 0 && buffer[0] != ':') { + url->flags |= URL_FLAGS_HAS_USERNAME; + } + for (size_t n = 0; n < blen; n++) { + const char bch = buffer[n]; + if (bch == ':') { + url->flags |= URL_FLAGS_HAS_PASSWORD; + if (!uflag) { + uflag = true; + continue; + } + } + if (uflag) { + AppendOrEscape(&url->password, bch, USERINFO_ENCODE_SET); + } else { + AppendOrEscape(&url->username, bch, USERINFO_ENCODE_SET); + } + } + buffer.clear(); + } else if (ch == kEOL || + ch == '/' || + ch == '?' || + ch == '#' || + special_back_slash) { + if (atflag && buffer.size() == 0) { + url->flags |= URL_FLAGS_FAILED; + return; + } + p -= buffer.size() + 1; + buffer.clear(); + state = kHost; + } else { + buffer += ch; + } + break; + case kHost: + case kHostname: + if (has_state_override && url->scheme == "file:") { + state = kFileHost; + continue; + } else if (ch == ':' && !sbflag) { + if (buffer.size() == 0) { + url->flags |= URL_FLAGS_FAILED; + return; + } + url->flags |= URL_FLAGS_HAS_HOST; + if (!ParseHost(buffer, &url->host, special)) { + url->flags |= URL_FLAGS_FAILED; + return; + } + buffer.clear(); + state = kPort; + if (state_override == kHostname) { + return; + } + } else if (ch == kEOL || + ch == '/' || + ch == '?' || + ch == '#' || + special_back_slash) { + p--; + if (special && buffer.size() == 0) { + url->flags |= URL_FLAGS_FAILED; + return; + } + if (has_state_override && + buffer.size() == 0 && + ((url->username.size() > 0 || url->password.size() > 0) || + url->port != -1)) { + url->flags |= URL_FLAGS_TERMINATED; + return; + } + url->flags |= URL_FLAGS_HAS_HOST; + if (!ParseHost(buffer, &url->host, special)) { + url->flags |= URL_FLAGS_FAILED; + return; + } + buffer.clear(); + state = kPathStart; + if (has_state_override) { + return; + } + } else { + if (ch == '[') + sbflag = true; + if (ch == ']') + sbflag = false; + buffer += ch; + } + break; + case kPort: + if (IsASCIIDigit(ch)) { + buffer += ch; + } else if (has_state_override || + ch == kEOL || + ch == '/' || + ch == '?' || + ch == '#' || + special_back_slash) { + if (buffer.size() > 0) { + unsigned port = 0; + // the condition port <= 0xffff prevents integer overflow + for (size_t i = 0; port <= 0xffff && i < buffer.size(); i++) + port = port * 10 + buffer[i] - '0'; + if (port > 0xffff) { + // TODO(TimothyGu): This hack is currently needed for the host + // setter since it needs access to hostname if it is valid, and + // if the FAILED flag is set the entire response to JS layer + // will be empty. + if (state_override == kHost) + url->port = -1; + else + url->flags |= URL_FLAGS_FAILED; + return; + } + // the port is valid + url->port = NormalizePort(url->scheme, static_cast(port)); + buffer.clear(); + } else if (has_state_override) { + // TODO(TimothyGu): Similar case as above. + if (state_override == kHost) + url->port = -1; + else + url->flags |= URL_FLAGS_TERMINATED; + return; + } + state = kPathStart; + continue; + } else { + url->flags |= URL_FLAGS_FAILED; + return; + } + break; + case kFile: + url->scheme = "file:"; + if (ch == '/' || ch == '\\') { + state = kFileSlash; + } else if (has_base && base->scheme == "file:") { + switch (ch) { + case kEOL: + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + if (base->flags & URL_FLAGS_HAS_QUERY) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = base->query; + } + break; + case '?': + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + url->flags |= URL_FLAGS_HAS_QUERY; + url->query.clear(); + state = kQuery; + break; + case '#': + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + if (base->flags & URL_FLAGS_HAS_QUERY) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = base->query; + } + url->flags |= URL_FLAGS_HAS_FRAGMENT; + url->fragment.clear(); + state = kFragment; + break; + default: + if (!StartsWithWindowsDriveLetter(p, end)) { + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } + if (base->flags & URL_FLAGS_HAS_PATH) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path = base->path; + } + ShortenUrlPath(url); + } + state = kPath; + continue; + } + } else { + state = kPath; + continue; + } + break; + case kFileSlash: + if (ch == '/' || ch == '\\') { + state = kFileHost; + } else { + if (has_base && + base->scheme == "file:" && + !StartsWithWindowsDriveLetter(p, end)) { + if (IsNormalizedWindowsDriveLetter(base->path[0])) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path.push_back(base->path[0]); + } else { + if (base->flags & URL_FLAGS_HAS_HOST) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host = base->host; + } else { + url->flags &= ~URL_FLAGS_HAS_HOST; + url->host.clear(); + } + } + } + state = kPath; + continue; + } + break; + case kFileHost: + if (ch == kEOL || + ch == '/' || + ch == '\\' || + ch == '?' || + ch == '#') { + if (!has_state_override && + buffer.size() == 2 && + IsWindowsDriveLetter(buffer)) { + state = kPath; + } else if (buffer.size() == 0) { + url->flags |= URL_FLAGS_HAS_HOST; + url->host.clear(); + if (has_state_override) + return; + state = kPathStart; + } else { + std::string host; + if (!ParseHost(buffer, &host, special)) { + url->flags |= URL_FLAGS_FAILED; + return; + } + if (host == "localhost") + host.clear(); + url->flags |= URL_FLAGS_HAS_HOST; + url->host = host; + if (has_state_override) + return; + buffer.clear(); + state = kPathStart; + } + continue; + } else { + buffer += ch; + } + break; + case kPathStart: + if (IsSpecial(url->scheme)) { + state = kPath; + if (ch != '/' && ch != '\\') { + continue; + } + } else if (!has_state_override && ch == '?') { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query.clear(); + state = kQuery; + } else if (!has_state_override && ch == '#') { + url->flags |= URL_FLAGS_HAS_FRAGMENT; + url->fragment.clear(); + state = kFragment; + } else if (ch != kEOL) { + state = kPath; + if (ch != '/') { + continue; + } + } + break; + case kPath: + if (ch == kEOL || + ch == '/' || + special_back_slash || + (!has_state_override && (ch == '?' || ch == '#'))) { + if (IsDoubleDotSegment(buffer)) { + ShortenUrlPath(url); + if (ch != '/' && !special_back_slash) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path.push_back(""); + } + } else if (IsSingleDotSegment(buffer) && + ch != '/' && !special_back_slash) { + url->flags |= URL_FLAGS_HAS_PATH; + url->path.push_back(""); + } else if (!IsSingleDotSegment(buffer)) { + if (url->scheme == "file:" && + url->path.empty() && + buffer.size() == 2 && + IsWindowsDriveLetter(buffer)) { + if ((url->flags & URL_FLAGS_HAS_HOST) && + !url->host.empty()) { + url->host.clear(); + url->flags |= URL_FLAGS_HAS_HOST; + } + buffer[1] = ':'; + } + url->flags |= URL_FLAGS_HAS_PATH; + std::string segment(buffer.c_str(), buffer.size()); + url->path.push_back(segment); + } + buffer.clear(); + if (url->scheme == "file:" && + (ch == kEOL || + ch == '?' || + ch == '#')) { + while (url->path.size() > 1 && url->path[0].length() == 0) { + url->path.erase(url->path.begin()); + } + } + if (ch == '?') { + url->flags |= URL_FLAGS_HAS_QUERY; + state = kQuery; + } else if (ch == '#') { + state = kFragment; + } + } else { + AppendOrEscape(&buffer, ch, PATH_ENCODE_SET); + } + break; + case kCannotBeBase: + switch (ch) { + case '?': + state = kQuery; + break; + case '#': + state = kFragment; + break; + default: + if (url->path.size() == 0) + url->path.push_back(""); + if (url->path.size() > 0 && ch != kEOL) + AppendOrEscape(&url->path[0], ch, C0_CONTROL_ENCODE_SET); + } + break; + case kQuery: + if (ch == kEOL || (!has_state_override && ch == '#')) { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query = buffer; + buffer.clear(); + if (ch == '#') + state = kFragment; + } else { + AppendOrEscape(&buffer, ch, QUERY_ENCODE_SET); + } + break; + case kFragment: + switch (ch) { + case kEOL: + url->flags |= URL_FLAGS_HAS_FRAGMENT; + url->fragment = buffer; + break; + case 0: + break; + default: + AppendOrEscape(&buffer, ch, C0_CONTROL_ENCODE_SET); + } + break; + default: + url->flags |= URL_FLAGS_INVALID_PARSE_STATE; + return; + } + + p++; + } +} // NOLINT(readability/fn_size) + +static inline void SetArgs(Environment* env, + Local argv[], + const struct url_data* url) { + Isolate* isolate = env->isolate(); + argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url->flags); + argv[ARG_PROTOCOL] = OneByteString(isolate, url->scheme.c_str()); + if (url->flags & URL_FLAGS_HAS_USERNAME) + argv[ARG_USERNAME] = UTF8STRING(isolate, url->username); + if (url->flags & URL_FLAGS_HAS_PASSWORD) + argv[ARG_PASSWORD] = UTF8STRING(isolate, url->password); + if (url->flags & URL_FLAGS_HAS_HOST) + argv[ARG_HOST] = UTF8STRING(isolate, url->host); + if (url->flags & URL_FLAGS_HAS_QUERY) + argv[ARG_QUERY] = UTF8STRING(isolate, url->query); + if (url->flags & URL_FLAGS_HAS_FRAGMENT) + argv[ARG_FRAGMENT] = UTF8STRING(isolate, url->fragment); + if (url->port > -1) + argv[ARG_PORT] = Integer::New(isolate, url->port); + if (url->flags & URL_FLAGS_HAS_PATH) + argv[ARG_PATH] = Copy(env, url->path); +} + +static void Parse(Environment* env, + Local recv, + const char* input, + const size_t len, + enum url_parse_state state_override, + Local base_obj, + Local context_obj, + Local cb, + Local error_cb) { + Isolate* isolate = env->isolate(); + Local context = env->context(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + + const bool has_context = context_obj->IsObject(); + const bool has_base = base_obj->IsObject(); + + struct url_data base; + struct url_data url; + if (has_context) + HarvestContext(env, &url, context_obj.As()); + if (has_base) + HarvestBase(env, &base, base_obj.As()); + + URL::Parse(input, len, state_override, &url, has_context, &base, has_base); + if ((url.flags & URL_FLAGS_INVALID_PARSE_STATE) || + ((state_override != kUnknownState) && + (url.flags & URL_FLAGS_TERMINATED))) + return; + + // Define the return value placeholders + const Local undef = Undefined(isolate); + const Local null = Null(isolate); + if (!(url.flags & URL_FLAGS_FAILED)) { + Local argv[9] = { + undef, + undef, + undef, + undef, + null, // host defaults to null + null, // port defaults to null + undef, + null, // query defaults to null + null, // fragment defaults to null + }; + SetArgs(env, argv, &url); + cb->Call(context, recv, arraysize(argv), argv).FromMaybe(Local()); + } else if (error_cb->IsFunction()) { + Local argv[2] = { undef, undef }; + argv[ERR_ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url.flags); + argv[ERR_ARG_INPUT] = + String::NewFromUtf8(env->isolate(), + input, + v8::NewStringType::kNormal).ToLocalChecked(); + error_cb.As()->Call(context, recv, arraysize(argv), argv) + .FromMaybe(Local()); + } +} + +static void Parse(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 5); + CHECK(args[0]->IsString()); // input + CHECK(args[2]->IsUndefined() || // base context + args[2]->IsNull() || + args[2]->IsObject()); + CHECK(args[3]->IsUndefined() || // context + args[3]->IsNull() || + args[3]->IsObject()); + CHECK(args[4]->IsFunction()); // complete callback + CHECK(args[5]->IsUndefined() || args[5]->IsFunction()); // error callback + + Utf8Value input(env->isolate(), args[0]); + enum url_parse_state state_override = kUnknownState; + if (args[1]->IsNumber()) { + state_override = static_cast( + args[1]->Uint32Value(env->context()).FromJust()); + } + + Parse(env, args.This(), + *input, input.length(), + state_override, + args[2], + args[3], + args[4].As(), + args[5]); +} + +static void EncodeAuthSet(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 1); + CHECK(args[0]->IsString()); + Utf8Value value(env->isolate(), args[0]); + std::string output; + const size_t len = value.length(); + output.reserve(len); + for (size_t n = 0; n < len; n++) { + const char ch = (*value)[n]; + AppendOrEscape(&output, ch, USERINFO_ENCODE_SET); + } + args.GetReturnValue().Set( + String::NewFromUtf8(env->isolate(), + output.c_str(), + v8::NewStringType::kNormal).ToLocalChecked()); +} + +static void ToUSVString(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 2); + CHECK(args[0]->IsString()); + CHECK(args[1]->IsNumber()); + + TwoByteValue value(env->isolate(), args[0]); + const size_t n = value.length(); + + const int64_t start = args[1]->IntegerValue(env->context()).FromJust(); + CHECK_GE(start, 0); + + for (size_t i = start; i < n; i++) { + char16_t c = value[i]; + if (!IsUnicodeSurrogate(c)) { + continue; + } else if (IsUnicodeSurrogateTrail(c) || i == n - 1) { + value[i] = kUnicodeReplacementCharacter; + } else { + char16_t d = value[i + 1]; + if (IsUnicodeTrail(d)) { + i++; + } else { + value[i] = kUnicodeReplacementCharacter; + } + } + } + + args.GetReturnValue().Set( + String::NewFromTwoByte(env->isolate(), + *value, + v8::NewStringType::kNormal, + n).ToLocalChecked()); +} + +static void DomainToASCII(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 1); + CHECK(args[0]->IsString()); + Utf8Value value(env->isolate(), args[0]); + + URLHost host; + // Assuming the host is used for a special scheme. + host.ParseHost(*value, value.length(), true); + if (host.ParsingFailed()) { + args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), "")); + return; + } + std::string out = host.ToString(); + args.GetReturnValue().Set( + String::NewFromUtf8(env->isolate(), + out.c_str(), + v8::NewStringType::kNormal).ToLocalChecked()); +} + +static void DomainToUnicode(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 1); + CHECK(args[0]->IsString()); + Utf8Value value(env->isolate(), args[0]); + + URLHost host; + // Assuming the host is used for a special scheme. + host.ParseHost(*value, value.length(), true, true); + if (host.ParsingFailed()) { + args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), "")); + return; + } + std::string out = host.ToString(); + args.GetReturnValue().Set( + String::NewFromUtf8(env->isolate(), + out.c_str(), + v8::NewStringType::kNormal).ToLocalChecked()); +} + +std::string URL::ToFilePath() const { + if (context_.scheme != "file:") { + return ""; + } + +#ifdef _WIN32 + const char* slash = "\\"; + auto is_slash = [] (char ch) { + return ch == '/' || ch == '\\'; + }; +#else + const char* slash = "/"; + auto is_slash = [] (char ch) { + return ch == '/'; + }; + if ((context_.flags & URL_FLAGS_HAS_HOST) && + context_.host.length() > 0) { + return ""; + } +#endif + std::string decoded_path; + for (const std::string& part : context_.path) { + std::string decoded = PercentDecode(part.c_str(), part.length()); + for (char& ch : decoded) { + if (is_slash(ch)) { + return ""; + } + } + decoded_path += slash + decoded; + } + +#ifdef _WIN32 + // TODO(TimothyGu): Use "\\?\" long paths on Windows. + + // If hostname is set, then we have a UNC path. Pass the hostname through + // ToUnicode just in case it is an IDN using punycode encoding. We do not + // need to worry about percent encoding because the URL parser will have + // already taken care of that for us. Note that this only causes IDNs with an + // appropriate `xn--` prefix to be decoded. + if ((context_.flags & URL_FLAGS_HAS_HOST) && + context_.host.length() > 0) { + std::string unicode_host; + if (!ToUnicode(context_.host, &unicode_host)) { + return ""; + } + return "\\\\" + unicode_host + decoded_path; + } + // Otherwise, it's a local path that requires a drive letter. + if (decoded_path.length() < 3) { + return ""; + } + if (decoded_path[2] != ':' || + !IsASCIIAlpha(decoded_path[1])) { + return ""; + } + // Strip out the leading '\'. + return decoded_path.substr(1); +#else + return decoded_path; +#endif +} + +// This function works by calling out to a JS function that creates and +// returns the JS URL object. Be mindful of the JS<->Native boundary +// crossing that is required. +const Local URL::ToObject(Environment* env) const { + Isolate* isolate = env->isolate(); + Local context = env->context(); + Context::Scope context_scope(context); + + const Local undef = Undefined(isolate); + const Local null = Null(isolate); + + if (context_.flags & URL_FLAGS_FAILED) + return Local(); + + Local argv[9] = { + undef, + undef, + undef, + undef, + null, // host defaults to null + null, // port defaults to null + undef, + null, // query defaults to null + null, // fragment defaults to null + }; + SetArgs(env, argv, &context_); + + TryCatch try_catch(isolate); + + // The SetURLConstructor method must have been called already to + // set the constructor function used below. SetURLConstructor is + // called automatically when the internal/url.js module is loaded + // during the internal/bootstrap_node.js processing. + MaybeLocal ret = + env->url_constructor_function() + ->Call(env->context(), undef, 9, argv); + + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(isolate, try_catch); + } + + return ret.ToLocalChecked(); +} + +static void SetURLConstructor(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + env->set_url_constructor_function(args[0].As()); +} + +static void Init(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "parse", Parse); + env->SetMethod(target, "encodeAuth", EncodeAuthSet); + env->SetMethod(target, "toUSVString", ToUSVString); + env->SetMethod(target, "domainToASCII", DomainToASCII); + env->SetMethod(target, "domainToUnicode", DomainToUnicode); + env->SetMethod(target, "setURLConstructor", SetURLConstructor); + +#define XX(name, _) NODE_DEFINE_CONSTANT(target, name); + FLAGS(XX) +#undef XX + +#define XX(name) NODE_DEFINE_CONSTANT(target, name); + PARSESTATES(XX) +#undef XX +} +} // namespace url +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(url, node::url::Init) diff --git a/src/node_url.h b/src/node_url.h new file mode 100644 index 00000000000000..6b526d15b07703 --- /dev/null +++ b/src/node_url.h @@ -0,0 +1,191 @@ +#ifndef SRC_NODE_URL_H_ +#define SRC_NODE_URL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "env-inl.h" + +#include + +namespace node { +namespace url { + +using v8::Local; +using v8::Value; + + +#define PARSESTATES(XX) \ + XX(kSchemeStart) \ + XX(kScheme) \ + XX(kNoScheme) \ + XX(kSpecialRelativeOrAuthority) \ + XX(kPathOrAuthority) \ + XX(kRelative) \ + XX(kRelativeSlash) \ + XX(kSpecialAuthoritySlashes) \ + XX(kSpecialAuthorityIgnoreSlashes) \ + XX(kAuthority) \ + XX(kHost) \ + XX(kHostname) \ + XX(kPort) \ + XX(kFile) \ + XX(kFileSlash) \ + XX(kFileHost) \ + XX(kPathStart) \ + XX(kPath) \ + XX(kCannotBeBase) \ + XX(kQuery) \ + XX(kFragment) + +#define FLAGS(XX) \ + XX(URL_FLAGS_NONE, 0) \ + XX(URL_FLAGS_FAILED, 0x01) \ + XX(URL_FLAGS_CANNOT_BE_BASE, 0x02) \ + XX(URL_FLAGS_INVALID_PARSE_STATE, 0x04) \ + XX(URL_FLAGS_TERMINATED, 0x08) \ + XX(URL_FLAGS_SPECIAL, 0x10) \ + XX(URL_FLAGS_HAS_USERNAME, 0x20) \ + XX(URL_FLAGS_HAS_PASSWORD, 0x40) \ + XX(URL_FLAGS_HAS_HOST, 0x80) \ + XX(URL_FLAGS_HAS_PATH, 0x100) \ + XX(URL_FLAGS_HAS_QUERY, 0x200) \ + XX(URL_FLAGS_HAS_FRAGMENT, 0x400) + +enum url_parse_state { + kUnknownState = -1, +#define XX(name) name, + PARSESTATES(XX) +#undef XX +}; + +enum url_flags { +#define XX(name, val) name = val, + FLAGS(XX) +#undef XX +}; + +struct url_data { + int32_t flags = URL_FLAGS_NONE; + int port = -1; + std::string scheme; + std::string username; + std::string password; + std::string host; + std::string query; + std::string fragment; + std::vector path; +}; + +class URL { + public: + static void Parse(const char* input, + size_t len, + enum url_parse_state state_override, + struct url_data* url, + bool has_url, + const struct url_data* base, + bool has_base); + + URL(const char* input, const size_t len) { + Parse(input, len, kUnknownState, &context_, false, nullptr, false); + } + + URL(const char* input, const size_t len, const URL* base) { + if (base != nullptr) + Parse(input, len, kUnknownState, + &context_, false, + &(base->context_), true); + else + Parse(input, len, kUnknownState, &context_, false, nullptr, false); + } + + URL(const char* input, const size_t len, + const char* base, const size_t baselen) { + if (base != nullptr && baselen > 0) { + URL _base(base, baselen); + Parse(input, len, kUnknownState, + &context_, false, + &(_base.context_), true); + } else { + Parse(input, len, kUnknownState, &context_, false, nullptr, false); + } + } + + explicit URL(std::string input) : + URL(input.c_str(), input.length()) {} + + URL(std::string input, const URL* base) : + URL(input.c_str(), input.length(), base) {} + + URL(std::string input, const URL& base) : + URL(input.c_str(), input.length(), &base) {} + + URL(std::string input, std::string base) : + URL(input.c_str(), input.length(), base.c_str(), base.length()) {} + + int32_t flags() { + return context_.flags; + } + + int port() { + return context_.port; + } + + const std::string& protocol() const { + return context_.scheme; + } + + const std::string& username() const { + return context_.username; + } + + const std::string& password() const { + return context_.password; + } + + const std::string& host() const { + return context_.host; + } + + const std::string& query() const { + return context_.query; + } + + const std::string& fragment() const { + return context_.fragment; + } + + std::string path() const { + std::string ret; + for (auto i = context_.path.begin(); i != context_.path.end(); i++) { + ret += '/'; + ret += *i; + } + return ret; + } + + // Get the path of the file: URL in a format consumable by native file system + // APIs. Returns an empty string if something went wrong. + std::string ToFilePath() const; + + const Local ToObject(Environment* env) const; + + URL(const URL&) = default; + URL& operator=(const URL&) = default; + URL(URL&&) = default; + URL& operator=(URL&&) = default; + + URL() : URL("") {} + + private: + struct url_data context_; +}; + +} // namespace url + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_URL_H_ diff --git a/src/node_util.cc b/src/node_util.cc index f96e91483857c7..8b093ab842869d 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -54,6 +54,12 @@ static void GetProxyDetails(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ret); } +// Side effect-free stringification that will never throw exceptions. +static void SafeToString(const FunctionCallbackInfo& args) { + auto context = args.GetIsolate()->GetCurrentContext(); + args.GetReturnValue().Set(args[0]->ToDetailString(context).ToLocalChecked()); +} + static void GetHiddenValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -122,6 +128,7 @@ void Initialize(Local target, env->SetMethod(target, "getHiddenValue", GetHiddenValue); env->SetMethod(target, "setHiddenValue", SetHiddenValue); env->SetMethod(target, "getProxyDetails", GetProxyDetails); + env->SetMethod(target, "safeToString", SafeToString); env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog); env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog); diff --git a/src/node_version.h b/src/node_version.h index aa7fa64d8737aa..69b813973694c4 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -2,13 +2,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 6 -#define NODE_MINOR_VERSION 12 -#define NODE_PATCH_VERSION 4 +#define NODE_MINOR_VERSION 13 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 1 #define NODE_VERSION_LTS_CODENAME "Boron" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 09c954b9c783e6..885e4b006e157c 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -73,6 +73,7 @@ StreamWrap::StreamWrap(Environment* env, void StreamWrap::AddMethods(Environment* env, v8::Local target, int flags) { + env->SetProtoMethod(target, "updateWriteQueueSize", UpdateWriteQueueSize); env->SetProtoMethod(target, "setBlocking", SetBlocking); StreamBase::AddMethods(env, target, flags); } @@ -113,11 +114,14 @@ bool StreamWrap::IsIPCPipe() { } -void StreamWrap::UpdateWriteQueueSize() { +uint32_t StreamWrap::UpdateWriteQueueSize() { HandleScope scope(env()->isolate()); - Local write_queue_size = - Integer::NewFromUnsigned(env()->isolate(), stream()->write_queue_size); - object()->Set(env()->write_queue_size_string(), write_queue_size); + uint32_t write_queue_size = stream()->write_queue_size; + object()->Set(env()->context(), + env()->write_queue_size_string(), + Integer::NewFromUnsigned(env()->isolate(), + write_queue_size)).FromJust(); + return write_queue_size; } @@ -242,6 +246,16 @@ void StreamWrap::OnRead(uv_stream_t* handle, } +void StreamWrap::UpdateWriteQueueSize( + const FunctionCallbackInfo& args) { + StreamWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + uint32_t write_queue_size = wrap->UpdateWriteQueueSize(); + args.GetReturnValue().Set(write_queue_size); +} + + void StreamWrap::SetBlocking(const FunctionCallbackInfo& args) { StreamWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); diff --git a/src/stream_wrap.h b/src/stream_wrap.h index 3b2ce8ee3beb00..3b076373e5abf0 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -67,13 +67,15 @@ class StreamWrap : public HandleWrap, public StreamBase { } AsyncWrap* GetAsyncWrap() override; - void UpdateWriteQueueSize(); + uint32_t UpdateWriteQueueSize(); static void AddMethods(Environment* env, v8::Local target, int flags = StreamBase::kFlagNone); private: + static void UpdateWriteQueueSize( + const v8::FunctionCallbackInfo& args); static void SetBlocking(const v8::FunctionCallbackInfo& args); // Callbacks for libuv diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index a0a2232884a805..bc98b2873d5e22 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -21,6 +21,7 @@ using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; +using v8::Integer; using v8::Local; using v8::Object; using v8::String; @@ -81,6 +82,19 @@ TLSWrap::~TLSWrap() { #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sni_context_.Reset(); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + // See test/parallel/test-tls-transport-destroy-after-own-gc.js: + // If this TLSWrap is garbage collected, we cannot allow callbacks to be + // called on this stream. + + if (stream_ == nullptr) + return; + stream_->set_destruct_cb({ nullptr, nullptr }); + stream_->set_after_write_cb({ nullptr, nullptr }); + stream_->set_alloc_cb({ nullptr, nullptr }); + stream_->set_read_cb({ nullptr, nullptr }); + stream_->set_destruct_cb({ nullptr, nullptr }); + stream_->Unconsume(); } @@ -276,6 +290,7 @@ void TLSWrap::EncOut() { // No data to write if (BIO_pending(enc_out_) == 0) { + UpdateWriteQueueSize(); if (clear_in_->Length() == 0) InvokeQueued(0); return; @@ -530,13 +545,29 @@ bool TLSWrap::IsClosing() { } +uint32_t TLSWrap::UpdateWriteQueueSize(uint32_t write_queue_size) { + HandleScope scope(env()->isolate()); + if (write_queue_size == 0) + write_queue_size = BIO_pending(enc_out_); + object()->Set(env()->context(), + env()->write_queue_size_string(), + Integer::NewFromUnsigned(env()->isolate(), + write_queue_size)).FromJust(); + return write_queue_size; +} + + int TLSWrap::ReadStart() { - return stream_->ReadStart(); + if (stream_ != nullptr) + return stream_->ReadStart(); + return 0; } int TLSWrap::ReadStop() { - return stream_->ReadStop(); + if (stream_ != nullptr) + return stream_->ReadStop(); + return 0; } @@ -570,8 +601,12 @@ int TLSWrap::DoWrite(WriteWrap* w, ClearOut(); // However, if there is any data that should be written to the socket, // the callback should not be invoked immediately - if (BIO_pending(enc_out_) == 0) + if (BIO_pending(enc_out_) == 0) { + // net.js expects writeQueueSize to be > 0 if the write isn't + // immediately flushed + UpdateWriteQueueSize(1); return stream_->DoWrite(w, bufs, count, send_handle); + } } // Queue callback to execute it on next tick @@ -621,13 +656,15 @@ int TLSWrap::DoWrite(WriteWrap* w, // Try writing data immediately EncOut(); + UpdateWriteQueueSize(); return 0; } void TLSWrap::OnAfterWriteImpl(WriteWrap* w, void* ctx) { - // Intentionally empty + TLSWrap* wrap = static_cast(ctx); + wrap->UpdateWriteQueueSize(); } @@ -891,6 +928,15 @@ int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +void TLSWrap::UpdateWriteQueueSize(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + uint32_t write_queue_size = wrap->UpdateWriteQueueSize(); + args.GetReturnValue().Set(write_queue_size); +} + + void TLSWrap::Initialize(Local target, Local unused, Local context) { @@ -912,6 +958,7 @@ void TLSWrap::Initialize(Local target, env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); env->SetProtoMethod(t, "destroySSL", DestroySSL); env->SetProtoMethod(t, "enableCertCb", EnableCertCb); + env->SetProtoMethod(t, "updateWriteQueueSize", UpdateWriteQueueSize); StreamBase::AddMethods(env, t, StreamBase::kFlagHasWritev); SSLWrap::AddMethods(env, t); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index dbb1a0199e4e66..8fdbde8a1bf3ab 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -111,6 +111,7 @@ class TLSWrap : public AsyncWrap, AsyncWrap* GetAsyncWrap() override; bool IsIPCPipe() override; + uint32_t UpdateWriteQueueSize(uint32_t write_queue_size = 0); // Resource implementation static void OnAfterWriteImpl(WriteWrap* w, void* ctx); @@ -119,7 +120,6 @@ class TLSWrap : public AsyncWrap, const uv_buf_t* buf, uv_handle_type pending, void* ctx); - static void OnAfterWriteSelf(WriteWrap* w, void* ctx); static void OnAllocSelf(size_t size, uv_buf_t* buf, void* ctx); static void OnReadSelf(ssize_t nread, const uv_buf_t* buf, @@ -166,6 +166,10 @@ class TLSWrap : public AsyncWrap, // If true - delivered EOF to the js-land, either after `close_notify`, or // after the `UV_EOF` on socket. bool eof_; + + private: + static void UpdateWriteQueueSize( + const v8::FunctionCallbackInfo& args); }; } // namespace node diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 7d2f8d7ebbbd5f..347ca8a3ac70ec 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -97,6 +97,7 @@ void UDPWrap::Initialize(Local target, GetSockOrPeerName); env->SetProtoMethod(t, "addMembership", AddMembership); env->SetProtoMethod(t, "dropMembership", DropMembership); + env->SetProtoMethod(t, "setMulticastInterface", SetMulticastInterface); env->SetProtoMethod(t, "setMulticastTTL", SetMulticastTTL); env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback); env->SetProtoMethod(t, "setBroadcast", SetBroadcast); @@ -208,6 +209,22 @@ X(SetMulticastLoopback, uv_udp_set_multicast_loop) #undef X +void UDPWrap::SetMulticastInterface(const FunctionCallbackInfo& args) { + UDPWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, + args.Holder(), + args.GetReturnValue().Set(UV_EBADF)); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); + + Utf8Value iface(args.GetIsolate(), args[0]); + + const char* iface_cstr = *iface; + + int err = uv_udp_set_multicast_interface(&wrap->handle_, iface_cstr); + args.GetReturnValue().Set(err); +} void UDPWrap::SetMembership(const FunctionCallbackInfo& args, uv_membership membership) { diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 8baa0235be12da..0faa22816f9640 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -28,6 +28,8 @@ class UDPWrap: public HandleWrap { static void RecvStop(const v8::FunctionCallbackInfo& args); static void AddMembership(const v8::FunctionCallbackInfo& args); static void DropMembership(const v8::FunctionCallbackInfo& args); + static void SetMulticastInterface( + const v8::FunctionCallbackInfo& args); static void SetMulticastTTL(const v8::FunctionCallbackInfo& args); static void SetMulticastLoopback( const v8::FunctionCallbackInfo& args); diff --git a/src/util.h b/src/util.h index 4ce25e4622f4b2..d1ada38ed57fd2 100644 --- a/src/util.h +++ b/src/util.h @@ -10,6 +10,7 @@ #include #include #include +#include // OSX 10.9 defaults to libc++ which provides a C++11 header. #if defined(__APPLE__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1090 @@ -310,29 +311,40 @@ class MaybeStackBuffer { return length_; } - // Call to make sure enough space for `storage` entries is available. - // There can only be 1 call to AllocateSufficientStorage or Invalidate - // per instance. + // Current maximum capacity of the buffer with which SetLength() can be used + // without first calling AllocateSufficientStorage(). + size_t capacity() const { + return IsAllocated() ? capacity_ : + IsInvalidated() ? 0 : kStackStorageSize; + } + + // Make sure enough space for `storage` entries is available. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called Invalidate() cannot be used. + // Content of the buffer in the range [0, length()) is preserved. void AllocateSufficientStorage(size_t storage) { - if (storage <= kStackStorageSize) { - buf_ = buf_st_; - } else { - buf_ = Malloc(storage); + CHECK(!IsInvalidated()); + if (storage > capacity()) { + bool was_allocated = IsAllocated(); + T* allocated_ptr = was_allocated ? buf_ : nullptr; + buf_ = Realloc(allocated_ptr, storage); + capacity_ = storage; + if (!was_allocated && length_ > 0) + memcpy(buf_, buf_st_, length_ * sizeof(buf_[0])); } - // Remember how much was allocated to check against that in SetLength(). length_ = storage; } void SetLength(size_t length) { - // length_ stores how much memory was allocated. - CHECK_LE(length, length_); + // capacity() returns how much memory is actually available. + CHECK_LE(length, capacity()); length_ = length; } void SetLengthAndZeroTerminate(size_t length) { - // length_ stores how much memory was allocated. - CHECK_LE(length + 1, length_); + // capacity() returns how much memory is actually available. + CHECK_LE(length + 1, capacity()); SetLength(length); // T() is 0 for integer types, nullptr for pointers, etc. @@ -340,15 +352,35 @@ class MaybeStackBuffer { } // Make derefencing this object return nullptr. - // Calling this is mutually exclusive with calling - // AllocateSufficientStorage. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called AllocateSufficientStorage() cannot + // be used. void Invalidate() { - CHECK_EQ(buf_, buf_st_); + CHECK(!IsAllocated()); length_ = 0; buf_ = nullptr; } - MaybeStackBuffer() : length_(0), buf_(buf_st_) { + // If the buffer is stored in the heap rather than on the stack. + bool IsAllocated() const { + return !IsInvalidated() && buf_ != buf_st_; + } + + // If Invalidate() has been called. + bool IsInvalidated() const { + return buf_ == nullptr; + } + + // Release ownership of the malloc'd buffer. + // Note: This does not free the buffer. + void Release() { + CHECK(IsAllocated()); + buf_ = buf_st_; + length_ = 0; + capacity_ = 0; + } + + MaybeStackBuffer() : length_(0), capacity_(0), buf_(buf_st_) { // Default to a zero-length, null-terminated buffer. buf_[0] = T(); } @@ -358,12 +390,14 @@ class MaybeStackBuffer { } ~MaybeStackBuffer() { - if (buf_ != buf_st_) + if (IsAllocated()) free(buf_); } private: size_t length_; + // capacity of the malloc'ed buf_ + size_t capacity_; T* buf_; T buf_st_[kStackStorageSize]; }; diff --git a/test/cctest/node_test_fixture.h b/test/cctest/node_test_fixture.h index e32e7e6dc6b3ba..5460a26e3764ad 100644 --- a/test/cctest/node_test_fixture.h +++ b/test/cctest/node_test_fixture.h @@ -34,8 +34,8 @@ struct Argv { snprintf(argv_[0], prog_len, "%s", prog); snprintf(argv_[0] + prog_len, arg1_len, "%s", arg1); snprintf(argv_[0] + prog_len + arg1_len, arg2_len, "%s", arg2); - argv_[1] = argv_[0] + prog_len + 1; - argv_[2] = argv_[0] + prog_len + arg1_len + 1; + argv_[1] = argv_[0] + prog_len; + argv_[2] = argv_[0] + prog_len + arg1_len; } ~Argv() { diff --git a/test/cctest/test_url.cc b/test/cctest/test_url.cc new file mode 100644 index 00000000000000..0b80d44caad807 --- /dev/null +++ b/test/cctest/test_url.cc @@ -0,0 +1,106 @@ +#include "node_url.h" +#include "node_i18n.h" + +#include "gtest/gtest.h" + +using node::url::URL; +using node::url::URL_FLAGS_FAILED; + +class URLTest : public ::testing::Test { + protected: + void SetUp() override { +#if defined(NODE_HAVE_I18N_SUPPORT) + std::string icu_data_dir; + node::i18n::InitializeICUDirectory(icu_data_dir); +#endif + } + + void TearDown() override {} +}; + +TEST_F(URLTest, Simple) { + URL simple("https://example.org:81/a/b/c?query#fragment"); + + EXPECT_FALSE(simple.flags() & URL_FLAGS_FAILED); + EXPECT_EQ(simple.protocol(), "https:"); + EXPECT_EQ(simple.host(), "example.org"); + EXPECT_EQ(simple.port(), 81); + EXPECT_EQ(simple.path(), "/a/b/c"); + EXPECT_EQ(simple.query(), "query"); + EXPECT_EQ(simple.fragment(), "fragment"); +} + +TEST_F(URLTest, Simple2) { + const char* input = "https://example.org:81/a/b/c?query#fragment"; + URL simple(input, strlen(input)); + + EXPECT_FALSE(simple.flags() & URL_FLAGS_FAILED); + EXPECT_EQ(simple.protocol(), "https:"); + EXPECT_EQ(simple.host(), "example.org"); + EXPECT_EQ(simple.port(), 81); + EXPECT_EQ(simple.path(), "/a/b/c"); + EXPECT_EQ(simple.query(), "query"); + EXPECT_EQ(simple.fragment(), "fragment"); +} + +TEST_F(URLTest, NoBase1) { + URL error("123noscheme"); + EXPECT_TRUE(error.flags() & URL_FLAGS_FAILED); +} + +TEST_F(URLTest, Base1) { + URL base("http://example.org/foo/bar"); + ASSERT_FALSE(base.flags() & URL_FLAGS_FAILED); + + URL simple("../baz", &base); + EXPECT_FALSE(simple.flags() & URL_FLAGS_FAILED); + EXPECT_EQ(simple.protocol(), "http:"); + EXPECT_EQ(simple.host(), "example.org"); + EXPECT_EQ(simple.path(), "/baz"); +} + +TEST_F(URLTest, Base2) { + URL simple("../baz", "http://example.org/foo/bar"); + + EXPECT_FALSE(simple.flags() & URL_FLAGS_FAILED); + EXPECT_EQ(simple.protocol(), "http:"); + EXPECT_EQ(simple.host(), "example.org"); + EXPECT_EQ(simple.path(), "/baz"); +} + +TEST_F(URLTest, Base3) { + const char* input = "../baz"; + const char* base = "http://example.org/foo/bar"; + + URL simple(input, strlen(input), base, strlen(base)); + + EXPECT_FALSE(simple.flags() & URL_FLAGS_FAILED); + EXPECT_EQ(simple.protocol(), "http:"); + EXPECT_EQ(simple.host(), "example.org"); + EXPECT_EQ(simple.path(), "/baz"); +} + +TEST_F(URLTest, ToFilePath) { +#define T(url, path) EXPECT_EQ(path, URL(url).ToFilePath()) + T("http://example.org/foo/bar", ""); + +#ifdef _WIN32 + T("file:///C:/Program%20Files/", "C:\\Program Files\\"); + T("file:///C:/a/b/c?query#fragment", "C:\\a\\b\\c"); + T("file://host/path/a/b/c?query#fragment", "\\\\host\\path\\a\\b\\c"); + T("file://xn--weird-prdj8vva.com/host/a", "\\\\wͪ͊eiͬ͋rd.com\\host\\a"); + T("file:///C:/a%2Fb", ""); + T("file:///", ""); + T("file:///home", ""); +#else + T("file:///", "/"); + T("file:///home/user?query#fragment", "/home/user"); + T("file:///home/user/?query#fragment", "/home/user/"); + T("file:///home/user/%20space", "/home/user/ space"); + T("file:///home/us%5Cer", "/home/us\\er"); + T("file:///home/us%2Fer", ""); + T("file://host/path", ""); +#endif + +#undef T +} diff --git a/test/cctest/test_util.cc b/test/cctest/test_util.cc index f1446ae0345153..62309306ba6ffb 100644 --- a/test/cctest/test_util.cc +++ b/test/cctest/test_util.cc @@ -121,3 +121,127 @@ TEST(UtilTest, UncheckedCalloc) { EXPECT_NE(nullptr, UncheckedCalloc(0)); EXPECT_NE(nullptr, UncheckedCalloc(1)); } + +template +static void MaybeStackBufferBasic() { + using node::MaybeStackBuffer; + + MaybeStackBuffer buf; + size_t old_length; + size_t old_capacity; + + /* Default constructor */ + EXPECT_EQ(0U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + + /* SetLength() expansion */ + buf.SetLength(buf.capacity()); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + + /* Means of accessing raw buffer */ + EXPECT_EQ(buf.out(), *buf); + EXPECT_EQ(&buf[0], *buf); + + /* Basic I/O */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* SetLengthAndZeroTerminate() */ + buf.SetLengthAndZeroTerminate(buf.capacity() - 1); + EXPECT_EQ(buf.capacity() - 1, buf.length()); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + buf.SetLength(buf.capacity()); + EXPECT_EQ(0, buf[buf.length() - 1]); + + /* Initial Realloc */ + old_length = buf.length() - 1; + old_capacity = buf.capacity(); + buf.AllocateSufficientStorage(buf.capacity() * 2); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_TRUE(buf.IsAllocated()); + for (size_t i = 0; i < old_length; i++) + EXPECT_EQ(static_cast(i), buf[i]); + EXPECT_EQ(0, buf[old_length]); + + /* SetLength() reduction and expansion */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + buf.SetLength(10); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + buf.SetLength(buf.capacity()); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Subsequent Realloc */ + old_length = buf.length(); + old_capacity = buf.capacity(); + buf.AllocateSufficientStorage(old_capacity * 1.5); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_EQ(static_cast(old_capacity * 1.5), buf.length()); + EXPECT_TRUE(buf.IsAllocated()); + for (size_t i = 0; i < old_length; i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Basic I/O on Realloc'd buffer */ + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + /* Release() */ + T* rawbuf = buf.out(); + buf.Release(); + EXPECT_EQ(0U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + free(rawbuf); +} + +TEST(UtilTest, MaybeStackBuffer) { + using node::MaybeStackBuffer; + + MaybeStackBufferBasic(); + MaybeStackBufferBasic(); + + // Constructor with size parameter + { + MaybeStackBuffer buf(100); + EXPECT_EQ(100U, buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_GT(buf.capacity(), buf.length()); + buf.SetLength(buf.capacity()); + EXPECT_EQ(buf.capacity(), buf.length()); + EXPECT_FALSE(buf.IsAllocated()); + for (size_t i = 0; i < buf.length(); i++) + buf[i] = static_cast(i); + for (size_t i = 0; i < buf.length(); i++) + EXPECT_EQ(static_cast(i), buf[i]); + + MaybeStackBuffer bigbuf(10000); + EXPECT_EQ(10000U, bigbuf.length()); + EXPECT_TRUE(bigbuf.IsAllocated()); + EXPECT_EQ(bigbuf.length(), bigbuf.capacity()); + for (size_t i = 0; i < bigbuf.length(); i++) + bigbuf[i] = static_cast(i); + for (size_t i = 0; i < bigbuf.length(); i++) + EXPECT_EQ(static_cast(i), bigbuf[i]); + } + + // Invalidated buffer + { + MaybeStackBuffer buf; + buf.Invalidate(); + EXPECT_TRUE(buf.IsInvalidated()); + EXPECT_FALSE(buf.IsAllocated()); + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(0U, buf.capacity()); + buf.Invalidate(); + EXPECT_TRUE(buf.IsInvalidated()); + } +} diff --git a/test/common/README.md b/test/common/README.md index defe9c1a38a989..4e7e955487bd3f 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -5,6 +5,7 @@ This directory contains modules used to test the Node.js implementation. ## Table of Contents * [Common module API](#common-module-api) +* [Duplex pair helper](#duplex-pair-helper) * [WPT module](#wpt-module) ## Common Module API @@ -26,12 +27,14 @@ A stream to push an array into a REPL Blocks for `time` amount of time. -### canCreateSymLink -API to indicate whether the current running process can create -symlinks. On Windows, this returns false if the process running -doesn't have privileges to create symlinks (specifically -[SeCreateSymbolicLinkPrivilege](https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx)). -On non-Windows platforms, this currently returns true. +### canCreateSymLink() +* return [<Boolean>] + +Checks whether the current running process can create symlinks. On Windows, this +returns `false` if the process running doesn't have privileges to create +symlinks +([SeCreateSymbolicLinkPrivilege](https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx)). +On non-Windows platforms, this always returns `true`. ### crashOnUnhandledRejection() @@ -46,9 +49,9 @@ failures. Platform normalizes the `dd` command ### enoughTestMem -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Check if there is more than 1gb of total memory. +Indicates if there is more than 1gb of total memory. ### expectsError(settings) * `settings` [<Object>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) @@ -81,7 +84,7 @@ Tests whether `name` and `expected` are part of a raised warning. Checks if `pathname` exists ### fixturesDir -* return [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) +* [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) Path to the 'fixtures' directory. @@ -92,37 +95,37 @@ Path to the 'fixtures' directory. Returns an instance of all possible `ArrayBufferView`s of the provided Buffer. ### globalCheck -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Turn this off if the test should not check for global leaks. +Set to `false` if the test should not check for global leaks. ### hasCrypto -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Checks for 'openssl'. +Indicates whether OpenSSL is available. ### hasFipsCrypto -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Checks `hasCrypto` and `crypto` with fips. +Indicates `hasCrypto` and `crypto` with fips. ### hasIPv6 -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Checks whether `IPv6` is supported on this platform. +Indicates whether `IPv6` is supported on this platform. ### hasMultiLocalhost -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Checks if there are multiple localhosts available. +Indicates if there are multiple localhosts available. ### inFreeBSDJail -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Checks whether free BSD Jail is true or false. ### isAix -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Advanced Interactive eXecutive (AIX). @@ -133,54 +136,54 @@ Platform check for Advanced Interactive eXecutive (AIX). Attempts to 'kill' `pid` ### isFreeBSD -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Free BSD. ### isLinux -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Linux. ### isLinuxPPCBE -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Linux on PowerPC. ### isOSX -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for macOS. ### isSunOS -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for SunOS. ### isWindows -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Windows. ### isWOW64 -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) Platform check for Windows 32-bit on Windows 64-bit. ### leakedGlobals -* return [<Array>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [<Array>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) -Checks whether any globals are not on the `knownGlobals` list. +Indicates whether any globals are not on the `knownGlobals` list. ### localhostIPv4 -* return [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) +* [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) -Gets IP of localhost +IP of `localhost`. ### localIPv6Hosts -* return [<Array>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [<Array>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) -Array of IPV6 hosts. +Array of IPV6 representations for `localhost`. ### mustCall([fn][, exact]) * `fn` [<Function>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) default = () => {} @@ -218,9 +221,9 @@ Returns a function that triggers an `AssertionError` if it is invoked. `msg` is Returns `true` if the exit code `exitCode` and/or signal name `signal` represent the exit code and/or signal name of a node process that aborted, `false` otherwise. ### opensslCli -* return [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) +* [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) -Checks whether 'opensslCli' is supported. +Indicates whether 'opensslCli' is supported. ### platformTimeout(ms) * `ms` [<Number>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) @@ -229,14 +232,14 @@ Checks whether 'opensslCli' is supported. Platform normalizes timeout. ### PIPE -* return [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) +* [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) -Path to the test sock. +Path to the test socket. ### PORT -* return [<Number>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) default = `12346` +* [<Number>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) default = `12346` -Port tests are running on. +A port number for tests to use if one is needed. ### printSkipMessage(msg) * `msg` [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) @@ -249,7 +252,7 @@ Logs '1..0 # Skipped: ' + `msg` Deletes the 'tmp' dir and recreates it ### rootDir -* return [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) +* [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) Path to the 'root' directory. either `/` or `c:\\` (windows) @@ -271,7 +274,7 @@ Platform normalizes the `pwd` command. Synchronous version of `spawnPwd`. ### tmpDir -* return [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) +* [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) The realpath of the 'tmp' directory. @@ -316,6 +319,14 @@ Decrements the `Countdown` counter. Specifies the remaining number of times `Countdown.prototype.dec()` must be called before the callback is invoked. +## Duplex pair helper + +The `common/duplexpair` module exports a single function `makeDuplexPair`, +which returns an object `{ clientSide, serverSide }` where each side is a +`Duplex` stream connected to the other side. + +There is no difference between client or server side beyond their names. + ## Fixtures Module The `common/fixtures` module provides convenience methods for working with diff --git a/test/common/duplexpair.js b/test/common/duplexpair.js new file mode 100644 index 00000000000000..ea5bd86a041b24 --- /dev/null +++ b/test/common/duplexpair.js @@ -0,0 +1,45 @@ +/* eslint-disable required-modules */ +'use strict'; +const { Duplex } = require('stream'); +const assert = require('assert'); + +const kCallback = Symbol('Callback'); +const kOtherSide = Symbol('Other'); + +class DuplexSocket extends Duplex { + constructor() { + super(); + this[kCallback] = null; + this[kOtherSide] = null; + } + + _read() { + const callback = this[kCallback]; + if (callback) { + this[kCallback] = null; + callback(); + } + } + + _write(chunk, encoding, callback) { + assert.notStrictEqual(this[kOtherSide], null); + assert.strictEqual(this[kOtherSide][kCallback], null); + this[kOtherSide][kCallback] = callback; + this[kOtherSide].push(chunk); + } + + _final(callback) { + this[kOtherSide].on('end', callback); + this[kOtherSide].push(null); + } +} + +function makeDuplexPair() { + const clientSide = new DuplexSocket(); + const serverSide = new DuplexSocket(); + clientSide[kOtherSide] = serverSide; + serverSide[kOtherSide] = clientSide; + return { clientSide, serverSide }; +} + +module.exports = makeDuplexPair; diff --git a/test/common/index.js b/test/common/index.js index 6ea3d89f0b9a37..7747cb57cf3ae7 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -577,6 +577,12 @@ exports.expectWarning = function(name, expected) { }, expected.length)); }; +Object.defineProperty(exports, 'hasIntl', { + get: function() { + return process.binding('config').hasIntl; + } +}); + // Crash the process on unhandled rejections. exports.crashOnUnhandledRejection = function() { process.on('unhandledRejection', diff --git a/test/fixtures/url-idna.js b/test/fixtures/url-idna.js new file mode 100644 index 00000000000000..4b8f5a48cc9646 --- /dev/null +++ b/test/fixtures/url-idna.js @@ -0,0 +1,215 @@ +'use strict'; + +// Credit for list: http://www.i18nguy.com/markup/idna-examples.html +module.exports = [ + { ascii: 'xn--mgbaal8b0b9b2b.icom.museum', + unicode: 'افغانستا.icom.museum' + }, + { + ascii: 'xn--lgbbat1ad8j.icom.museum', + unicode: 'الجزائر.icom.museum' + }, + { + ascii: 'xn--sterreich-z7a.icom.museum', + unicode: 'österreich.icom.museum' + }, + { + ascii: 'xn--54b6eqazv8bc7e.icom.museum', + unicode: 'বাংলাদেশ.icom.museum' + }, + { + ascii: 'xn--80abmy0agn7e.icom.museum', + unicode: 'беларусь.icom.museum' + }, + { + ascii: 'xn--belgi-rsa.icom.museum', + unicode: 'belgië.icom.museum' + }, + { + ascii: 'xn--80abgvm6a7d2b.icom.museum', + unicode: 'българия.icom.museum' + }, + { + ascii: 'xn--mgbfqim.icom.museum', + unicode: 'تشادر.icom.museum' + }, + { + ascii: 'xn--fiqs8s.icom.museum', + unicode: '中国.icom.museum' + }, + { + ascii: 'xn--mgbu4chg.icom.museum', + unicode: 'القمر.icom.museum' + }, + { + ascii: 'xn--vxakcego.icom.museum', + unicode: 'κυπρος.icom.museum' + }, + { + ascii: 'xn--eskrepublika-ebb62d.icom.museum', + unicode: 'českárepublika.icom.museum' + }, + { + ascii: 'xn--wgbh1c.icom.museum', + unicode: 'مصر.icom.museum' + }, + { + ascii: 'xn--hxakic4aa.icom.museum', + unicode: 'ελλάδα.icom.museum' + }, + { + ascii: 'xn--magyarorszg-t7a.icom.museum', + unicode: 'magyarország.icom.museum' + }, + { + ascii: 'xn--sland-ysa.icom.museum', + unicode: 'ísland.icom.museum' + }, + { + ascii: 'xn--h2brj9c.icom.museum', + unicode: 'भारत.icom.museum' + }, + { + ascii: 'xn--mgba3a4fra.icom.museum', + unicode: 'ايران.icom.museum' + }, + { + ascii: 'xn--ire-9la.icom.museum', + unicode: 'éire.icom.museum' + }, + { + ascii: 'xn--4dbklr2c8d.xn--4dbrk0ce.museum', + unicode: 'איקו״ם.ישראל.museum' + }, + { + ascii: 'xn--wgv71a.icom.museum', + unicode: '日本.icom.museum' + }, + { + ascii: 'xn--igbhzh7gpa.icom.museum', + unicode: 'الأردن.icom.museum' + }, + { + ascii: 'xn--80aaa0a6awh12ed.icom.museum', + unicode: 'қазақстан.icom.museum' + }, + { + ascii: 'xn--3e0b707e.icom.museum', + unicode: '한국.icom.museum' + }, + { + ascii: 'xn--80afmksoji0fc.icom.museum', + unicode: 'кыргызстан.icom.museum' + }, + { + ascii: 'xn--q7ce6a.icom.museum', + unicode: 'ລາວ.icom.museum' + }, + { + ascii: 'xn--mgbb7fjb.icom.museum', + unicode: 'لبنان.icom.museum' + }, + { + ascii: 'xn--80aaldqjmmi6x.icom.museum', + unicode: 'македонија.icom.museum' + }, + { + ascii: 'xn--mgbah1a3hjkrd.icom.museum', + unicode: 'موريتانيا.icom.museum' + }, + { + ascii: 'xn--mxico-bsa.icom.museum', + unicode: 'méxico.icom.museum' + }, + { + ascii: 'xn--c1aqabffc0aq.icom.museum', + unicode: 'монголулс.icom.museum' + }, + { + ascii: 'xn--mgbc0a9azcg.icom.museum', + unicode: 'المغرب.icom.museum' + }, + { + ascii: 'xn--l2bey1c2b.icom.museum', + unicode: 'नेपाल.icom.museum' + }, + { + ascii: 'xn--mgb9awbf.icom.museum', + unicode: 'عمان.icom.museum' + }, + { + ascii: 'xn--wgbl6a.icom.museum', + unicode: 'قطر.icom.museum' + }, + { + ascii: 'xn--romnia-yta.icom.museum', + unicode: 'românia.icom.museum' + }, + { + ascii: 'xn--h1alffa9f.xn--h1aegh.museum', + unicode: 'россия.иком.museum' + }, + { + ascii: 'xn--80aaabm1ab4blmeec9e7n.xn--h1aegh.museum', + unicode: 'србијаицрнагора.иком.museum' + }, + { + ascii: 'xn--xkc2al3hye2a.icom.museum', + unicode: 'இலங்கை.icom.museum' + }, + { + ascii: 'xn--espaa-rta.icom.museum', + unicode: 'españa.icom.museum' + }, + { + ascii: 'xn--o3cw4h.icom.museum', + unicode: 'ไทย.icom.museum' + }, + { + ascii: 'xn--pgbs0dh.icom.museum', + unicode: 'تونس.icom.museum' + }, + { + ascii: 'xn--trkiye-3ya.icom.museum', + unicode: 'türkiye.icom.museum' + }, + { + ascii: 'xn--80aaxgrpt.icom.museum', + unicode: 'украина.icom.museum' + }, + { + ascii: 'xn--vitnam-jk8b.icom.museum', + unicode: 'việtnam.icom.museum' + }, + // long label + { + ascii: `${'a'.repeat(64)}.com`, + unicode: `${'a'.repeat(64)}.com`, + }, + // long URL + { + ascii: `${`${'a'.repeat(64)}.`.repeat(4)}com`, + unicode: `${`${'a'.repeat(64)}.`.repeat(4)}com` + }, + // URLs with hyphen + { + ascii: 'r4---sn-a5mlrn7s.gevideo.com', + unicode: 'r4---sn-a5mlrn7s.gevideo.com' + }, + { + ascii: '-sn-a5mlrn7s.gevideo.com', + unicode: '-sn-a5mlrn7s.gevideo.com' + }, + { + ascii: 'sn-a5mlrn7s-.gevideo.com', + unicode: 'sn-a5mlrn7s-.gevideo.com' + }, + { + ascii: '-sn-a5mlrn7s-.gevideo.com', + unicode: '-sn-a5mlrn7s-.gevideo.com' + }, + { + ascii: '-sn--a5mlrn7s-.gevideo.com', + unicode: '-sn--a5mlrn7s-.gevideo.com' + } +]; diff --git a/test/fixtures/url-searchparams.js b/test/fixtures/url-searchparams.js new file mode 100644 index 00000000000000..918af678bc563e --- /dev/null +++ b/test/fixtures/url-searchparams.js @@ -0,0 +1,77 @@ +module.exports = [ + ['', '', []], + [ + 'foo=918854443121279438895193', + 'foo=918854443121279438895193', + [['foo', '918854443121279438895193']] + ], + ['foo=bar', 'foo=bar', [['foo', 'bar']]], + ['foo=bar&foo=quux', 'foo=bar&foo=quux', [['foo', 'bar'], ['foo', 'quux']]], + ['foo=1&bar=2', 'foo=1&bar=2', [['foo', '1'], ['bar', '2']]], + [ + "my%20weird%20field=q1!2%22'w%245%267%2Fz8)%3F", + 'my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', + [['my weird field', 'q1!2"\'w$5&7/z8)?']] + ], + ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', [['foo=baz', 'bar']]], + ['foo=baz=bar', 'foo=baz%3Dbar', [['foo', 'baz=bar']]], + [ + 'str=foo&arr=1&somenull&arr=2&undef=&arr=3', + 'str=foo&arr=1&somenull=&arr=2&undef=&arr=3', + [ + ['str', 'foo'], + ['arr', '1'], + ['somenull', ''], + ['arr', '2'], + ['undef', ''], + ['arr', '3'] + ] + ], + [' foo = bar ', '+foo+=+bar+', [[' foo ', ' bar ']]], + ['foo=%zx', 'foo=%25zx', [['foo', '%zx']]], + ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', [['foo', '\ufffd']]], + // See: https://github.com/joyent/node/issues/3058 + ['foo&bar=baz', 'foo=&bar=baz', [['foo', ''], ['bar', 'baz']]], + ['a=b&c&d=e', 'a=b&c=&d=e', [['a', 'b'], ['c', ''], ['d', 'e']]], + ['a=b&c=&d=e', 'a=b&c=&d=e', [['a', 'b'], ['c', ''], ['d', 'e']]], + ['a=b&=c&d=e', 'a=b&=c&d=e', [['a', 'b'], ['', 'c'], ['d', 'e']]], + ['a=b&=&d=e', 'a=b&=&d=e', [['a', 'b'], ['', ''], ['d', 'e']]], + ['&&foo=bar&&', 'foo=bar', [['foo', 'bar']]], + ['&', '', []], + ['&&&&', '', []], + ['&=&', '=', [['', '']]], + ['&=&=', '=&=', [['', ''], ['', '']]], + ['=', '=', [['', '']]], + ['+', '+=', [[' ', '']]], + ['+=', '+=', [[' ', '']]], + ['+&', '+=', [[' ', '']]], + ['=+', '=+', [['', ' ']]], + ['+=&', '+=', [[' ', '']]], + ['a&&b', 'a=&b=', [['a', ''], ['b', '']]], + ['a=a&&b=b', 'a=a&b=b', [['a', 'a'], ['b', 'b']]], + ['&a', 'a=', [['a', '']]], + ['&=', '=', [['', '']]], + ['a&a&', 'a=&a=', [['a', ''], ['a', '']]], + ['a&a&a&', 'a=&a=&a=', [['a', ''], ['a', ''], ['a', '']]], + ['a&a&a&a&', 'a=&a=&a=&a=', [['a', ''], ['a', ''], ['a', ''], ['a', '']]], + ['a=&a=value&a=', 'a=&a=value&a=', [['a', ''], ['a', 'value'], ['a', '']]], + ['foo%20bar=baz%20quux', 'foo+bar=baz+quux', [['foo bar', 'baz quux']]], + ['+foo=+bar', '+foo=+bar', [[' foo', ' bar']]], + ['a+', 'a+=', [['a ', '']]], + ['=a+', '=a+', [['', 'a ']]], + ['a+&', 'a+=', [['a ', '']]], + ['=a+&', '=a+', [['', 'a ']]], + ['%20+', '++=', [[' ', '']]], + ['=%20+', '=++', [['', ' ']]], + ['%20+&', '++=', [[' ', '']]], + ['=%20+&', '=++', [['', ' ']]], + [ + // fake percent encoding + 'foo=%©ar&baz=%A©uux&xyzzy=%©ud', + 'foo=%25%C2%A9ar&baz=%25A%C2%A9uux&xyzzy=%25%C2%A9ud', + [['foo', '%©ar'], ['baz', '%A©uux'], ['xyzzy', '%©ud']] + ], + // always preserve order of key-value pairs + ['a=1&b=2&a=3', 'a=1&b=2&a=3', [['a', '1'], ['b', '2'], ['a', '3']]], + ['?a', '%3Fa=', [['?a', '']]] +]; diff --git a/test/fixtures/url-setter-tests-additional.js b/test/fixtures/url-setter-tests-additional.js new file mode 100644 index 00000000000000..b27ae336a28776 --- /dev/null +++ b/test/fixtures/url-setter-tests-additional.js @@ -0,0 +1,237 @@ +module.exports = { + 'username': [ + { + 'comment': 'Surrogate pair', + 'href': 'https://github.com/', + 'new_value': '\uD83D\uDE00', + 'expected': { + 'href': 'https://%F0%9F%98%80@github.com/', + 'username': '%F0%9F%98%80' + } + }, + { + 'comment': 'Unpaired low surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uD83D', + 'expected': { + 'href': 'https://%EF%BF%BD@github.com/', + 'username': '%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired low surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uD83Dnode', + 'expected': { + 'href': 'https://%EF%BF%BDnode@github.com/', + 'username': '%EF%BF%BDnode' + } + }, + { + 'comment': 'Unpaired high surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uDE00', + 'expected': { + 'href': 'https://%EF%BF%BD@github.com/', + 'username': '%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired high surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uDE00node', + 'expected': { + 'href': 'https://%EF%BF%BDnode@github.com/', + 'username': '%EF%BF%BDnode' + } + } + ], + 'password': [ + { + 'comment': 'Surrogate pair', + 'href': 'https://github.com/', + 'new_value': '\uD83D\uDE00', + 'expected': { + 'href': 'https://:%F0%9F%98%80@github.com/', + 'password': '%F0%9F%98%80' + } + }, + { + 'comment': 'Unpaired low surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uD83D', + 'expected': { + 'href': 'https://:%EF%BF%BD@github.com/', + 'password': '%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired low surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uD83Dnode', + 'expected': { + 'href': 'https://:%EF%BF%BDnode@github.com/', + 'password': '%EF%BF%BDnode' + } + }, + { + 'comment': 'Unpaired high surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uDE00', + 'expected': { + 'href': 'https://:%EF%BF%BD@github.com/', + 'password': '%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired high surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uDE00node', + 'expected': { + 'href': 'https://:%EF%BF%BDnode@github.com/', + 'password': '%EF%BF%BDnode' + } + } + ], + 'pathname': [ + { + 'comment': 'Surrogate pair', + 'href': 'https://github.com/', + 'new_value': '/\uD83D\uDE00', + 'expected': { + 'href': 'https://github.com/%F0%9F%98%80', + 'pathname': '/%F0%9F%98%80' + } + }, + { + 'comment': 'Unpaired low surrogate 1', + 'href': 'https://github.com/', + 'new_value': '/\uD83D', + 'expected': { + 'href': 'https://github.com/%EF%BF%BD', + 'pathname': '/%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired low surrogate 2', + 'href': 'https://github.com/', + 'new_value': '/\uD83Dnode', + 'expected': { + 'href': 'https://github.com/%EF%BF%BDnode', + 'pathname': '/%EF%BF%BDnode' + } + }, + { + 'comment': 'Unpaired high surrogate 1', + 'href': 'https://github.com/', + 'new_value': '/\uDE00', + 'expected': { + 'href': 'https://github.com/%EF%BF%BD', + 'pathname': '/%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired high surrogate 2', + 'href': 'https://github.com/', + 'new_value': '/\uDE00node', + 'expected': { + 'href': 'https://github.com/%EF%BF%BDnode', + 'pathname': '/%EF%BF%BDnode' + } + } + ], + 'search': [ + { + 'comment': 'Surrogate pair', + 'href': 'https://github.com/', + 'new_value': '\uD83D\uDE00', + 'expected': { + 'href': 'https://github.com/?%F0%9F%98%80', + 'search': '?%F0%9F%98%80' + } + }, + { + 'comment': 'Unpaired low surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uD83D', + 'expected': { + 'href': 'https://github.com/?%EF%BF%BD', + 'search': '?%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired low surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uD83Dnode', + 'expected': { + 'href': 'https://github.com/?%EF%BF%BDnode', + 'search': '?%EF%BF%BDnode' + } + }, + { + 'comment': 'Unpaired high surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uDE00', + 'expected': { + 'href': 'https://github.com/?%EF%BF%BD', + 'search': '?%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired high surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uDE00node', + 'expected': { + 'href': 'https://github.com/?%EF%BF%BDnode', + 'search': '?%EF%BF%BDnode' + } + } + ], + 'hash': [ + { + 'comment': 'Surrogate pair', + 'href': 'https://github.com/', + 'new_value': '\uD83D\uDE00', + 'expected': { + 'href': 'https://github.com/#%F0%9F%98%80', + 'hash': '#%F0%9F%98%80' + } + }, + { + 'comment': 'Unpaired low surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uD83D', + 'expected': { + 'href': 'https://github.com/#%EF%BF%BD', + 'hash': '#%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired low surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uD83Dnode', + 'expected': { + 'href': 'https://github.com/#%EF%BF%BDnode', + 'hash': '#%EF%BF%BDnode' + } + }, + { + 'comment': 'Unpaired high surrogate 1', + 'href': 'https://github.com/', + 'new_value': '\uDE00', + 'expected': { + 'href': 'https://github.com/#%EF%BF%BD', + 'hash': '#%EF%BF%BD' + } + }, + { + 'comment': 'Unpaired high surrogate 2', + 'href': 'https://github.com/', + 'new_value': '\uDE00node', + 'expected': { + 'href': 'https://github.com/#%EF%BF%BDnode', + 'hash': '#%EF%BF%BDnode' + } + } + ] +}; diff --git a/test/fixtures/url-setter-tests.js b/test/fixtures/url-setter-tests.js new file mode 100644 index 00000000000000..6f769eaec7543d --- /dev/null +++ b/test/fixtures/url-setter-tests.js @@ -0,0 +1,1823 @@ +'use strict'; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/b30abaecf4/url/setters_tests.json + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +module.exports = +{ + "comment": [ + "## Tests for setters of https://url.spec.whatwg.org/#urlutils-members", + "", + "This file contains a JSON object.", + "Other than 'comment', each key is an attribute of the `URL` interface", + "defined in WHATWG’s URL Standard.", + "The values are arrays of test case objects for that attribute.", + "", + "To run a test case for the attribute `attr`:", + "", + "* Create a new `URL` object with the value for the 'href' key", + " the constructor single parameter. (Without a base URL.)", + " This must not throw.", + "* Set the attribute `attr` to (invoke its setter with)", + " with the value of for 'new_value' key.", + "* The value for the 'expected' key is another object.", + " For each `key` / `value` pair of that object,", + " get the attribute `key` (invoke its getter).", + " The returned string must be equal to `value`.", + "", + "Note: the 'href' setter is already covered by urltestdata.json." + ], + "protocol": [ + { + "comment": "The empty string is not a valid scheme. Setter leaves the URL unchanged.", + "href": "a://example.net", + "new_value": "", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "href": "a://example.net", + "new_value": "b", + "expected": { + "href": "b://example.net", + "protocol": "b:" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "defuse", + "expected": { + "href": "defuse:alert(1)", + "protocol": "defuse:" + } + }, + { + "comment": "Upper-case ASCII is lower-cased", + "href": "a://example.net", + "new_value": "B", + "expected": { + "href": "b://example.net", + "protocol": "b:" + } + }, + { + "comment": "Non-ASCII is rejected", + "href": "a://example.net", + "new_value": "é", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "No leading digit", + "href": "a://example.net", + "new_value": "0b", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "No leading punctuation", + "href": "a://example.net", + "new_value": "+b", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "href": "a://example.net", + "new_value": "bC0+-.", + "expected": { + "href": "bc0+-.://example.net", + "protocol": "bc0+-.:" + } + }, + { + "comment": "Only some punctuation is acceptable", + "href": "a://example.net", + "new_value": "b,c", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "Non-ASCII is rejected", + "href": "a://example.net", + "new_value": "bé", + "expected": { + "href": "a://example.net", + "protocol": "a:" + } + }, + { + "comment": "Can’t switch from URL containing username/password/port to file", + "href": "http://test@example.net", + "new_value": "file", + "expected": { + "href": "http://test@example.net/", + "protocol": "http:" + } + }, + { + "href": "gopher://example.net:1234", + "new_value": "file", + "expected": { + "href": "gopher://example.net:1234/", + "protocol": "gopher:" + } + }, + { + "href": "wss://x:x@example.net:1234", + "new_value": "file", + "expected": { + "href": "wss://x:x@example.net:1234/", + "protocol": "wss:" + } + }, + { + "comment": "Can’t switch from file URL with no host", + "href": "file://localhost/", + "new_value": "http", + "expected": { + "href": "file:///", + "protocol": "file:" + } + }, + { + "href": "file:///test", + "new_value": "gopher", + "expected": { + "href": "file:///test", + "protocol": "file:" + } + }, + { + "href": "file:", + "new_value": "wss", + "expected": { + "href": "file:///", + "protocol": "file:" + } + }, + { + "comment": "Can’t switch from special scheme to non-special", + "href": "http://example.net", + "new_value": "b", + "expected": { + "href": "http://example.net/", + "protocol": "http:" + } + }, + { + "href": "file://hi/path", + "new_value": "s", + "expected": { + "href": "file://hi/path", + "protocol": "file:" + } + }, + { + "href": "https://example.net", + "new_value": "s", + "expected": { + "href": "https://example.net/", + "protocol": "https:" + } + }, + { + "href": "ftp://example.net", + "new_value": "test", + "expected": { + "href": "ftp://example.net/", + "protocol": "ftp:" + } + }, + { + "comment": "Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must.", + "href": "mailto:me@example.net", + "new_value": "http", + "expected": { + "href": "mailto:me@example.net", + "protocol": "mailto:" + } + }, + { + "comment": "Can’t switch from non-special scheme to special", + "href": "ssh://me@example.net", + "new_value": "http", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://me@example.net", + "new_value": "gopher", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://me@example.net", + "new_value": "file", + "expected": { + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://example.net", + "new_value": "file", + "expected": { + "href": "ssh://example.net", + "protocol": "ssh:" + } + }, + { + "href": "nonsense:///test", + "new_value": "https", + "expected": { + "href": "nonsense:///test", + "protocol": "nonsense:" + } + }, + { + "comment": "Stuff after the first ':' is ignored", + "href": "http://example.net", + "new_value": "https:foo : bar", + "expected": { + "href": "https://example.net/", + "protocol": "https:" + } + }, + { + "comment": "Stuff after the first ':' is ignored", + "href": "data:text/html,

Test", + "new_value": "view-source+data:foo : bar", + "expected": { + "href": "view-source+data:text/html,

Test", + "protocol": "view-source+data:" + } + }, + { + "comment": "Port is set to null if it is the default for new scheme.", + "href": "http://foo.com:443/", + "new_value": "https", + "expected": { + "href": "https://foo.com/", + "protocol": "https:", + "port": "" + } + } + ], + "username": [ + { + "comment": "No host means no username", + "href": "file:///home/you/index.html", + "new_value": "me", + "expected": { + "href": "file:///home/you/index.html", + "username": "" + } + }, + { + "comment": "No host means no username", + "href": "unix:/run/foo.socket", + "new_value": "me", + "expected": { + "href": "unix:/run/foo.socket", + "username": "" + } + }, + { + "comment": "Cannot-be-a-base means no username", + "href": "mailto:you@example.net", + "new_value": "me", + "expected": { + "href": "mailto:you@example.net", + "username": "" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "wario", + "expected": { + "href": "javascript:alert(1)", + "username": "" + } + }, + { + "href": "http://example.net", + "new_value": "me", + "expected": { + "href": "http://me@example.net/", + "username": "me" + } + }, + { + "href": "http://:secret@example.net", + "new_value": "me", + "expected": { + "href": "http://me:secret@example.net/", + "username": "me" + } + }, + { + "href": "http://me@example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "username": "" + } + }, + { + "href": "http://me:secret@example.net", + "new_value": "", + "expected": { + "href": "http://:secret@example.net/", + "username": "" + } + }, + { + "comment": "UTF-8 percent encoding with the userinfo encode set.", + "href": "http://example.net", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/", + "username": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is.", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://%c3%89t%C3%A9@example.net/", + "username": "%c3%89t%C3%A9" + } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "username": "" + } + }, + { + "href": "javascript://x/", + "new_value": "wario", + "expected": { + "href": "javascript://wario@x/", + "username": "wario" + } + }, + { + "href": "file://test/", + "new_value": "test", + "expected": { + "href": "file://test/", + "username": "" + } + } + ], + "password": [ + { + "comment": "No host means no password", + "href": "file:///home/me/index.html", + "new_value": "secret", + "expected": { + "href": "file:///home/me/index.html", + "password": "" + } + }, + { + "comment": "No host means no password", + "href": "unix:/run/foo.socket", + "new_value": "secret", + "expected": { + "href": "unix:/run/foo.socket", + "password": "" + } + }, + { + "comment": "Cannot-be-a-base means no password", + "href": "mailto:me@example.net", + "new_value": "secret", + "expected": { + "href": "mailto:me@example.net", + "password": "" + } + }, + { + "href": "http://example.net", + "new_value": "secret", + "expected": { + "href": "http://:secret@example.net/", + "password": "secret" + } + }, + { + "href": "http://me@example.net", + "new_value": "secret", + "expected": { + "href": "http://me:secret@example.net/", + "password": "secret" + } + }, + { + "href": "http://:secret@example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "password": "" + } + }, + { + "href": "http://me:secret@example.net", + "new_value": "", + "expected": { + "href": "http://me@example.net/", + "password": "" + } + }, + { + "comment": "UTF-8 percent encoding with the userinfo encode set.", + "href": "http://example.net", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/", + "password": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is.", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://:%c3%89t%C3%A9@example.net/", + "password": "%c3%89t%C3%A9" + } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "password": "" + } + }, + { + "href": "javascript://x/", + "new_value": "bowser", + "expected": { + "href": "javascript://:bowser@x/", + "password": "bowser" + } + }, + { + "href": "file://test/", + "new_value": "test", + "expected": { + "href": "file://test/", + "password": "" + } + } + ], + "host": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "\u0009", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000A", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000D", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "#", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "/", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "?", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "ß", + "expected": { + "href": "sc://%C3%9F/", + "host": "%C3%9F", + "hostname": "%C3%9F" + } + }, + { + "comment": "IDNA Nontransitional_Processing", + "href": "https://x/", + "new_value": "ß", + "expected": { + "href": "https://xn--zca/", + "host": "xn--zca", + "hostname": "xn--zca" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "mailto:me@example.net", + "new_value": "example.com", + "expected": { + "href": "mailto:me@example.net", + "host": "" + } + }, + { + "comment": "Cannot-be-a-base means no password", + "href": "data:text/plain,Stuff", + "new_value": "example.net", + "expected": { + "href": "data:text/plain,Stuff", + "host": "" + } + }, + { + "href": "http://example.net", + "new_value": "example.com:8080", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Port number is unchanged if not specified in the new value", + "href": "http://example.net:8080", + "new_value": "example.com", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Port number is unchanged if not specified", + "href": "http://example.net:8080", + "new_value": "example.com:", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "The empty host is not valid for special schemes", + "href": "http://example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net" + } + }, + { + "comment": "The empty host is OK for non-special schemes", + "href": "view-source+http://example.net/foo", + "new_value": "", + "expected": { + "href": "view-source+http:///foo", + "host": "" + } + }, + { + "comment": "Path-only URLs can gain a host", + "href": "a:/foo", + "new_value": "example.net", + "expected": { + "href": "a://example.net/foo", + "host": "example.net" + } + }, + { + "comment": "IPv4 address syntax is normalized", + "href": "http://example.net", + "new_value": "0x7F000001:8080", + "expected": { + "href": "http://127.0.0.1:8080/", + "host": "127.0.0.1:8080", + "hostname": "127.0.0.1", + "port": "8080" + } + }, + { + "comment": "IPv6 address syntax is normalized", + "href": "http://example.net", + "new_value": "[::0:01]:2", + "expected": { + "href": "http://[::1]:2/", + "host": "[::1]:2", + "hostname": "[::1]", + "port": "2" + } + }, + { + "comment": "Default port number is removed", + "href": "http://example.net", + "new_value": "example.com:80", + "expected": { + "href": "http://example.com/", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "https://example.net", + "new_value": "example.com:443", + "expected": { + "href": "https://example.com/", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Default port number is only removed for the relevant scheme", + "href": "https://example.net", + "new_value": "example.com:80", + "expected": { + "href": "https://example.com:80/", + "host": "example.com:80", + "hostname": "example.com", + "port": "80" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com/stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080/stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com?stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080?stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com#stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080#stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com:8080\\stuff", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", + "href": "view-source+http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "view-source+http://example.net/path", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "view-source+http://example.net/path", + "new_value": "example.com:8080stuff2", + "expected": { + "href": "view-source+http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "example.com:8080stuff2", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "example.com:8080+2", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers", + "href": "http://example.net/path", + "new_value": "example.com:65535", + "expected": { + "href": "http://example.com:65535/path", + "host": "example.com:65535", + "hostname": "example.com", + "port": "65535" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.", + "href": "http://example.net/path", + "new_value": "example.com:65536", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.4x]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "file://y/", + "new_value": "x:123", + "expected": { + "href": "file://y/", + "host": "y", + "hostname": "y", + "port": "" + } + }, + { + "href": "file://y/", + "new_value": "loc%41lhost", + "expected": { + "href": "file:///", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "file://hi/x", + "new_value": "", + "expected": { + "href": "file:///x", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "sc://test@test/", + "new_value": "", + "expected": { + "href": "sc://test@test/", + "host": "test", + "hostname": "test", + "username": "test" + } + }, + { + "href": "sc://test:12/", + "new_value": "", + "expected": { + "href": "sc://test:12/", + "host": "test:12", + "hostname": "test", + "port": "12" + } + } + ], + "hostname": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "\u0009", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000A", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "\u000D", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "href": "sc://x/", + "new_value": "#", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "/", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "?", + "expected": { + "href": "sc:///", + "host": "", + "hostname": "" + } + }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + { + "comment": "Cannot-be-a-base means no host", + "href": "mailto:me@example.net", + "new_value": "example.com", + "expected": { + "href": "mailto:me@example.net", + "host": "" + } + }, + { + "comment": "Cannot-be-a-base means no password", + "href": "data:text/plain,Stuff", + "new_value": "example.net", + "expected": { + "href": "data:text/plain,Stuff", + "host": "" + } + }, + { + "href": "http://example.net:8080", + "new_value": "example.com", + "expected": { + "href": "http://example.com:8080/", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "The empty host is not valid for special schemes", + "href": "http://example.net", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net" + } + }, + { + "comment": "The empty host is OK for non-special schemes", + "href": "view-source+http://example.net/foo", + "new_value": "", + "expected": { + "href": "view-source+http:///foo", + "host": "" + } + }, + { + "comment": "Path-only URLs can gain a host", + "href": "a:/foo", + "new_value": "example.net", + "expected": { + "href": "a://example.net/foo", + "host": "example.net" + } + }, + { + "comment": "IPv4 address syntax is normalized", + "href": "http://example.net:8080", + "new_value": "0x7F000001", + "expected": { + "href": "http://127.0.0.1:8080/", + "host": "127.0.0.1:8080", + "hostname": "127.0.0.1", + "port": "8080" + } + }, + { + "comment": "IPv6 address syntax is normalized", + "href": "http://example.net", + "new_value": "[::0:01]", + "expected": { + "href": "http://[::1]/", + "host": "[::1]", + "hostname": "[::1]", + "port": "" + } + }, + { + "comment": "Stuff after a : delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com:8080", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a : delimiter is ignored", + "href": "http://example.net:8080/path", + "new_value": "example.com:", + "expected": { + "href": "http://example.com:8080/path", + "host": "example.com:8080", + "hostname": "example.com", + "port": "8080" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com/stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com?stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "example.com#stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "http://example.com/path", + "host": "example.com", + "hostname": "example.com", + "port": "" + } + }, + { + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", + "href": "view-source+http://example.net/path", + "new_value": "example.com\\stuff", + "expected": { + "href": "view-source+http://example.net/path", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.4x]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.3.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.2.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "http://example.net/", + "new_value": "[::1.]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + { + "href": "file://y/", + "new_value": "x:123", + "expected": { + "href": "file://y/", + "host": "y", + "hostname": "y", + "port": "" + } + }, + { + "href": "file://y/", + "new_value": "loc%41lhost", + "expected": { + "href": "file:///", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "file://hi/x", + "new_value": "", + "expected": { + "href": "file:///x", + "host": "", + "hostname": "", + "port": "" + } + }, + { + "href": "sc://test@test/", + "new_value": "", + "expected": { + "href": "sc://test@test/", + "host": "test", + "hostname": "test", + "username": "test" + } + }, + { + "href": "sc://test:12/", + "new_value": "", + "expected": { + "href": "sc://test:12/", + "host": "test:12", + "hostname": "test", + "port": "12" + } + } + ], + "port": [ + { + "href": "http://example.net", + "new_value": "8080", + "expected": { + "href": "http://example.net:8080/", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port number is removed if empty is the new value", + "href": "http://example.net:8080", + "new_value": "", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "http://example.net:8080", + "new_value": "80", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is removed", + "href": "https://example.net:4433", + "new_value": "443", + "expected": { + "href": "https://example.net/", + "host": "example.net", + "hostname": "example.net", + "port": "" + } + }, + { + "comment": "Default port number is only removed for the relevant scheme", + "href": "https://example.net", + "new_value": "80", + "expected": { + "href": "https://example.net:80/", + "host": "example.net:80", + "hostname": "example.net", + "port": "80" + } + }, + { + "comment": "Stuff after a / delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080/stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a ? delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080?stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a # delimiter is ignored", + "href": "http://example.net/path", + "new_value": "8080#stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Stuff after a \\ delimiter is ignored for special schemes", + "href": "http://example.net/path", + "new_value": "8080\\stuff", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "view-source+http://example.net/path", + "new_value": "8080stuff2", + "expected": { + "href": "view-source+http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "8080stuff2", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error", + "href": "http://example.net/path", + "new_value": "8080+2", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers", + "href": "http://example.net/path", + "new_value": "65535", + "expected": { + "href": "http://example.net:65535/path", + "host": "example.net:65535", + "hostname": "example.net", + "port": "65535" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error", + "href": "http://example.net:8080/path", + "new_value": "65536", + "expected": { + "href": "http://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error", + "href": "non-special://example.net:8080/path", + "new_value": "65536", + "expected": { + "href": "non-special://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "href": "file://test/", + "new_value": "12", + "expected": { + "href": "file://test/", + "port": "" + } + }, + { + "href": "file://localhost/", + "new_value": "12", + "expected": { + "href": "file:///", + "port": "" + } + }, + { + "href": "non-base:value", + "new_value": "12", + "expected": { + "href": "non-base:value", + "port": "" + } + }, + { + "href": "sc:///", + "new_value": "12", + "expected": { + "href": "sc:///", + "port": "" + } + }, + { + "href": "sc://x/", + "new_value": "12", + "expected": { + "href": "sc://x:12/", + "port": "12" + } + }, + { + "href": "javascript://x/", + "new_value": "12", + "expected": { + "href": "javascript://x:12/", + "port": "12" + } + } + ], + "pathname": [ + { + "comment": "Cannot-be-a-base don’t have a path", + "href": "mailto:me@example.net", + "new_value": "/foo", + "expected": { + "href": "mailto:me@example.net", + "pathname": "me@example.net" + } + }, + { + "href": "unix:/run/foo.socket?timeout=10", + "new_value": "/var/log/../run/bar.socket", + "expected": { + "href": "unix:/var/run/bar.socket?timeout=10", + "pathname": "/var/run/bar.socket" + } + }, + { + "href": "https://example.net#nav", + "new_value": "home", + "expected": { + "href": "https://example.net/home#nav", + "pathname": "/home" + } + }, + { + "href": "https://example.net#nav", + "new_value": "../home", + "expected": { + "href": "https://example.net/home#nav", + "pathname": "/home" + } + }, + { + "comment": "\\ is a segment delimiter for 'special' URLs", + "href": "http://example.net/home?lang=fr#nav", + "new_value": "\\a\\%2E\\b\\%2e.\\c", + "expected": { + "href": "http://example.net/a/c?lang=fr#nav", + "pathname": "/a/c" + } + }, + { + "comment": "\\ is *not* a segment delimiter for non-'special' URLs", + "href": "view-source+http://example.net/home?lang=fr#nav", + "new_value": "\\a\\%2E\\b\\%2e.\\c", + "expected": { + "href": "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav", + "pathname": "/\\a\\%2E\\b\\%2e.\\c" + } + }, + { + "comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed.", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9", + "pathname": "/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is, including %2E outside dotted segments.", + "href": "http://example.net", + "new_value": "%2e%2E%c3%89té", + "expected": { + "href": "http://example.net/%2e%2E%c3%89t%C3%A9", + "pathname": "/%2e%2E%c3%89t%C3%A9" + } + }, + { + "comment": "? needs to be encoded", + "href": "http://example.net", + "new_value": "?", + "expected": { + "href": "http://example.net/%3F", + "pathname": "/%3F" + } + }, + { + "comment": "# needs to be encoded", + "href": "http://example.net", + "new_value": "#", + "expected": { + "href": "http://example.net/%23", + "pathname": "/%23" + } + }, + { + "comment": "? needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "?", + "expected": { + "href": "sc://example.net/%3F", + "pathname": "/%3F" + } + }, + { + "comment": "# needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "#", + "expected": { + "href": "sc://example.net/%23", + "pathname": "/%23" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file://monkey/", + "new_value": "\\\\", + "expected": { + "href": "file://monkey/", + "pathname": "/" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file:///unicorn", + "new_value": "//\\/", + "expected": { + "href": "file:///", + "pathname": "/" + } + }, + { + "comment": "File URLs and (back)slashes", + "href": "file:///unicorn", + "new_value": "//monkey/..//", + "expected": { + "href": "file:///", + "pathname": "/" + } + } + ], + "search": [ + { + "href": "https://example.net#nav", + "new_value": "lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "?lang=fr", + "expected": { + "href": "https://example.net/?lang=fr#nav", + "search": "?lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "??lang=fr", + "expected": { + "href": "https://example.net/??lang=fr#nav", + "search": "??lang=fr" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "?", + "expected": { + "href": "https://example.net/?#nav", + "search": "" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "", + "expected": { + "href": "https://example.net/#nav", + "search": "" + } + }, + { + "href": "https://example.net?lang=en-US", + "new_value": "", + "expected": { + "href": "https://example.net/", + "search": "" + } + }, + { + "href": "https://example.net", + "new_value": "", + "expected": { + "href": "https://example.net/", + "search": "" + } + }, + { + "comment": "UTF-8 percent encoding with the query encode set. Tabs and newlines are removed.", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "search": "?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://example.net/?%c3%89t%C3%A9", + "search": "?%c3%89t%C3%A9" + } + } + ], + "hash": [ + { + "href": "https://example.net", + "new_value": "main", + "expected": { + "href": "https://example.net/#main", + "hash": "#main" + } + }, + { + "href": "https://example.net#nav", + "new_value": "main", + "expected": { + "href": "https://example.net/#main", + "hash": "#main" + } + }, + { + "href": "https://example.net?lang=en-US", + "new_value": "##nav", + "expected": { + "href": "https://example.net/?lang=en-US##nav", + "hash": "##nav" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "#main", + "expected": { + "href": "https://example.net/?lang=en-US#main", + "hash": "#main" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "#", + "expected": { + "href": "https://example.net/?lang=en-US#", + "hash": "" + } + }, + { + "href": "https://example.net?lang=en-US#nav", + "new_value": "", + "expected": { + "href": "https://example.net/?lang=en-US", + "hash": "" + } + }, + { + "comment": "Simple percent-encoding; nuls, tabs, and newlines are removed", + "href": "a:/", + "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé", + "expected": { + "href": "a:/#%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9", + "hash": "#%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" + } + }, + { + "comment": "Bytes already percent-encoded are left as-is", + "href": "http://example.net", + "new_value": "%c3%89té", + "expected": { + "href": "http://example.net/#%c3%89t%C3%A9", + "hash": "#%c3%89t%C3%A9" + } + }, + { + "href": "javascript:alert(1)", + "new_value": "castle", + "expected": { + "href": "javascript:alert(1)#castle", + "hash": "#castle" + } + } + ] +} diff --git a/test/fixtures/url-tests-additional.js b/test/fixtures/url-tests-additional.js new file mode 100644 index 00000000000000..c1c640f4bb4b7d --- /dev/null +++ b/test/fixtures/url-tests-additional.js @@ -0,0 +1,36 @@ +'use strict'; + +// This file contains test cases not part of the WPT + +module.exports = [ + { + // surrogate pair + 'url': 'https://github.com/nodejs/\uD83D\uDE00node', + 'protocol': 'https:', + 'pathname': '/nodejs/%F0%9F%98%80node' + }, + { + // unpaired low surrogate + 'url': 'https://github.com/nodejs/\uD83D', + 'protocol': 'https:', + 'pathname': '/nodejs/%EF%BF%BD' + }, + { + // unpaired low surrogate + 'url': 'https://github.com/nodejs/\uD83Dnode', + 'protocol': 'https:', + 'pathname': '/nodejs/%EF%BF%BDnode' + }, + { + // unmatched high surrogate + 'url': 'https://github.com/nodejs/\uDE00', + 'protocol': 'https:', + 'pathname': '/nodejs/%EF%BF%BD' + }, + { + // unmatched high surrogate + 'url': 'https://github.com/nodejs/\uDE00node', + 'protocol': 'https:', + 'pathname': '/nodejs/%EF%BF%BDnode' + } +]; diff --git a/test/fixtures/url-tests.js b/test/fixtures/url-tests.js new file mode 100644 index 00000000000000..48f77fe0774d64 --- /dev/null +++ b/test/fixtures/url-tests.js @@ -0,0 +1,6565 @@ +'use strict'; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/11757f1/url/urltestdata.json + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +module.exports = +[ + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js", + { + "input": "http://example\t.\norg", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@foo:21/bar;par?b#c", + "base": "http://example.org/foo/bar", + "href": "http://user:pass@foo:21/bar;par?b#c", + "origin": "http://foo:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "foo:21", + "hostname": "foo", + "port": "21", + "pathname": "/bar;par", + "search": "?b", + "hash": "#c" + }, + { + "input": "https://test:@test", + "base": "about:blank", + "href": "https://test@test/", + "origin": "https://test", + "protocol": "https:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://:@test", + "base": "about:blank", + "href": "https://test/", + "origin": "https://test", + "protocol": "https:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://test:@test/x", + "base": "about:blank", + "href": "non-special://test@test/x", + "origin": "null", + "protocol": "non-special:", + "username": "test", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "non-special://:@test/x", + "base": "about:blank", + "href": "non-special://test/x", + "origin": "null", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + { + "input": "http:foo.com", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "\t :foo.com \n", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com", + "search": "", + "hash": "" + }, + { + "input": " foo.com ", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/foo.com", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/foo.com", + "search": "", + "hash": "" + }, + { + "input": "a:\t foo.com", + "base": "http://example.org/foo/bar", + "href": "a: foo.com", + "origin": "null", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": " foo.com", + "search": "", + "hash": "" + }, + { + "input": "http://f:21/ b ? d # e ", + "base": "http://example.org/foo/bar", + "href": "http://f:21/%20b%20?%20d%20# e", + "origin": "http://f:21", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:21", + "hostname": "f", + "port": "21", + "pathname": "/%20b%20", + "search": "?%20d%20", + "hash": "# e" + }, + { + "input": "lolscheme:x x#x x", + "base": "about:blank", + "href": "lolscheme:x x#x x", + "protocol": "lolscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x x", + "search": "", + "hash": "#x x" + }, + { + "input": "http://f:/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:0/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000/c", + "base": "http://example.org/foo/bar", + "href": "http://f:0/c", + "origin": "http://f:0", + "protocol": "http:", + "username": "", + "password": "", + "host": "f:0", + "hostname": "f", + "port": "0", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:00000000000000000000080/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:b/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: /c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:\n/c", + "base": "http://example.org/foo/bar", + "href": "http://f/c", + "origin": "http://f", + "protocol": "http:", + "username": "", + "password": "", + "host": "f", + "hostname": "f", + "port": "", + "pathname": "/c", + "search": "", + "hash": "" + }, + { + "input": "http://f:fifty-two/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "non-special://f:999999/c", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://f: 21 / b ? d # e ", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": " \t", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": ":foo.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:foo.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:foo.com/", + "search": "", + "hash": "" + }, + { + "input": ":", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": ":a", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:a", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:a", + "search": "", + "hash": "" + }, + { + "input": ":/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:/", + "search": "", + "hash": "" + }, + { + "input": ":#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:", + "search": "", + "hash": "" + }, + { + "input": "#", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "#/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#/" + }, + { + "input": "#\\", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#\\", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#\\" + }, + { + "input": "#;?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#;?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#;?" + }, + { + "input": "?", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": ":23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:23", + "search": "", + "hash": "" + }, + { + "input": "/:23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/:23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/:23", + "search": "", + "hash": "" + }, + { + "input": "::", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::", + "search": "", + "hash": "" + }, + { + "input": "::23", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/::23", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/::23", + "search": "", + "hash": "" + }, + { + "input": "foo://", + "base": "http://example.org/foo/bar", + "href": "foo://", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@c:29/d", + "base": "http://example.org/foo/bar", + "href": "http://a:b@c:29/d", + "origin": "http://c:29", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "c:29", + "hostname": "c", + "port": "29", + "pathname": "/d", + "search": "", + "hash": "" + }, + { + "input": "http::@c:29", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/:@c:29", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/:@c:29", + "search": "", + "hash": "" + }, + { + "input": "http://&a:foo(b]c@d:2/", + "base": "http://example.org/foo/bar", + "href": "http://&a:foo(b%5Dc@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "&a", + "password": "foo(b%5Dc", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://::@c@d:2", + "base": "http://example.org/foo/bar", + "href": "http://:%3A%40c@d:2/", + "origin": "http://d:2", + "protocol": "http:", + "username": "", + "password": "%3A%40c", + "host": "d:2", + "hostname": "d", + "port": "2", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com:b@d/", + "base": "http://example.org/foo/bar", + "href": "http://foo.com:b@d/", + "origin": "http://d", + "protocol": "http:", + "username": "foo.com", + "password": "b", + "host": "d", + "hostname": "d", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo.com/\\@", + "base": "http://example.org/foo/bar", + "href": "http://foo.com//@", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "//@", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://foo.com/", + "origin": "http://foo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.com", + "hostname": "foo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\a\\b:c\\d@foo.com\\", + "base": "http://example.org/foo/bar", + "href": "http://a/b:c/d@foo.com/", + "origin": "http://a", + "protocol": "http:", + "username": "", + "password": "", + "host": "a", + "hostname": "a", + "port": "", + "pathname": "/b:c/d@foo.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:/", + "base": "http://example.org/foo/bar", + "href": "foo:/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "foo:/bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo:/bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo://///////", + "base": "http://example.org/foo/bar", + "href": "foo://///////", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////", + "search": "", + "hash": "" + }, + { + "input": "foo://///////bar.com/", + "base": "http://example.org/foo/bar", + "href": "foo://///////bar.com/", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "///////bar.com/", + "search": "", + "hash": "" + }, + { + "input": "foo:////://///", + "base": "http://example.org/foo/bar", + "href": "foo:////://///", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//://///", + "search": "", + "hash": "" + }, + { + "input": "c:/foo", + "base": "http://example.org/foo/bar", + "href": "c:/foo", + "origin": "null", + "protocol": "c:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "//foo/bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + { + "input": "http://foo/path;a??e#f#g", + "base": "http://example.org/foo/bar", + "href": "http://foo/path;a??e#f#g", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/path;a", + "search": "??e", + "hash": "#f#g" + }, + { + "input": "http://foo/abcd?efgh?ijkl", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd?efgh?ijkl", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "?efgh?ijkl", + "hash": "" + }, + { + "input": "http://foo/abcd#foo?bar", + "base": "http://example.org/foo/bar", + "href": "http://foo/abcd#foo?bar", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/abcd", + "search": "", + "hash": "#foo?bar" + }, + { + "input": "[61:24:74]:98", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:24:74]:98", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:24:74]:98", + "search": "", + "hash": "" + }, + { + "input": "http:[61:27]/:foo", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/[61:27]/:foo", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/[61:27]/:foo", + "search": "", + "hash": "" + }, + { + "input": "http://[1::2]:3:4", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://2001::1]:80", + "base": "http://example.org/foo/bar", + "failure": true + }, + { + "input": "http://[2001::1]", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[::127.0.0.1]", + "base": "http://example.org/foo/bar", + "href": "http://[::7f00:1]/", + "origin": "http://[::7f00:1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::7f00:1]", + "hostname": "[::7f00:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:0:0:0:0:0:13.1.68.3]", + "base": "http://example.org/foo/bar", + "href": "http://[::d01:4403]/", + "origin": "http://[::d01:4403]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[::d01:4403]", + "hostname": "[::d01:4403]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[2001::1]:80", + "base": "http://example.org/foo/bar", + "href": "http://[2001::1]/", + "origin": "http://[2001::1]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[2001::1]", + "hostname": "[2001::1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "http://example.org/foo/bar", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file://example:1/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://example:test/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://example%/", + "base": "about:blank", + "failure": true + }, + { + "input": "file://[example]/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftps:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "http://example.org/foo/bar", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/example.com/", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "http://example.org/foo/bar", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "http://example.org/foo/bar", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "http://example.org/foo/bar", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "http://example.org/foo/bar", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "http://example.org/foo/bar", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "http://example.org/foo/bar", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "http://example.org/foo/bar", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "http://example.org/foo/bar", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "http://example.org/foo/bar", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "/a/b/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/b/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/b/c", + "search": "", + "hash": "" + }, + { + "input": "/a/ /c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%20/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%20/c", + "search": "", + "hash": "" + }, + { + "input": "/a%2fc", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a%2fc", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a%2fc", + "search": "", + "hash": "" + }, + { + "input": "/a/%2f/c", + "base": "http://example.org/foo/bar", + "href": "http://example.org/a/%2f/c", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/a/%2f/c", + "search": "", + "hash": "" + }, + { + "input": "#β", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar#%CE%B2", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "#%CE%B2" + }, + { + "input": "data:text/html,test#test", + "base": "http://example.org/foo/bar", + "href": "data:text/html,test#test", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "text/html,test", + "search": "", + "hash": "#test" + }, + { + "input": "tel:1234567890", + "base": "http://example.org/foo/bar", + "href": "tel:1234567890", + "origin": "null", + "protocol": "tel:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "1234567890", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html", + { + "input": "file:c:\\foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:/foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": " File:c|////foo\\bar.html", + "base": "file:///tmp/mock/path", + "href": "file:///c:////foo/bar.html", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:////foo/bar.html", + "search": "", + "hash": "" + }, + { + "input": "C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/C|\\foo\\bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//C|/foo/bar", + "base": "file:///tmp/mock/path", + "href": "file:///C:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "//server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "\\\\server\\file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "/\\server/file", + "base": "file:///tmp/mock/path", + "href": "file://server/file", + "protocol": "file:", + "username": "", + "password": "", + "host": "server", + "hostname": "server", + "port": "", + "pathname": "/file", + "search": "", + "hash": "" + }, + { + "input": "file:///foo/bar.txt", + "base": "file:///tmp/mock/path", + "href": "file:///foo/bar.txt", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/foo/bar.txt", + "search": "", + "hash": "" + }, + { + "input": "file:///home/me", + "base": "file:///tmp/mock/path", + "href": "file:///home/me", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/home/me", + "search": "", + "hash": "" + }, + { + "input": "//", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "///test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "file://test", + "base": "file:///tmp/mock/path", + "href": "file://test/", + "protocol": "file:", + "username": "", + "password": "", + "host": "test", + "hostname": "test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/", + "base": "file:///tmp/mock/path", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file://localhost/test", + "base": "file:///tmp/mock/path", + "href": "file:///test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + { + "input": "file:test", + "base": "file:///tmp/mock/path", + "href": "file:///tmp/mock/test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/tmp/mock/test", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js", + { + "input": "http://example.com/././foo", + "base": "about:blank", + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/./.foo", + "base": "about:blank", + "href": "http://example.com/.foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/.foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/.", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/./", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/..", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/..bar", + "base": "about:blank", + "href": "http://example.com/foo/..bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/..bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton", + "base": "about:blank", + "href": "http://example.com/foo/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar/../ton/../../a", + "base": "about:blank", + "href": "http://example.com/a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../..", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/../../../ton", + "base": "about:blank", + "href": "http://example.com/ton", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/ton", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e%2", + "base": "about:blank", + "href": "http://example.com/foo/%2e%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/%2e%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar", + "base": "about:blank", + "href": "http://example.com/%2e.bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%2e.bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com////../..", + "base": "about:blank", + "href": "http://example.com//", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//../..", + "base": "about:blank", + "href": "http://example.com/foo/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo/bar//..", + "base": "about:blank", + "href": "http://example.com/foo/bar/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo/bar/", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo", + "base": "about:blank", + "href": "http://example.com/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%20foo", + "base": "about:blank", + "href": "http://example.com/%20foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%20foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%", + "base": "about:blank", + "href": "http://example.com/foo%", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2", + "base": "about:blank", + "href": "http://example.com/foo%2", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2zbar", + "base": "about:blank", + "href": "http://example.com/foo%2zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%2©zbar", + "base": "about:blank", + "href": "http://example.com/foo%2%C3%82%C2%A9zbar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%2%C3%82%C2%A9zbar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%41%7a", + "base": "about:blank", + "href": "http://example.com/foo%41%7a", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%41%7a", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\t\u0091%91", + "base": "about:blank", + "href": "http://example.com/foo%C2%91%91", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%C2%91%91", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo%00%51", + "base": "about:blank", + "href": "http://example.com/foo%00%51", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foo%00%51", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/(%28:%3A%29)", + "base": "about:blank", + "href": "http://example.com/(%28:%3A%29)", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/(%28:%3A%29)", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%3A%3a%3C%3c", + "base": "about:blank", + "href": "http://example.com/%3A%3a%3C%3c", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%3A%3a%3C%3c", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/foo\tbar", + "base": "about:blank", + "href": "http://example.com/foobar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/foobar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com\\\\foo\\\\bar", + "base": "about:blank", + "href": "http://example.com//foo//bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "//foo//bar", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "base": "about:blank", + "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/@asdf%40", + "base": "about:blank", + "href": "http://example.com/@asdf%40", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/@asdf%40", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/你好你好", + "base": "about:blank", + "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‥/foo", + "base": "about:blank", + "href": "http://example.com/%E2%80%A5/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%A5/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com//foo", + "base": "about:blank", + "href": "http://example.com/%EF%BB%BF/foo", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%EF%BB%BF/foo", + "search": "", + "hash": "" + }, + { + "input": "http://example.com/‮/foo/‭/bar", + "base": "about:blank", + "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js", + { + "input": "http://www.google.com/foo?bar=baz#", + "base": "about:blank", + "href": "http://www.google.com/foo?bar=baz#", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "" + }, + { + "input": "http://www.google.com/foo?bar=baz# »", + "base": "about:blank", + "href": "http://www.google.com/foo?bar=baz# %C2%BB", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "?bar=baz", + "hash": "# %C2%BB" + }, + { + "input": "data:test# »", + "base": "about:blank", + "href": "data:test# %C2%BB", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "# %C2%BB" + }, + { + "input": "http://www.google.com", + "base": "about:blank", + "href": "http://www.google.com/", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.0x00A80001", + "base": "about:blank", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo%2Ehtml", + "base": "about:blank", + "href": "http://www/foo%2Ehtml", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo%2Ehtml", + "search": "", + "hash": "" + }, + { + "input": "http://www/foo/%2E/html", + "base": "about:blank", + "href": "http://www/foo/html", + "origin": "http://www", + "protocol": "http:", + "username": "", + "password": "", + "host": "www", + "hostname": "www", + "port": "", + "pathname": "/foo/html", + "search": "", + "hash": "" + }, + { + "input": "http://user:pass@/", + "base": "about:blank", + "failure": true + }, + { + "input": "http://%25DOMAIN:foobar@foodomain.com/", + "base": "about:blank", + "href": "http://%25DOMAIN:foobar@foodomain.com/", + "origin": "http://foodomain.com", + "protocol": "http:", + "username": "%25DOMAIN", + "password": "foobar", + "host": "foodomain.com", + "hostname": "foodomain.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:\\\\www.google.com\\foo", + "base": "about:blank", + "href": "http://www.google.com/foo", + "origin": "http://www.google.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.google.com", + "hostname": "www.google.com", + "port": "", + "pathname": "/foo", + "search": "", + "hash": "" + }, + { + "input": "http://foo:80/", + "base": "about:blank", + "href": "http://foo/", + "origin": "http://foo", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:81/", + "base": "about:blank", + "href": "http://foo:81/", + "origin": "http://foo:81", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "httpa://foo:80/", + "base": "about:blank", + "href": "httpa://foo:80/", + "origin": "null", + "protocol": "httpa:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://foo:-80/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://foo:443/", + "base": "about:blank", + "href": "https://foo/", + "origin": "https://foo", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://foo:80/", + "base": "about:blank", + "href": "https://foo:80/", + "origin": "https://foo:80", + "protocol": "https:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:21/", + "base": "about:blank", + "href": "ftp://foo/", + "origin": "ftp://foo", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp://foo:80/", + "base": "about:blank", + "href": "ftp://foo:80/", + "origin": "ftp://foo:80", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:70/", + "base": "about:blank", + "href": "gopher://foo/", + "origin": "gopher://foo", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "gopher://foo:443/", + "base": "about:blank", + "href": "gopher://foo:443/", + "origin": "gopher://foo:443", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:80/", + "base": "about:blank", + "href": "ws://foo/", + "origin": "ws://foo", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:81/", + "base": "about:blank", + "href": "ws://foo:81/", + "origin": "ws://foo:81", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:443/", + "base": "about:blank", + "href": "ws://foo:443/", + "origin": "ws://foo:443", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:443", + "hostname": "foo", + "port": "443", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws://foo:815/", + "base": "about:blank", + "href": "ws://foo:815/", + "origin": "ws://foo:815", + "protocol": "ws:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:80/", + "base": "about:blank", + "href": "wss://foo:80/", + "origin": "wss://foo:80", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:80", + "hostname": "foo", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:81/", + "base": "about:blank", + "href": "wss://foo:81/", + "origin": "wss://foo:81", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:81", + "hostname": "foo", + "port": "81", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:443/", + "base": "about:blank", + "href": "wss://foo/", + "origin": "wss://foo", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo", + "hostname": "foo", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss://foo:815/", + "base": "about:blank", + "href": "wss://foo:815/", + "origin": "wss://foo:815", + "protocol": "wss:", + "username": "", + "password": "", + "host": "foo:815", + "hostname": "foo", + "port": "815", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/example.com/", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:/example.com/", + "base": "about:blank", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:/example.com/", + "base": "about:blank", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:/example.com/", + "base": "about:blank", + "href": "madeupscheme:/example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "file:/example.com/", + "base": "about:blank", + "href": "file:///example.com/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:/example.com/", + "base": "about:blank", + "href": "ftps:/example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:/example.com/", + "base": "about:blank", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:/example.com/", + "base": "about:blank", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:/example.com/", + "base": "about:blank", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/example.com/", + "base": "about:blank", + "href": "data:/example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/example.com/", + "base": "about:blank", + "href": "javascript:/example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/example.com/", + "base": "about:blank", + "href": "mailto:/example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/example.com/", + "search": "", + "hash": "" + }, + { + "input": "http:example.com/", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ftp:example.com/", + "base": "about:blank", + "href": "ftp://example.com/", + "origin": "ftp://example.com", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https:example.com/", + "base": "about:blank", + "href": "https://example.com/", + "origin": "https://example.com", + "protocol": "https:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "madeupscheme:example.com/", + "base": "about:blank", + "href": "madeupscheme:example.com/", + "origin": "null", + "protocol": "madeupscheme:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "ftps:example.com/", + "base": "about:blank", + "href": "ftps:example.com/", + "origin": "null", + "protocol": "ftps:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "gopher:example.com/", + "base": "about:blank", + "href": "gopher://example.com/", + "origin": "gopher://example.com", + "protocol": "gopher:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ws:example.com/", + "base": "about:blank", + "href": "ws://example.com/", + "origin": "ws://example.com", + "protocol": "ws:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "wss:example.com/", + "base": "about:blank", + "href": "wss://example.com/", + "origin": "wss://example.com", + "protocol": "wss:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:example.com/", + "base": "about:blank", + "href": "data:example.com/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "javascript:example.com/", + "base": "about:blank", + "href": "javascript:example.com/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + { + "input": "mailto:example.com/", + "base": "about:blank", + "href": "mailto:example.com/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "example.com/", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html", + { + "input": "http:@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:b@www.example.com", + "base": "about:blank", + "href": "http://a:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://@pple.com", + "base": "about:blank", + "href": "http://pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http::b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://:b@www.example.com", + "base": "about:blank", + "href": "http://:b@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "b", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://user@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "https:@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://a:b@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http::@/www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:/a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://a:@www.example.com", + "base": "about:blank", + "href": "http://a@www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "a", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://www.@pple.com", + "base": "about:blank", + "href": "http://www.@pple.com/", + "origin": "http://pple.com", + "protocol": "http:", + "username": "www.", + "password": "", + "host": "pple.com", + "hostname": "pple.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http:@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http:/@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://@:www.example.com", + "base": "about:blank", + "failure": true + }, + { + "input": "http://:@www.example.com", + "base": "about:blank", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# Others", + { + "input": "/", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": ".", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "http://www.example.com/test", + "href": "http://www.example.com/", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "./test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../aaa/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/aaa/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/aaa/test.txt", + "search": "", + "hash": "" + }, + { + "input": "../../test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/test.txt", + "search": "", + "hash": "" + }, + { + "input": "中/test.txt", + "base": "http://www.example.com/test", + "href": "http://www.example.com/%E4%B8%AD/test.txt", + "origin": "http://www.example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example.com", + "hostname": "www.example.com", + "port": "", + "pathname": "/%E4%B8%AD/test.txt", + "search": "", + "hash": "" + }, + { + "input": "http://www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "//www.example2.com", + "base": "http://www.example.com/test", + "href": "http://www.example2.com/", + "origin": "http://www.example2.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.example2.com", + "hostname": "www.example2.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:...", + "base": "http://www.example.com/test", + "href": "file:///...", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/...", + "search": "", + "hash": "" + }, + { + "input": "file:..", + "base": "http://www.example.com/test", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:a", + "base": "http://www.example.com/test", + "href": "file:///a", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/a", + "search": "", + "hash": "" + }, + "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html", + "Basic canonicalization, uppercase should be converted to lowercase", + { + "input": "http://ExAmPlE.CoM", + "base": "http://other.com/", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://example example.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://Goo%20 goo%7C|.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[:]", + "base": "http://other.com/", + "failure": true + }, + "U+3000 is mapped to U+0020 (space) which is disallowed", + { + "input": "http://GOO\u00a0\u3000goo.com", + "base": "http://other.com/", + "failure": true + }, + "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored", + { + "input": "http://GOO\u200b\u2060\ufeffgoo.com", + "base": "http://other.com/", + "href": "http://googoo.com/", + "origin": "http://googoo.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "googoo.com", + "hostname": "googoo.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Leading and trailing C0 control or space", + { + "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ", + "base": "about:blank", + "href": "http://example.com/", + "origin": "http://example.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", + { + "input": "http://www.foo。bar.com", + "base": "http://other.com/", + "href": "http://www.foo.bar.com/", + "origin": "http://www.foo.bar.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "www.foo.bar.com", + "hostname": "www.foo.bar.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0", + { + "input": "http://\ufdd0zyx.com", + "base": "http://other.com/", + "failure": true + }, + "This is the same as previous but escaped", + { + "input": "http://%ef%b7%90zyx.com", + "base": "http://other.com/", + "failure": true + }, + "U+FFFD", + { + "input": "https://\ufffd", + "base": "about:blank", + "failure": true + }, + { + "input": "https://%EF%BF%BD", + "base": "about:blank", + "failure": true + }, + { + "input": "https://x/\ufffd?\ufffd#\ufffd", + "base": "about:blank", + "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD", + "origin": "https://x", + "protocol": "https:", + "username": "", + "password": "", + "host": "x", + "hostname": "x", + "port": "", + "pathname": "/%EF%BF%BD", + "search": "?%EF%BF%BD", + "hash": "#%EF%BF%BD" + }, + "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.", + { + "input": "http://Go.com", + "base": "http://other.com/", + "href": "http://go.com/", + "origin": "http://go.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "go.com", + "hostname": "go.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257", + { + "input": "http://%41.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com", + "base": "http://other.com/", + "failure": true + }, + "...%00 in fullwidth should fail (also as escaped UTF-8 input)", + { + "input": "http://%00.com", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com", + "base": "http://other.com/", + "failure": true + }, + "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN", + { + "input": "http://你好你好", + "base": "http://other.com/", + "href": "http://xn--6qqa088eba/", + "origin": "http://xn--6qqa088eba", + "protocol": "http:", + "username": "", + "password": "", + "host": "xn--6qqa088eba", + "hostname": "xn--6qqa088eba", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://faß.ExAmPlE/", + "base": "about:blank", + "href": "https://xn--fa-hia.example/", + "origin": "https://xn--fa-hia.example", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--fa-hia.example", + "hostname": "xn--fa-hia.example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://faß.ExAmPlE/", + "base": "about:blank", + "href": "sc://fa%C3%9F.ExAmPlE/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "fa%C3%9F.ExAmPlE", + "hostname": "fa%C3%9F.ExAmPlE", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191", + { + "input": "http://%zz%66%a.com", + "base": "http://other.com/", + "failure": true + }, + "If we get an invalid character that has been escaped.", + { + "input": "http://%25", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://hello%00", + "base": "http://other.com/", + "failure": true + }, + "Escaped numbers should be treated like IP addresses if they are.", + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.0.257", + "base": "http://other.com/", + "failure": true + }, + "Invalid escaping in hosts causes failure", + { + "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01", + "base": "http://other.com/", + "failure": true + }, + "A space in a host causes failure", + { + "input": "http://192.168.0.1 hello", + "base": "http://other.com/", + "failure": true + }, + { + "input": "https://x x:12", + "base": "about:blank", + "failure": true + }, + "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP", + { + "input": "http://0Xc0.0250.01", + "base": "http://other.com/", + "href": "http://192.168.0.1/", + "origin": "http://192.168.0.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.0.1", + "hostname": "192.168.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Domains with empty labels", + { + "input": "http://./", + "base": "about:blank", + "href": "http://./", + "origin": "http://.", + "protocol": "http:", + "username": "", + "password": "", + "host": ".", + "hostname": ".", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://../", + "base": "about:blank", + "href": "http://../", + "origin": "http://..", + "protocol": "http:", + "username": "", + "password": "", + "host": "..", + "hostname": "..", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0..0x300/", + "base": "about:blank", + "href": "http://0..0x300/", + "origin": "http://0..0x300", + "protocol": "http:", + "username": "", + "password": "", + "host": "0..0x300", + "hostname": "0..0x300", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Broken IPv6", + { + "input": "http://[www.google.com]/", + "base": "about:blank", + "failure": true + }, + { + "input": "http://[google.com]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.4x]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.3.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.2.]", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://[::1.]", + "base": "http://other.com/", + "failure": true + }, + "Misc Unicode", + { + "input": "http://foo:💩@example.com/bar", + "base": "http://other.com/", + "href": "http://foo:%F0%9F%92%A9@example.com/bar", + "origin": "http://example.com", + "protocol": "http:", + "username": "foo", + "password": "%F0%9F%92%A9", + "host": "example.com", + "hostname": "example.com", + "port": "", + "pathname": "/bar", + "search": "", + "hash": "" + }, + "# resolving a fragment against any scheme succeeds", + { + "input": "#", + "base": "test:test", + "href": "test:test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "", + "hash": "" + }, + { + "input": "#x", + "base": "mailto:x@x.com", + "href": "mailto:x@x.com#x", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "x@x.com", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "data:,", + "href": "data:,#x", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ",", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "about:blank", + "href": "about:blank#x", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "blank", + "search": "", + "hash": "#x" + }, + { + "input": "#", + "base": "test:test?test", + "href": "test:test?test#", + "origin": "null", + "protocol": "test:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "test", + "search": "?test", + "hash": "" + }, + "# multiple @ in authority state", + { + "input": "https://@test@test@example:800/", + "base": "http://doesnotmatter/", + "href": "https://%40test%40test@example:800/", + "origin": "https://example:800", + "protocol": "https:", + "username": "%40test%40test", + "password": "", + "host": "example:800", + "hostname": "example", + "port": "800", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://@@@example", + "base": "http://doesnotmatter/", + "href": "https://%40%40@example/", + "origin": "https://example", + "protocol": "https:", + "username": "%40%40", + "password": "", + "host": "example", + "hostname": "example", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "non-az-09 characters", + { + "input": "http://`{}:`{}@h/`{}?`{}", + "base": "http://doesnotmatter/", + "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}", + "origin": "http://h", + "protocol": "http:", + "username": "%60%7B%7D", + "password": "%60%7B%7D", + "host": "h", + "hostname": "h", + "port": "", + "pathname": "/%60%7B%7D", + "search": "?`{}", + "hash": "" + }, + "# Credentials in base", + { + "input": "/some/path", + "base": "http://user@example.org/smth", + "href": "http://user@example.org/some/path", + "origin": "http://example.org", + "protocol": "http:", + "username": "user", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/smth", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/smth", + "search": "", + "hash": "" + }, + { + "input": "/some/path", + "base": "http://user:pass@example.org:21/smth", + "href": "http://user:pass@example.org:21/some/path", + "origin": "http://example.org:21", + "protocol": "http:", + "username": "user", + "password": "pass", + "host": "example.org:21", + "hostname": "example.org", + "port": "21", + "pathname": "/some/path", + "search": "", + "hash": "" + }, + "# a set of tests designed by zcorpan for relative URLs with unknown schemes", + { + "input": "i", + "base": "sc:sd", + "failure": true + }, + { + "input": "i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "i", + "base": "sc:/pa/pa", + "href": "sc:/pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "i", + "base": "sc:///pa/pa", + "href": "sc:///pa/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "../i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "../i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "/i", + "base": "sc:/pa/pa", + "href": "sc:/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc://ho/pa", + "href": "sc://ho/i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "/i", + "base": "sc:///pa/pa", + "href": "sc:///i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/i", + "search": "", + "hash": "" + }, + { + "input": "?i", + "base": "sc:sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:sd/sd", + "failure": true + }, + { + "input": "?i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc://ho/pa", + "href": "sc://ho/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "?i", + "hash": "" + }, + { + "input": "?i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa?i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "?i", + "hash": "" + }, + { + "input": "#i", + "base": "sc:sd", + "href": "sc:sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:sd/sd", + "href": "sc:sd/sd#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "sd/sd", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:/pa/pa", + "href": "sc:/pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc://ho/pa", + "href": "sc://ho/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "ho", + "hostname": "ho", + "port": "", + "pathname": "/pa", + "search": "", + "hash": "#i" + }, + { + "input": "#i", + "base": "sc:///pa/pa", + "href": "sc:///pa/pa#i", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pa/pa", + "search": "", + "hash": "#i" + }, + "# make sure that relative URL logic works on known typically non-relative schemes too", + { + "input": "about:/../", + "base": "about:blank", + "href": "about:/", + "origin": "null", + "protocol": "about:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "data:/../", + "base": "about:blank", + "href": "data:/", + "origin": "null", + "protocol": "data:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "javascript:/../", + "base": "about:blank", + "href": "javascript:/", + "origin": "null", + "protocol": "javascript:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "mailto:/../", + "base": "about:blank", + "href": "mailto:/", + "origin": "null", + "protocol": "mailto:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# unknown schemes and their hosts", + { + "input": "sc://ñ.test/", + "base": "about:blank", + "href": "sc://%C3%B1.test/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1.test", + "hostname": "%C3%B1.test", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/", + "base": "about:blank", + "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://\u0000/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc:// /", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://%/", + "base": "about:blank", + "href": "sc://%/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%", + "hostname": "%", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "sc://@/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://te@s:t@/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://:/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://:12/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://[/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://\\/", + "base": "about:blank", + "failure": true + }, + { + "input": "sc://]/", + "base": "about:blank", + "failure": true + }, + { + "input": "x", + "base": "sc://ñ", + "href": "sc://%C3%B1/x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "/x", + "search": "", + "hash": "" + }, + "# unknown schemes and backslashes", + { + "input": "sc:\\../", + "base": "about:blank", + "href": "sc:\\../", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "\\../", + "search": "", + "hash": "" + }, + "# unknown scheme with path looking like a password", + { + "input": "sc::a@example.net", + "base": "about:blank", + "href": "sc::a@example.net", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": ":a@example.net", + "search": "", + "hash": "" + }, + "# unknown scheme with bogus percent-encoding", + { + "input": "wow:%NBD", + "base": "about:blank", + "href": "wow:%NBD", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%NBD", + "search": "", + "hash": "" + }, + { + "input": "wow:%1G", + "base": "about:blank", + "href": "wow:%1G", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%1G", + "search": "", + "hash": "" + }, + "# Hosts and percent-encoding", + { + "input": "ftp://example.com%80/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftp://example.com%A0/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://example.com%80/", + "base": "about:blank", + "failure": true + }, + { + "input": "https://example.com%A0/", + "base": "about:blank", + "failure": true + }, + { + "input": "ftp://%e2%98%83", + "base": "about:blank", + "href": "ftp://xn--n3h/", + "origin": "ftp://xn--n3h", + "protocol": "ftp:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://%e2%98%83", + "base": "about:blank", + "href": "https://xn--n3h/", + "origin": "https://xn--n3h", + "protocol": "https:", + "username": "", + "password": "", + "host": "xn--n3h", + "hostname": "xn--n3h", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# tests from jsdom/whatwg-url designed for code coverage", + { + "input": "http://127.0.0.1:10100/relative_import.html", + "base": "about:blank", + "href": "http://127.0.0.1:10100/relative_import.html", + "origin": "http://127.0.0.1:10100", + "protocol": "http:", + "username": "", + "password": "", + "host": "127.0.0.1:10100", + "hostname": "127.0.0.1", + "port": "10100", + "pathname": "/relative_import.html", + "search": "", + "hash": "" + }, + { + "input": "http://facebook.com/?foo=%7B%22abc%22", + "base": "about:blank", + "href": "http://facebook.com/?foo=%7B%22abc%22", + "origin": "http://facebook.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "facebook.com", + "hostname": "facebook.com", + "port": "", + "pathname": "/", + "search": "?foo=%7B%22abc%22", + "hash": "" + }, + { + "input": "https://localhost:3000/jqueryui@1.2.3", + "base": "about:blank", + "href": "https://localhost:3000/jqueryui@1.2.3", + "origin": "https://localhost:3000", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost:3000", + "hostname": "localhost", + "port": "3000", + "pathname": "/jqueryui@1.2.3", + "search": "", + "hash": "" + }, + "# tab/LF/CR", + { + "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg", + "base": "about:blank", + "href": "http://host:9000/path?query#frag", + "origin": "http://host:9000", + "protocol": "http:", + "username": "", + "password": "", + "host": "host:9000", + "hostname": "host", + "port": "9000", + "pathname": "/path", + "search": "?query", + "hash": "#frag" + }, + "# Stringification of URL.searchParams", + { + "input": "?a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar?a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "?a=b&c=d", + "searchParams": "a=b&c=d", + "hash": "" + }, + { + "input": "??a=b&c=d", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar??a=b&c=d", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "??a=b&c=d", + "searchParams": "%3Fa=b&c=d", + "hash": "" + }, + "# Scheme only", + { + "input": "http:", + "base": "http://example.org/foo/bar", + "href": "http://example.org/foo/bar", + "origin": "http://example.org", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/foo/bar", + "search": "", + "searchParams": "", + "hash": "" + }, + { + "input": "http:", + "base": "https://example.org/foo/bar", + "failure": true + }, + { + "input": "sc:", + "base": "https://example.org/foo/bar", + "href": "sc:", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "searchParams": "", + "hash": "" + }, + "# Percent encoding of fragments", + { + "input": "http://foo.bar/baz?qux#foo\bbar", + "base": "about:blank", + "href": "http://foo.bar/baz?qux#foo%08bar", + "origin": "http://foo.bar", + "protocol": "http:", + "username": "", + "password": "", + "host": "foo.bar", + "hostname": "foo.bar", + "port": "", + "pathname": "/baz", + "search": "?qux", + "searchParams": "qux=", + "hash": "#foo%08bar" + }, + "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)", + { + "input": "http://192.168.257", + "base": "http://other.com/", + "href": "http://192.168.1.1/", + "origin": "http://192.168.1.1", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.1.1", + "hostname": "192.168.1.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://192.168.257.com", + "base": "http://other.com/", + "href": "http://192.168.257.com/", + "origin": "http://192.168.257.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "192.168.257.com", + "hostname": "192.168.257.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256", + "base": "http://other.com/", + "href": "http://0.0.1.0/", + "origin": "http://0.0.1.0", + "protocol": "http:", + "username": "", + "password": "", + "host": "0.0.1.0", + "hostname": "0.0.1.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://256.com", + "base": "http://other.com/", + "href": "http://256.com/", + "origin": "http://256.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.com", + "hostname": "256.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999", + "base": "http://other.com/", + "href": "http://59.154.201.255/", + "origin": "http://59.154.201.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "59.154.201.255", + "hostname": "59.154.201.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://999999999.com", + "base": "http://other.com/", + "href": "http://999999999.com/", + "origin": "http://999999999.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "999999999.com", + "hostname": "999999999.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://10000000000", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://10000000000.com", + "base": "http://other.com/", + "href": "http://10000000000.com/", + "origin": "http://10000000000.com", + "protocol": "http:", + "username": "", + "password": "", + "host": "10000000000.com", + "hostname": "10000000000.com", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967295", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://4294967296", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://0xffffffff", + "base": "http://other.com/", + "href": "http://255.255.255.255/", + "origin": "http://255.255.255.255", + "protocol": "http:", + "username": "", + "password": "", + "host": "255.255.255.255", + "hostname": "255.255.255.255", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0xffffffff1", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256", + "base": "http://other.com/", + "failure": true + }, + { + "input": "http://256.256.256.256.256", + "base": "http://other.com/", + "href": "http://256.256.256.256.256/", + "origin": "http://256.256.256.256.256", + "protocol": "http:", + "username": "", + "password": "", + "host": "256.256.256.256.256", + "hostname": "256.256.256.256.256", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "https://0x.0x.0", + "base": "about:blank", + "href": "https://0.0.0.0/", + "origin": "https://0.0.0.0", + "protocol": "https:", + "username": "", + "password": "", + "host": "0.0.0.0", + "hostname": "0.0.0.0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)", + { + "input": "https://0x100000000/test", + "base": "about:blank", + "failure": true + }, + { + "input": "https://256.0.0.1/test", + "base": "about:blank", + "failure": true + }, + "# file URLs containing percent-encoded Windows drive letters (shouldn't work)", + { + "input": "file:///C%3A/", + "base": "about:blank", + "href": "file:///C%3A/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%3A/", + "search": "", + "hash": "" + }, + { + "input": "file:///C%7C/", + "base": "about:blank", + "href": "file:///C%7C/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C%7C/", + "search": "", + "hash": "" + }, + "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)", + { + "input": "pix/submit.gif", + "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html", + "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///C:/", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# More file URL tests by zcorpan and annevk", + { + "input": "/", + "base": "file:///C:/a/b", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "//d:", + "base": "file:///C:/a/b", + "href": "file:///d:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:", + "search": "", + "hash": "" + }, + { + "input": "//d:/..", + "base": "file:///C:/a/b", + "href": "file:///d:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/d:/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///ab:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "..", + "base": "file:///1:/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "file:", + "base": "file:///test?test#test", + "href": "file:///test?test", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "" + }, + { + "input": "?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "file:?x", + "base": "file:///test?test#test", + "href": "file:///test?x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?x", + "hash": "" + }, + { + "input": "#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + { + "input": "file:#x", + "base": "file:///test?test#test", + "href": "file:///test?test#x", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test", + "search": "?test", + "hash": "#x" + }, + "# File URLs and many (back)slashes", + { + "input": "file:\\\\//", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\\\\\?fox", + "base": "about:blank", + "href": "file:///?fox", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "?fox", + "hash": "" + }, + { + "input": "file:\\\\\\\\#guppy", + "base": "about:blank", + "href": "file:///#guppy", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "#guppy" + }, + { + "input": "file://spider///", + "base": "about:blank", + "href": "file://spider/", + "protocol": "file:", + "username": "", + "password": "", + "host": "spider", + "hostname": "spider", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:\\\\localhost//", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:///localhost//cat", + "base": "about:blank", + "href": "file:///localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://\\/localhost//cat", + "base": "about:blank", + "href": "file:///localhost//cat", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/localhost//cat", + "search": "", + "hash": "" + }, + { + "input": "file://localhost//a//../..//", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "/////mouse", + "base": "file:///elephant", + "href": "file:///mouse", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/mouse", + "search": "", + "hash": "" + }, + { + "input": "\\//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "\\/localhost//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "//localhost//pig", + "base": "file://lion/", + "href": "file:///pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/pig", + "search": "", + "hash": "" + }, + { + "input": "/..//localhost//pig", + "base": "file://lion/", + "href": "file://lion/localhost//pig", + "protocol": "file:", + "username": "", + "password": "", + "host": "lion", + "hostname": "lion", + "port": "", + "pathname": "/localhost//pig", + "search": "", + "hash": "" + }, + { + "input": "file://", + "base": "file://ape/", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "# File URLs with non-empty hosts", + { + "input": "/rooibos", + "base": "file://tea/", + "href": "file://tea/rooibos", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/rooibos", + "search": "", + "hash": "" + }, + { + "input": "/?chai", + "base": "file://tea/", + "href": "file://tea/?chai", + "protocol": "file:", + "username": "", + "password": "", + "host": "tea", + "hostname": "tea", + "port": "", + "pathname": "/", + "search": "?chai", + "hash": "" + }, + "# Windows drive letter handling with the 'file:' base URL", + { + "input": "C|", + "base": "file://host/dir/file", + "href": "file:///C:", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|#", + "base": "file://host/dir/file", + "href": "file:///C:#", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|?", + "base": "file://host/dir/file", + "href": "file:///C:?", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:", + "search": "", + "hash": "" + }, + { + "input": "C|/", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\n/", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C|\\", + "base": "file://host/dir/file", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "C", + "base": "file://host/dir/file", + "href": "file://host/dir/C", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C", + "search": "", + "hash": "" + }, + { + "input": "C|a", + "base": "file://host/dir/file", + "href": "file://host/dir/C|a", + "protocol": "file:", + "username": "", + "password": "", + "host": "host", + "hostname": "host", + "port": "", + "pathname": "/dir/C|a", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk in the file slash state", + { + "input": "/c:/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c|/foo/bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "file:\\c:\\foo\\bar", + "base": "file:///c:/baz/qux", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "/c:/foo/bar", + "base": "file://host/path", + "href": "file:///c:/foo/bar", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/c:/foo/bar", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk with not empty host", + { + "input": "file://example.net/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://1.2.3.4/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://[1::8]/C:/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# Windows drive letter quirk (no host)", + { + "input": "file:/C|/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + { + "input": "file://C|/", + "base": "about:blank", + "href": "file:///C:/", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/C:/", + "search": "", + "hash": "" + }, + "# file URLs without base URL by Rimas Misevičius", + { + "input": "file:", + "base": "about:blank", + "href": "file:///", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "file:?q=v", + "base": "about:blank", + "href": "file:///?q=v", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "?q=v", + "hash": "" + }, + { + "input": "file:#frag", + "base": "about:blank", + "href": "file:///#frag", + "protocol": "file:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "#frag" + }, + "# IPv6 tests", + { + "input": "http://[1:0::]", + "base": "http://example.net/", + "href": "http://[1::]/", + "origin": "http://[1::]", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1::]", + "hostname": "[1::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[0:1:2:3:4:5:6:7:8]", + "base": "http://example.net/", + "failure": true + }, + { + "input": "https://[0::0::0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:0:]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.00.0.0.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.290.0.0.0]", + "base": "about:blank", + "failure": true + }, + { + "input": "https://[0:1.23.23]", + "base": "about:blank", + "failure": true + }, + "# Empty host", + { + "input": "http://?", + "base": "about:blank", + "failure": true + }, + { + "input": "http://#", + "base": "about:blank", + "failure": true + }, + "Port overflow (2^32 + 81)", + { + "input": "http://f:4294967377/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^64 + 81)", + { + "input": "http://f:18446744073709551697/c", + "base": "http://example.org/", + "failure": true + }, + "Port overflow (2^128 + 81)", + { + "input": "http://f:340282366920938463463374607431768211537/c", + "base": "http://example.org/", + "failure": true + }, + "# Non-special-URL path tests", + { + "input": "sc://ñ", + "base": "about:blank", + "href": "sc://%C3%B1", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://ñ?x", + "base": "about:blank", + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://ñ#x", + "base": "about:blank", + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "#x", + "base": "sc://ñ", + "href": "sc://%C3%B1#x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "", + "hash": "#x" + }, + { + "input": "?x", + "base": "sc://ñ", + "href": "sc://%C3%B1?x", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%C3%B1", + "hostname": "%C3%B1", + "port": "", + "pathname": "", + "search": "?x", + "hash": "" + }, + { + "input": "sc://?", + "base": "about:blank", + "href": "sc://?", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "sc://#", + "base": "about:blank", + "href": "sc://#", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, + { + "input": "///", + "base": "sc://x/", + "href": "sc:///", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "////", + "base": "sc://x/", + "href": "sc:////", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "////x/", + "base": "sc://x/", + "href": "sc:////x/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//x/", + "search": "", + "hash": "" + }, + { + "input": "tftp://foobar.com/someconfig;mode=netascii", + "base": "about:blank", + "href": "tftp://foobar.com/someconfig;mode=netascii", + "origin": "null", + "protocol": "tftp:", + "username": "", + "password": "", + "host": "foobar.com", + "hostname": "foobar.com", + "port": "", + "pathname": "/someconfig;mode=netascii", + "search": "", + "hash": "" + }, + { + "input": "telnet://user:pass@foobar.com:23/", + "base": "about:blank", + "href": "telnet://user:pass@foobar.com:23/", + "origin": "null", + "protocol": "telnet:", + "username": "user", + "password": "pass", + "host": "foobar.com:23", + "hostname": "foobar.com", + "port": "23", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "ut2004://10.10.10.10:7777/Index.ut2", + "base": "about:blank", + "href": "ut2004://10.10.10.10:7777/Index.ut2", + "origin": "null", + "protocol": "ut2004:", + "username": "", + "password": "", + "host": "10.10.10.10:7777", + "hostname": "10.10.10.10", + "port": "7777", + "pathname": "/Index.ut2", + "search": "", + "hash": "" + }, + { + "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "base": "about:blank", + "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz", + "origin": "null", + "protocol": "redis:", + "username": "foo", + "password": "bar", + "host": "somehost:6379", + "hostname": "somehost", + "port": "6379", + "pathname": "/0", + "search": "?baz=bam&qux=baz", + "hash": "" + }, + { + "input": "rsync://foo@host:911/sup", + "base": "about:blank", + "href": "rsync://foo@host:911/sup", + "origin": "null", + "protocol": "rsync:", + "username": "foo", + "password": "", + "host": "host:911", + "hostname": "host", + "port": "911", + "pathname": "/sup", + "search": "", + "hash": "" + }, + { + "input": "git://github.com/foo/bar.git", + "base": "about:blank", + "href": "git://github.com/foo/bar.git", + "origin": "null", + "protocol": "git:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar.git", + "search": "", + "hash": "" + }, + { + "input": "irc://myserver.com:6999/channel?passwd", + "base": "about:blank", + "href": "irc://myserver.com:6999/channel?passwd", + "origin": "null", + "protocol": "irc:", + "username": "", + "password": "", + "host": "myserver.com:6999", + "hostname": "myserver.com", + "port": "6999", + "pathname": "/channel", + "search": "?passwd", + "hash": "" + }, + { + "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "base": "about:blank", + "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT", + "origin": "null", + "protocol": "dns:", + "username": "", + "password": "", + "host": "fw.example.org:9999", + "hostname": "fw.example.org", + "port": "9999", + "pathname": "/foo.bar.org", + "search": "?type=TXT", + "hash": "" + }, + { + "input": "ldap://localhost:389/ou=People,o=JNDITutorial", + "base": "about:blank", + "href": "ldap://localhost:389/ou=People,o=JNDITutorial", + "origin": "null", + "protocol": "ldap:", + "username": "", + "password": "", + "host": "localhost:389", + "hostname": "localhost", + "port": "389", + "pathname": "/ou=People,o=JNDITutorial", + "search": "", + "hash": "" + }, + { + "input": "git+https://github.com/foo/bar", + "base": "about:blank", + "href": "git+https://github.com/foo/bar", + "origin": "null", + "protocol": "git+https:", + "username": "", + "password": "", + "host": "github.com", + "hostname": "github.com", + "port": "", + "pathname": "/foo/bar", + "search": "", + "hash": "" + }, + { + "input": "urn:ietf:rfc:2648", + "base": "about:blank", + "href": "urn:ietf:rfc:2648", + "origin": "null", + "protocol": "urn:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "ietf:rfc:2648", + "search": "", + "hash": "" + }, + { + "input": "tag:joe@example.org,2001:foo/bar", + "base": "about:blank", + "href": "tag:joe@example.org,2001:foo/bar", + "origin": "null", + "protocol": "tag:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "joe@example.org,2001:foo/bar", + "search": "", + "hash": "" + }, + "# percent encoded hosts in non-special-URLs", + { + "input": "non-special://%E2%80%A0/", + "base": "about:blank", + "href": "non-special://%E2%80%A0/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "%E2%80%A0", + "hostname": "%E2%80%A0", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://H%4fSt/path", + "base": "about:blank", + "href": "non-special://H%4fSt/path", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "H%4fSt", + "hostname": "H%4fSt", + "port": "", + "pathname": "/path", + "search": "", + "hash": "" + }, + "# IPv6 in non-special-URLs", + { + "input": "non-special://[1:2:0:0:5:0:0:0]/", + "base": "about:blank", + "href": "non-special://[1:2:0:0:5::]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2:0:0:5::]", + "hostname": "[1:2:0:0:5::]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2:0:0:0:0:0:3]/", + "base": "about:blank", + "href": "non-special://[1:2::3]/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]", + "hostname": "[1:2::3]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[1:2::3]:80/", + "base": "about:blank", + "href": "non-special://[1:2::3]:80/", + "protocol": "non-special:", + "username": "", + "password": "", + "host": "[1:2::3]:80", + "hostname": "[1:2::3]", + "port": "80", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "non-special://[:80/", + "base": "about:blank", + "failure": true + }, + { + "input": "blob:https://example.com:443/", + "base": "about:blank", + "href": "blob:https://example.com:443/", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "https://example.com:443/", + "search": "", + "hash": "" + }, + { + "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "base": "about:blank", + "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf", + "protocol": "blob:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf", + "search": "", + "hash": "" + }, + "Invalid IPv4 radix digits", + { + "input": "http://0177.0.0.0189", + "base": "about:blank", + "href": "http://0177.0.0.0189/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0177.0.0.0189", + "hostname": "0177.0.0.0189", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0x7f.0.0.0x7g", + "base": "about:blank", + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://0X7F.0.0.0X7G", + "base": "about:blank", + "href": "http://0x7f.0.0.0x7g/", + "protocol": "http:", + "username": "", + "password": "", + "host": "0x7f.0.0.0x7g", + "hostname": "0x7f.0.0.0x7g", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Invalid IPv4 portion of IPv6 address", + { + "input": "http://[::127.0.0.0.1]", + "base": "about:blank", + "failure": true + }, + "Uncompressed IPv6 addresses with 0", + { + "input": "http://[0:1:0:1:0:1:0:1]", + "base": "about:blank", + "href": "http://[0:1:0:1:0:1:0:1]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[0:1:0:1:0:1:0:1]", + "hostname": "[0:1:0:1:0:1:0:1]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + { + "input": "http://[1:0:1:0:1:0:1:0]", + "base": "about:blank", + "href": "http://[1:0:1:0:1:0:1:0]/", + "protocol": "http:", + "username": "", + "password": "", + "host": "[1:0:1:0:1:0:1:0]", + "hostname": "[1:0:1:0:1:0:1:0]", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, + "Percent-encoded query and fragment", + { + "input": "http://example.org/test?\u0022", + "base": "about:blank", + "href": "http://example.org/test?%22", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%22", + "hash": "" + }, + { + "input": "http://example.org/test?\u0023", + "base": "about:blank", + "href": "http://example.org/test?#", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "", + "hash": "" + }, + { + "input": "http://example.org/test?\u003C", + "base": "about:blank", + "href": "http://example.org/test?%3C", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3C", + "hash": "" + }, + { + "input": "http://example.org/test?\u003E", + "base": "about:blank", + "href": "http://example.org/test?%3E", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%3E", + "hash": "" + }, + { + "input": "http://example.org/test?\u2323", + "base": "about:blank", + "href": "http://example.org/test?%E2%8C%A3", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%E2%8C%A3", + "hash": "" + }, + { + "input": "http://example.org/test?%23%23", + "base": "about:blank", + "href": "http://example.org/test?%23%23", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%23%23", + "hash": "" + }, + { + "input": "http://example.org/test?%GH", + "base": "about:blank", + "href": "http://example.org/test?%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?%GH", + "hash": "" + }, + { + "input": "http://example.org/test?a#%EF", + "base": "about:blank", + "href": "http://example.org/test?a#%EF", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%EF" + }, + { + "input": "http://example.org/test?a#%GH", + "base": "about:blank", + "href": "http://example.org/test?a#%GH", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#%GH" + }, + "Bad bases", + { + "input": "test-a.html", + "base": "a", + "failure": true + }, + { + "input": "test-a-slash.html", + "base": "a/", + "failure": true + }, + { + "input": "test-a-slash-slash.html", + "base": "a//", + "failure": true + }, + { + "input": "test-a-colon.html", + "base": "a:", + "failure": true + }, + { + "input": "test-a-colon-slash.html", + "base": "a:/", + "href": "a:/test-a-colon-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash.html", + "base": "a://", + "href": "a:///test-a-colon-slash-slash.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-slash.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-b.html", + "base": "a:b", + "failure": true + }, + { + "input": "test-a-colon-slash-b.html", + "base": "a:/b", + "href": "a:/test-a-colon-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "/test-a-colon-slash-b.html", + "search": "", + "hash": "" + }, + { + "input": "test-a-colon-slash-slash-b.html", + "base": "a://b", + "href": "a://b/test-a-colon-slash-slash-b.html", + "protocol": "a:", + "username": "", + "password": "", + "host": "b", + "hostname": "b", + "port": "", + "pathname": "/test-a-colon-slash-slash-b.html", + "search": "", + "hash": "" + }, + "Null code point in fragment", + { + "input": "http://example.org/test?a#b\u0000c", + "base": "about:blank", + "href": "http://example.org/test?a#bc", + "protocol": "http:", + "username": "", + "password": "", + "host": "example.org", + "hostname": "example.org", + "port": "", + "pathname": "/test", + "search": "?a", + "hash": "#bc" + } +] diff --git a/test/fixtures/url-toascii.js b/test/fixtures/url-toascii.js new file mode 100644 index 00000000000000..59b76330f867f2 --- /dev/null +++ b/test/fixtures/url-toascii.js @@ -0,0 +1,157 @@ +'use strict'; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/4839a0a804/url/toascii.json + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +module.exports = +[ + "This resource is focused on highlighting issues with UTS #46 ToASCII", + { + "comment": "Label with hyphens in 3rd and 4th position", + "input": "aa--", + "output": "aa--" + }, + { + "input": "a†--", + "output": "xn--a---kp0a" + }, + { + "input": "ab--c", + "output": "ab--c" + }, + { + "comment": "Label with leading hyphen", + "input": "-x", + "output": "-x" + }, + { + "input": "-†", + "output": "xn----xhn" + }, + { + "input": "-x.xn--nxa", + "output": "-x.xn--nxa" + }, + { + "input": "-x.β", + "output": "-x.xn--nxa" + }, + { + "comment": "Label with trailing hyphen", + "input": "x-.xn--nxa", + "output": "x-.xn--nxa" + }, + { + "input": "x-.β", + "output": "x-.xn--nxa" + }, + { + "comment": "Empty labels", + "input": "x..xn--nxa", + "output": "x..xn--nxa" + }, + { + "input": "x..β", + "output": "x..xn--nxa" + }, + { + "comment": "Invalid Punycode", + "input": "xn--a", + "output": null + }, + { + "input": "xn--a.xn--nxa", + "output": null + }, + { + "input": "xn--a.β", + "output": null + }, + { + "comment": "Valid Punycode", + "input": "xn--nxa.xn--nxa", + "output": "xn--nxa.xn--nxa" + }, + { + "comment": "Mixed", + "input": "xn--nxa.β", + "output": "xn--nxa.xn--nxa" + }, + { + "input": "ab--c.xn--nxa", + "output": "ab--c.xn--nxa" + }, + { + "input": "ab--c.β", + "output": "ab--c.xn--nxa" + }, + { + "comment": "CheckJoiners is true", + "input": "\u200D.example", + "output": null + }, + { + "input": "xn--1ug.example", + "output": null + }, + { + "comment": "CheckBidi is true", + "input": "يa", + "output": null + }, + { + "input": "xn--a-yoc", + "output": null + }, + { + "comment": "processing_option is Nontransitional_Processing", + "input": "ශ්‍රී", + "output": "xn--10cl1a0b660p" + }, + { + "input": "نامه‌ای", + "output": "xn--mgba3gch31f060k" + }, + { + "comment": "U+FFFD", + "input": "\uFFFD.com", + "output": null + }, + { + "comment": "U+FFFD character encoded in Punycode", + "input": "xn--zn7c.com", + "output": null + }, + { + "comment": "Label longer than 63 code points", + "input": "x01234567890123456789012345678901234567890123456789012345678901x", + "output": "x01234567890123456789012345678901234567890123456789012345678901x" + }, + { + "input": "x01234567890123456789012345678901234567890123456789012345678901†", + "output": "xn--x01234567890123456789012345678901234567890123456789012345678901-6963b" + }, + { + "input": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa", + "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa" + }, + { + "input": "x01234567890123456789012345678901234567890123456789012345678901x.β", + "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa" + }, + { + "comment": "Domain excluding TLD longer than 253 code points", + "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x", + "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x" + }, + { + "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa", + "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa" + }, + { + "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.β", + "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa" + } +] diff --git a/test/internet/test-dgram-multicast-multi-process.js b/test/internet/test-dgram-multicast-multi-process.js index 0bcfead879c7ad..52e7ea4b3a007d 100644 --- a/test/internet/test-dgram-multicast-multi-process.js +++ b/test/internet/test-dgram-multicast-multi-process.js @@ -8,6 +8,7 @@ const assert = require('assert'); const dgram = require('dgram'); const fork = require('child_process').fork; const LOCAL_BROADCAST_HOST = '224.0.0.114'; +const LOCAL_HOST_IFADDR = '0.0.0.0'; const TIMEOUT = common.platformTimeout(5000); const messages = [ Buffer.from('First message to send'), @@ -136,6 +137,7 @@ if (process.argv[2] !== 'child') { sendSocket.setBroadcast(true); sendSocket.setMulticastTTL(1); sendSocket.setMulticastLoopback(true); + sendSocket.setMulticastInterface(LOCAL_HOST_IFADDR); }); sendSocket.on('close', function() { @@ -175,7 +177,7 @@ if (process.argv[2] === 'child') { }); listenSocket.on('listening', function() { - listenSocket.addMembership(LOCAL_BROADCAST_HOST); + listenSocket.addMembership(LOCAL_BROADCAST_HOST, LOCAL_HOST_IFADDR); listenSocket.on('message', function(buf, rinfo) { console.error('[CHILD] %s received "%s" from %j', process.pid, diff --git a/test/parallel/test-buffer-slice.js b/test/parallel/test-buffer-slice.js index b43654b952de0b..b6760785a43dfe 100644 --- a/test/parallel/test-buffer-slice.js +++ b/test/parallel/test-buffer-slice.js @@ -7,80 +7,55 @@ assert.strictEqual(0, Buffer.from('hello', 'utf8').slice(0, 0).length); assert.strictEqual(0, Buffer('hello', 'utf8').slice(0, 0).length); const buf = Buffer.from('0123456789', 'utf8'); -assert.strictEqual(0, Buffer.compare(buf.slice(-10, 10), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-20, 10), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-20, -10), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0, 0), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(undefined), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('foobar'), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(undefined, undefined), - Buffer.from('0123456789', 'utf8'))); +const expectedSameBufs = [ + [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice(-20, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice(-20, -10), Buffer.from('', 'utf8')], + [buf.slice(), Buffer.from('0123456789', 'utf8')], + [buf.slice(0), Buffer.from('0123456789', 'utf8')], + [buf.slice(0, 0), Buffer.from('', 'utf8')], + [buf.slice(undefined), Buffer.from('0123456789', 'utf8')], + [buf.slice('foobar'), Buffer.from('0123456789', 'utf8')], + [buf.slice(undefined, undefined), Buffer.from('0123456789', 'utf8')], + [buf.slice(2), Buffer.from('23456789', 'utf8')], + [buf.slice(5), Buffer.from('56789', 'utf8')], + [buf.slice(10), Buffer.from('', 'utf8')], + [buf.slice(5, 8), Buffer.from('567', 'utf8')], + [buf.slice(8, -1), Buffer.from('8', 'utf8')], + [buf.slice(-10), Buffer.from('0123456789', 'utf8')], + [buf.slice(0, -9), Buffer.from('0', 'utf8')], + [buf.slice(0, -10), Buffer.from('', 'utf8')], + [buf.slice(0, -1), Buffer.from('012345678', 'utf8')], + [buf.slice(2, -2), Buffer.from('234567', 'utf8')], + [buf.slice(0, 65536), Buffer.from('0123456789', 'utf8')], + [buf.slice(65536, 0), Buffer.from('', 'utf8')], + [buf.slice(-5, -8), Buffer.from('', 'utf8')], + [buf.slice(-5, -3), Buffer.from('56', 'utf8')], + [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice('0', '1'), Buffer.from('0', 'utf8')], + [buf.slice('-5', '10'), Buffer.from('56789', 'utf8')], + [buf.slice('-10', '10'), Buffer.from('0123456789', 'utf8')], + [buf.slice('-10', '-5'), Buffer.from('01234', 'utf8')], + [buf.slice('-10', '-0'), Buffer.from('', 'utf8')], + [buf.slice('111'), Buffer.from('', 'utf8')], + [buf.slice('0', '-111'), Buffer.from('', 'utf8')] +]; -assert.strictEqual(0, Buffer.compare(buf.slice(2), - Buffer.from('23456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(5), - Buffer.from('56789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(10), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(5, 8), - Buffer.from('567', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(8, -1), - Buffer.from('8', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-10), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0, -9), - Buffer.from('0', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0, -10), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0, -1), - Buffer.from('012345678', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(2, -2), - Buffer.from('234567', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(0, 65536), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(65536, 0), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-5, -8), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-5, -3), - Buffer.from('56', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice(-10, 10), - Buffer.from('0123456789', 'utf8'))); -for (let i = 0, s = buf; i < buf.length; ++i) { - assert.strictEqual(0, Buffer.compare(buf.slice(i), s.slice(i))); - assert.strictEqual(0, Buffer.compare(buf.slice(0, i), s.slice(0, i))); - assert.strictEqual(0, Buffer.compare(buf.slice(-i), s.slice(-i))); - assert.strictEqual(0, Buffer.compare(buf.slice(0, -i), s.slice(0, -i))); +for (let i = 0, s = buf.toString(); i < buf.length; ++i) { + expectedSameBufs.push( + [buf.slice(i), Buffer.from(s.slice(i))], + [buf.slice(0, i), Buffer.from(s.slice(0, i))], + [buf.slice(-i), Buffer.from(s.slice(-i))], + [buf.slice(0, -i), Buffer.from(s.slice(0, -i))] + ); } +expectedSameBufs.forEach(([buf1, buf2]) => { + assert.strictEqual(0, Buffer.compare(buf1, buf2)); +}); + const utf16Buf = Buffer.from('0123456789', 'utf16le'); assert.deepStrictEqual(utf16Buf.slice(0, 6), Buffer.from('012', 'utf16le')); - -assert.strictEqual(0, Buffer.compare(buf.slice('0', '1'), - Buffer.from('0', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('-5', '10'), - Buffer.from('56789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('-10', '10'), - Buffer.from('0123456789', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('-10', '-5'), - Buffer.from('01234', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('-10', '-0'), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('111'), - Buffer.from('', 'utf8'))); -assert.strictEqual(0, Buffer.compare(buf.slice('0', '-111'), - Buffer.from('', 'utf8'))); - // try to slice a zero length Buffer // see https://github.com/joyent/node/issues/5881 assert.doesNotThrow(() => Buffer.alloc(0).slice(0, 1)); diff --git a/test/parallel/test-regress-GH-1899.js b/test/parallel/test-child-process-can-write-to-stdout.js similarity index 76% rename from test/parallel/test-regress-GH-1899.js rename to test/parallel/test-child-process-can-write-to-stdout.js index 6bf6860ef77d5b..bb9ddb71c827bc 100644 --- a/test/parallel/test-regress-GH-1899.js +++ b/test/parallel/test-child-process-can-write-to-stdout.js @@ -1,4 +1,7 @@ 'use strict'; +// Tests that a spawned child process can write to stdout without throwing. +// See https://github.com/nodejs/node-v0.x-archive/issues/1899. + require('../common'); const fixtures = require('../common/fixtures'); const assert = require('assert'); diff --git a/test/parallel/test-child-process-exec-stdout-stderr-data-string.js b/test/parallel/test-child-process-exec-stdout-stderr-data-string.js index 472d31f16ac920..1fbdfbf8e45b4d 100644 --- a/test/parallel/test-child-process-exec-stdout-stderr-data-string.js +++ b/test/parallel/test-child-process-exec-stdout-stderr-data-string.js @@ -4,20 +4,10 @@ const common = require('../common'); const assert = require('assert'); const exec = require('child_process').exec; -let stdoutCalls = 0; -let stderrCalls = 0; - const command = common.isWindows ? 'dir' : 'ls'; -exec(command).stdout.on('data', (data) => { - stdoutCalls += 1; -}); -exec('fhqwhgads').stderr.on('data', (data) => { - assert.strictEqual(typeof data, 'string'); - stderrCalls += 1; -}); +exec(command).stdout.on('data', common.mustCallAtLeast()); -process.on('exit', () => { - assert(stdoutCalls > 0); - assert(stderrCalls > 0); -}); +exec('fhqwhgads').stderr.on('data', common.mustCallAtLeast((data) => { + assert.strictEqual(typeof data, 'string'); +})); diff --git a/test/parallel/test-child-process-fork-net2.js b/test/parallel/test-child-process-fork-net2.js index 41ba49c24ca06c..4729fe0573c51c 100644 --- a/test/parallel/test-child-process-fork-net2.js +++ b/test/parallel/test-child-process-fork-net2.js @@ -12,7 +12,7 @@ if (process.argv[2] === 'child') { process.on('message', function(m, socket) { if (!socket) return; - console.error('[%d] got socket', id, m); + console.error(`[${id}] got socket ${m}`); // will call .end('end') or .write('write'); socket[m](m); @@ -20,11 +20,11 @@ if (process.argv[2] === 'child') { socket.resume(); socket.on('data', function() { - console.error('[%d] socket.data', id, m); + console.error(`[${id}] socket.data ${m}`); }); socket.on('end', function() { - console.error('[%d] socket.end', id, m); + console.error(`[${id}] socket.end ${m}`); }); // store the unfinished socket @@ -33,27 +33,27 @@ if (process.argv[2] === 'child') { } socket.on('close', function(had_error) { - console.error('[%d] socket.close', id, had_error, m); + console.error(`[${id}] socket.close ${had_error} ${m}`); }); socket.on('finish', function() { - console.error('[%d] socket finished', id, m); + console.error(`[${id}] socket finished ${m}`); }); }); process.on('message', function(m) { if (m !== 'close') return; - console.error('[%d] got close message', id); + console.error(`[${id}] got close message`); needEnd.forEach(function(endMe, i) { - console.error('[%d] ending %d/%d', id, i, needEnd.length); + console.error(`[${id}] ending ${i}/${needEnd.length}`); endMe.end('end'); }); }); process.on('disconnect', function() { - console.error('[%d] process disconnect, ending', id); + console.error(`[${id}] process disconnect, ending`); needEnd.forEach(function(endMe, i) { - console.error('[%d] ending %d/%d', id, i, needEnd.length); + console.error(`[${id}] ending ${i}/${needEnd.length}`); endMe.end('end'); }); }); @@ -86,7 +86,7 @@ if (process.argv[2] === 'child') { connected += 1; socket.once('close', function() { - console.log('[m] socket closed, total %d', ++closed); + console.log(`[m] socket closed, total ${++closed}`); }); if (connected === count) { diff --git a/test/parallel/test-cli-syntax.js b/test/parallel/test-cli-syntax.js index c27090db62bf95..3dbcf4a334985b 100644 --- a/test/parallel/test-cli-syntax.js +++ b/test/parallel/test-cli-syntax.js @@ -1,9 +1,9 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); -const spawnSync = require('child_process').spawnSync; const fixtures = require('../common/fixtures'); +const exec = require('child_process').exec; const node = process.execPath; @@ -13,7 +13,9 @@ const syntaxArgs = [ ['--check'] ]; -const syntaxErrorRE = /^SyntaxError: Unexpected identifier$/m; +// Match on the name of the `Error` but not the message as it is different +// depending on the JavaScript engine. +const syntaxErrorRE = /^SyntaxError: \b/m; const notFoundRE = /^Error: Cannot find module/m; // test good syntax with and without shebang @@ -29,12 +31,13 @@ const notFoundRE = /^Error: Cannot find module/m; // loop each possible option, `-c` or `--check` syntaxArgs.forEach(function(args) { const _args = args.concat(file); - const c = spawnSync(node, _args, {encoding: 'utf8'}); - // no output should be produced - assert.strictEqual(c.stdout, '', 'stdout produced'); - assert.strictEqual(c.stderr, '', 'stderr produced'); - assert.strictEqual(c.status, 0, `code == ${c.status}`); + const cmd = [node, ..._args].join(' '); + exec(cmd, common.mustCall((err, stdout, stderr) => { + assert.ifError(err); + assert.strictEqual(stdout, '', 'stdout produced'); + assert.strictEqual(stderr, '', 'stderr produced'); + })); }); }); @@ -50,15 +53,20 @@ const notFoundRE = /^Error: Cannot find module/m; // loop each possible option, `-c` or `--check` syntaxArgs.forEach(function(args) { const _args = args.concat(file); - const c = spawnSync(node, _args, {encoding: 'utf8'}); + const cmd = [node, ..._args].join(' '); + exec(cmd, common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err instanceof Error, true); + assert.strictEqual(err.code, 1, `code === ${err.code}`); - // no stdout should be produced - assert.strictEqual(c.stdout, '', 'stdout produced'); + // no stdout should be produced + assert.strictEqual(stdout, '', 'stdout produced'); - // stderr should have a syntax error message - assert(syntaxErrorRE.test(c.stderr), 'stderr incorrect'); + // stderr should have a syntax error message + assert(syntaxErrorRE.test(stderr), 'stderr incorrect'); - assert.strictEqual(c.status, 1, `code == ${c.status}`); + // stderr should include the filename + assert(stderr.startsWith(file), "stderr doesn't start with the filename"); + })); }); }); @@ -72,14 +80,15 @@ const notFoundRE = /^Error: Cannot find module/m; // loop each possible option, `-c` or `--check` syntaxArgs.forEach(function(args) { const _args = args.concat(file); - const c = spawnSync(node, _args, {encoding: 'utf8'}); + const cmd = [node, ..._args].join(' '); + exec(cmd, common.mustCall((err, stdout, stderr) => { + // no stdout should be produced + assert.strictEqual(stdout, '', 'stdout produced'); - // no stdout should be produced - assert.strictEqual(c.stdout, '', 'stdout produced'); + // stderr should have a module not found error message + assert(notFoundRE.test(stderr), 'stderr incorrect'); - // stderr should have a module not found error message - assert(notFoundRE.test(c.stderr), 'stderr incorrect'); - - assert.strictEqual(c.status, 1, `code == ${c.status}`); + assert.strictEqual(err.code, 1, `code === ${err.code}`); + })); }); }); diff --git a/test/parallel/test-console-clear.js b/test/parallel/test-console-clear.js new file mode 100644 index 00000000000000..b6fc003165dc66 --- /dev/null +++ b/test/parallel/test-console-clear.js @@ -0,0 +1,22 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const stdoutWrite = process.stdout.write; + +// The sequence for moving the cursor to 0,0 and clearing screen down +const check = '\u001b[1;1H\u001b[0J'; + +function doTest(isTTY, check) { + let buf = ''; + process.stdout.isTTY = isTTY; + process.stdout.write = (string) => buf += string; + console.clear(); + process.stdout.write = stdoutWrite; + assert.strictEqual(buf, check); +} + +// Fake TTY +doTest(true, check); +doTest(false, ''); diff --git a/test/parallel/test-console-count.js b/test/parallel/test-console-count.js new file mode 100644 index 00000000000000..64e2641f0a641d --- /dev/null +++ b/test/parallel/test-console-count.js @@ -0,0 +1,63 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const stdoutWrite = process.stdout.write; + +let buf = ''; + +process.stdout.write = (string) => buf = string; + +console.count(); +assert.strictEqual(buf, 'default: 1\n'); + +// 'default' and undefined are equivalent +console.count('default'); +assert.strictEqual(buf, 'default: 2\n'); + +console.count('a'); +assert.strictEqual(buf, 'a: 1\n'); + +console.count('b'); +assert.strictEqual(buf, 'b: 1\n'); + +console.count('a'); +assert.strictEqual(buf, 'a: 2\n'); + +console.count(); +assert.strictEqual(buf, 'default: 3\n'); + +console.count({}); +assert.strictEqual(buf, '[object Object]: 1\n'); + +console.count(1); +assert.strictEqual(buf, '1: 1\n'); + +console.count(null); +assert.strictEqual(buf, 'null: 1\n'); + +console.count('null'); +assert.strictEqual(buf, 'null: 2\n'); + +console.countReset(); +console.count(); +assert.strictEqual(buf, 'default: 1\n'); + +console.countReset('a'); +console.count('a'); +assert.strictEqual(buf, 'a: 1\n'); + +// countReset('a') only reset the a counter +console.count(); +assert.strictEqual(buf, 'default: 2\n'); + +process.stdout.write = stdoutWrite; + +// Symbol labels do not work +assert.throws( + () => console.count(Symbol('test')), + /^TypeError: Cannot convert a Symbol value to a string$/); +assert.throws( + () => console.countReset(Symbol('test')), + /^TypeError: Cannot convert a Symbol value to a string$/); diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index 6d3b3d8c624c12..b6a519186844de 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -314,6 +314,14 @@ const errMessages = { const ciphers = crypto.getCiphers(); +common.expectWarning('Warning', (common.hasFipsCrypto ? [] : [ + 'Use Cipheriv for counter mode of aes-192-gcm' +]).concat( + [0, 1, 2, 6, 9, 10, 11, 17] + .map((i) => `Permitting authentication tag lengths of ${i} bytes is ` + + 'deprecated. Valid GCM tag lengths are 4, 8, 12, 13, 14, 15, 16.') +)); + for (const i in TEST_CASES) { const test = TEST_CASES[i]; @@ -362,7 +370,7 @@ for (const i in TEST_CASES) { assert.strictEqual(msg, test.plain); } else { // assert that final throws if input data could not be verified! - assert.throws(function() { decrypt.final('ascii'); }, errMessages.auth); + assert.throws(function() { decrypt.final('hex'); }, errMessages.auth); } } @@ -455,3 +463,14 @@ for (const i in TEST_CASES) { assert.throws(() => encrypt.setAAD(Buffer.from('123', 'ascii')), errMessages.state); } + +// GCM only supports specific authentication tag lengths, invalid lengths should +// produce warnings. +{ + for (const length of [0, 1, 2, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) { + const decrypt = crypto.createDecipheriv('aes-256-gcm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6Szih'); + decrypt.setAuthTag(Buffer.from('1'.repeat(length))); + } +} diff --git a/test/parallel/test-crypto-cipher-decipher.js b/test/parallel/test-crypto-cipher-decipher.js index ad7fc54ea90003..14a1601981f353 100644 --- a/test/parallel/test-crypto-cipher-decipher.js +++ b/test/parallel/test-crypto-cipher-decipher.js @@ -141,7 +141,7 @@ testCipher2(Buffer.from('0123456789abcdef')); // setAutoPadding/setAuthTag/setAAD should return `this` { const key = '0123456789'; - const tagbuf = Buffer.from('tagbuf'); + const tagbuf = Buffer.from('auth_tag'); const aadbuf = Buffer.from('aadbuf'); const decipher = crypto.createDecipher('aes-256-gcm', key); assert.strictEqual(decipher.setAutoPadding(), decipher); diff --git a/test/parallel/test-crypto-classes.js b/test/parallel/test-crypto-classes.js new file mode 100644 index 00000000000000..9f8ab65a02fcd0 --- /dev/null +++ b/test/parallel/test-crypto-classes.js @@ -0,0 +1,43 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); + return; +} +const crypto = require('crypto'); + +// 'ClassName' : ['args', 'for', 'constructor'] +const TEST_CASES = { + 'Hash': ['sha1'], + 'Hmac': ['sha1', 'Node'], + 'Cipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Decipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Sign': ['RSA-SHA1'], + 'Verify': ['RSA-SHA1'], + 'DiffieHellman': [1024], + 'DiffieHellmanGroup': ['modp5'], + 'ECDH': ['prime256v1'], + 'Credentials': [] +}; + +if (!common.hasFipsCrypto) { + TEST_CASES.Cipher = ['aes192', 'secret']; + TEST_CASES.Decipher = ['aes192', 'secret']; + TEST_CASES.DiffieHellman = [256]; +} + +function entries(obj) { + const ownProps = Object.keys(obj); + let i = ownProps.length; + const resArray = new Array(i); // preallocate the Array + while (i--) + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + + return resArray; +} + +for (const [clazz, args] of entries(TEST_CASES)) { + assert(crypto[`create${clazz}`](...args) instanceof crypto[clazz]); +} diff --git a/test/parallel/test-crypto-random.js b/test/parallel/test-crypto-random.js index a664023e81c06b..386602e4a13f2d 100644 --- a/test/parallel/test-crypto-random.js +++ b/test/parallel/test-crypto-random.js @@ -28,6 +28,208 @@ const expectedErrorRegexp = /^TypeError: size must be a number >= 0$/; }); }); +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + const after = crypto.randomFillSync(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustCall((err, buf) => { + assert.ifError(err); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustCall((err, buf) => { + assert.ifError(err); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + const bufs = [ + Buffer.alloc(10), + new Uint8Array(new Array(10).fill(0)) + ]; + + for (const buf of bufs) { + const len = Buffer.byteLength(buf); + assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`); + + assert.throws(() => { + crypto.randomFillSync(buf, 'test'); + }, /offset must be a number/); + + assert.throws(() => { + crypto.randomFillSync(buf, NaN); + }, /offset must be a number/); + + assert.throws(() => { + crypto.randomFill(buf, 'test', function() {}); + }, /offset must be a number/); + + assert.throws(() => { + crypto.randomFill(buf, NaN, function() {}); + }, /offset must be a number/); + + const max = require('buffer').kMaxLength + 1; + + assert.throws(() => { + crypto.randomFillSync(buf, 11); + }, /offset out of range/); + + assert.throws(() => { + crypto.randomFillSync(buf, max); + }, /offset out of range/); + + assert.throws(() => { + crypto.randomFill(buf, 11, function() {}); + }, /offset out of range/); + + assert.throws(() => { + crypto.randomFill(buf, max, function() {}); + }, /offset out of range/); + + assert.throws(() => { + crypto.randomFillSync(buf, 0, 'test'); + }, /size must be a number/); + + assert.throws(() => { + crypto.randomFillSync(buf, 0, NaN); + }, /size must be a number/); + + assert.throws(() => { + crypto.randomFill(buf, 0, 'test', function() {}); + }, /size must be a number/); + + assert.throws(() => { + crypto.randomFill(buf, 0, NaN, function() {}); + }, /size must be a number/); + + { + const size = (-1 >>> 0) + 1; + + assert.throws(() => { + crypto.randomFillSync(buf, 0, -10); + }, /size must be a uint32/); + + assert.throws(() => { + crypto.randomFillSync(buf, 0, size); + }, /size must be a uint32/); + + assert.throws(() => { + crypto.randomFill(buf, 0, -10, function() {}); + }, /size must be a uint32/); + + assert.throws(() => { + crypto.randomFill(buf, 0, size, function() {}); + }, /size must be a uint32/); + } + + assert.throws(() => { + crypto.randomFillSync(buf, -10); + }, /offset must be a uint32/); + + assert.throws(() => { + crypto.randomFill(buf, -10, function() {}); + }, /offset must be a uint32/); + + assert.throws(() => { + crypto.randomFillSync(buf, 1, 10); + }, /buffer too small/); + + assert.throws(() => { + crypto.randomFill(buf, 1, 10, function() {}); + }, /buffer too small/); + + assert.throws(() => { + crypto.randomFillSync(buf, 0, 12); + }, /buffer too small/); + + assert.throws(() => { + crypto.randomFill(buf, 0, 12, function() {}); + }, /buffer too small/); + + { + // Offset is too big + const offset = (-1 >>> 0) + 1; + assert.throws(() => { + crypto.randomFillSync(buf, offset, 10); + }, /offset must be a uint32/); + + assert.throws(() => { + crypto.randomFill(buf, offset, 10, function() {}); + }, /offset must be a uint32/); + } + } +} + // #5126, "FATAL ERROR: v8::Object::SetIndexedPropertiesToExternalArrayData() // length exceeds max acceptable value" assert.throws(function() { diff --git a/test/parallel/test-dgram-multicast-set-interface-lo.js b/test/parallel/test-dgram-multicast-set-interface-lo.js new file mode 100644 index 00000000000000..740b175d69be8a --- /dev/null +++ b/test/parallel/test-dgram-multicast-set-interface-lo.js @@ -0,0 +1,293 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const util = require('util'); + +if (common.inFreeBSDJail) { + common.skip('in a FreeBSD jail'); + return; +} + +// All SunOS systems must be able to pass this manual test before the +// following barrier can be removed: +// $ socat UDP-RECVFROM:12356,ip-add-membership=224.0.0.115:127.0.0.1,fork \ +// EXEC:hostname & +// $ echo hi |socat STDIO \ +// UDP4-DATAGRAM:224.0.0.115:12356,ip-multicast-if=127.0.0.1 + +if (common.isSunOS) { + common.skip('SunOs is not correctly delivering to loopback multicast.'); + return; +} + +const networkInterfaces = require('os').networkInterfaces(); +const Buffer = require('buffer').Buffer; +const fork = require('child_process').fork; +const MULTICASTS = { + IPv4: ['224.0.0.115', '224.0.0.116', '224.0.0.117'], + IPv6: ['ff02::1:115', 'ff02::1:116', 'ff02::1:117'] +}; +const LOOPBACK = { IPv4: '127.0.0.1', IPv6: '::1' }; +const ANY = { IPv4: '0.0.0.0', IPv6: '::' }; +const FAM = 'IPv4'; + +// Windows wont bind on multicasts so its filtering is by port. +const PORTS = {}; +for (let i = 0; i < MULTICASTS[FAM].length; i++) { + PORTS[MULTICASTS[FAM][i]] = common.PORT + (common.isWindows ? i : 0); +} + +const UDP = { IPv4: 'udp4', IPv6: 'udp6' }; + +const TIMEOUT = common.platformTimeout(5000); +const NOW = Date.now(); +const TMPL = (tail) => `${NOW} - ${tail}`; + +// Take the first non-internal interface as the other interface to isolate +// from loopback. Ideally, this should check for whether or not this interface +// and the loopback have the MULTICAST flag. +const interfaceAddress = ((networkInterfaces) => { + for (const name in networkInterfaces) { + for (const localInterface of networkInterfaces[name]) { + if (!localInterface.internal && localInterface.family === FAM) { + let interfaceAddress = localInterface.address; + // On Windows, IPv6 would need: `%${localInterface.scopeid}` + if (FAM === 'IPv6') + interfaceAddress += `${interfaceAddress}%${name}`; + return interfaceAddress; + } + } + } +})(networkInterfaces); + +assert.ok(interfaceAddress); + +const messages = [ + { tail: 'First message to send', mcast: MULTICASTS[FAM][0], rcv: true }, + { tail: 'Second message to send', mcast: MULTICASTS[FAM][0], rcv: true }, + { tail: 'Third message to send', mcast: MULTICASTS[FAM][1], rcv: true, + newAddr: interfaceAddress }, + { tail: 'Fourth message to send', mcast: MULTICASTS[FAM][2] }, + { tail: 'Fifth message to send', mcast: MULTICASTS[FAM][1], rcv: true }, + { tail: 'Sixth message to send', mcast: MULTICASTS[FAM][2], rcv: true, + newAddr: LOOPBACK[FAM] } +]; + + +if (process.argv[2] !== 'child') { + const IFACES = [ANY[FAM], interfaceAddress, LOOPBACK[FAM]]; + const workers = {}; + const listeners = MULTICASTS[FAM].length * 2; + let listening = 0; + let dead = 0; + let i = 0; + let done = 0; + let timer = null; + // Exit the test if it doesn't succeed within the TIMEOUT. + timer = setTimeout(function() { + console.error('[PARENT] Responses were not received within %d ms.', + TIMEOUT); + console.error('[PARENT] Skip'); + + killChildren(workers); + common.skip('Check filter policy'); + + process.exit(1); + }, TIMEOUT); + + // Launch the child processes. + for (let i = 0; i < listeners; i++) { + const IFACE = IFACES[i % IFACES.length]; + const MULTICAST = MULTICASTS[FAM][i % MULTICASTS[FAM].length]; + + const messagesNeeded = messages.filter((m) => m.rcv && + m.mcast === MULTICAST) + .map((m) => TMPL(m.tail)); + const worker = fork(process.argv[1], + ['child', + IFACE, + MULTICAST, + messagesNeeded.length, + NOW]); + workers[worker.pid] = worker; + + worker.messagesReceived = []; + worker.messagesNeeded = messagesNeeded; + + // Handle the death of workers. + worker.on('exit', function(code, signal) { + // Don't consider this a true death if the worker has finished + // successfully or if the exit code is 0. + if (worker.isDone || code === 0) { + return; + } + + dead += 1; + console.error('[PARENT] Worker %d died. %d dead of %d', + worker.pid, + dead, + listeners); + + if (dead === listeners) { + console.error('[PARENT] All workers have died.'); + console.error('[PARENT] Fail'); + + killChildren(workers); + + process.exit(1); + } + }); + + worker.on('message', function(msg) { + if (msg.listening) { + listening += 1; + + if (listening === listeners) { + // All child process are listening, so start sending. + sendSocket.sendNext(); + } + } else if (msg.message) { + worker.messagesReceived.push(msg.message); + + if (worker.messagesReceived.length === worker.messagesNeeded.length) { + done += 1; + worker.isDone = true; + console.error('[PARENT] %d received %d messages total.', + worker.pid, + worker.messagesReceived.length); + } + + if (done === listeners) { + console.error('[PARENT] All workers have received the ' + + 'required number of ' + + 'messages. Will now compare.'); + + Object.keys(workers).forEach(function(pid) { + const worker = workers[pid]; + + let count = 0; + + worker.messagesReceived.forEach(function(buf) { + for (let i = 0; i < worker.messagesNeeded.length; ++i) { + if (buf.toString() === worker.messagesNeeded[i]) { + count++; + break; + } + } + }); + + console.error('[PARENT] %d received %d matching messages.', + worker.pid, + count); + + assert.strictEqual(count, worker.messagesNeeded.length, + 'A worker received ' + + 'an invalid multicast message'); + }); + + clearTimeout(timer); + console.error('[PARENT] Success'); + killChildren(workers); + } + } + }); + } + + const sendSocket = dgram.createSocket({ + type: UDP[FAM], + reuseAddr: true + }); + + // Don't bind the address explicitly when sending and start with + // the OSes default multicast interface selection. + sendSocket.bind(common.PORT, ANY[FAM]); + sendSocket.on('listening', function() { + console.error(`outgoing iface ${interfaceAddress}`); + }); + + sendSocket.on('close', function() { + console.error('[PARENT] sendSocket closed'); + }); + + sendSocket.sendNext = function() { + const msg = messages[i++]; + + if (!msg) { + sendSocket.close(); + return; + } + console.error(TMPL(NOW, msg.tail)); + const buf = Buffer.from(TMPL(msg.tail)); + if (msg.newAddr) { + console.error(`changing outgoing multicast ${msg.newAddr}`); + sendSocket.setMulticastInterface(msg.newAddr); + } + sendSocket.send( + buf, + 0, + buf.length, + PORTS[msg.mcast], + msg.mcast, + function(err) { + assert.ifError(err); + console.error('[PARENT] sent %s to %s:%s', + util.inspect(buf.toString()), + msg.mcast, PORTS[msg.mcast]); + + process.nextTick(sendSocket.sendNext); + } + ); + }; + + function killChildren(children) { + for (const i in children) + children[i].kill(); + } +} + +if (process.argv[2] === 'child') { + const IFACE = process.argv[3]; + const MULTICAST = process.argv[4]; + const NEEDEDMSGS = Number(process.argv[5]); + const SESSION = Number(process.argv[6]); + const receivedMessages = []; + + console.error(`pid ${process.pid} iface ${IFACE} MULTICAST ${MULTICAST}`); + const listenSocket = dgram.createSocket({ + type: UDP[FAM], + reuseAddr: true + }); + + listenSocket.on('message', function(buf, rinfo) { + // Examine udp messages only when they were sent by the parent. + if (!buf.toString().startsWith(SESSION)) return; + + console.error('[CHILD] %s received %s from %j', + process.pid, + util.inspect(buf.toString()), + rinfo); + + receivedMessages.push(buf); + + let closecb; + + if (receivedMessages.length === NEEDEDMSGS) { + listenSocket.close(); + closecb = () => process.exit(); + } + + process.send({ message: buf.toString() }, closecb); + }); + + + listenSocket.on('listening', function() { + listenSocket.addMembership(MULTICAST, IFACE); + process.send({ listening: true }); + }); + + if (common.isWindows) + listenSocket.bind(PORTS[MULTICAST], ANY[FAM]); + else + listenSocket.bind(common.PORT, MULTICAST); +} diff --git a/test/parallel/test-dgram-multicast-set-interface.js b/test/parallel/test-dgram-multicast-set-interface.js new file mode 100644 index 00000000000000..c065683f53c7fd --- /dev/null +++ b/test/parallel/test-dgram-multicast-set-interface.js @@ -0,0 +1,120 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Explicitly request default system selection + socket.setMulticastInterface('0.0.0.0'); + + socket.close(); + })); +} + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + socket.close(common.mustCall(() => { + assert.throws(() => { socket.setMulticastInterface('0.0.0.0'); }, + /Not running/); + })); + })); +} + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Try to set with an invalid interfaceAddress (wrong address class) + try { + socket.setMulticastInterface('::'); + throw new Error('Not detected.'); + } catch (e) { + console.error(`setMulticastInterface: wrong family error is: ${e}`); + } + + socket.close(); + })); +} + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Try to set with an invalid interfaceAddress (wrong Type) + assert.throws(() => { + socket.setMulticastInterface(1); + }, /TypeError/); + + socket.close(); + })); +} + +{ + const socket = dgram.createSocket('udp4'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Try to set with an invalid interfaceAddress (non-unicast) + assert.throws(() => { + socket.setMulticastInterface('224.0.0.2'); + }, /Error/); + + socket.close(); + })); +} + +// If IPv6 is not supported, skip the rest of the test. However, don't call +// common.skip(), which calls process.exit() while there is outstanding +// common.mustCall() activity. +if (!common.hasIPv6) + return; + +{ + const socket = dgram.createSocket('udp6'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Try to set with an invalid interfaceAddress ('undefined') + assert.throws(() => { + socket.setMulticastInterface(String(undefined)); + }, /EINVAL/); + + socket.close(); + })); +} + +{ + const socket = dgram.createSocket('udp6'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Try to set with an invalid interfaceAddress ('') + assert.throws(() => { + socket.setMulticastInterface(''); + }, /EINVAL/); + + socket.close(); + })); +} + +{ + const socket = dgram.createSocket('udp6'); + + socket.bind(0); + socket.on('listening', common.mustCall(() => { + // Using lo0 for OsX, on all other OSes, an invalid Scope gets + // turned into #0 (default selection) which is also acceptable. + socket.setMulticastInterface('::%lo0'); + + socket.close(); + })); +} diff --git a/test/parallel/test-domain-crypto.js b/test/parallel/test-domain-crypto.js index f0995a75c05fdd..05fd2c522d807f 100644 --- a/test/parallel/test-domain-crypto.js +++ b/test/parallel/test-domain-crypto.js @@ -15,6 +15,8 @@ global.domain = require('domain'); // should not throw a 'TypeError: undefined is not a function' exception crypto.randomBytes(8); crypto.randomBytes(8, common.mustCall()); +const buf = Buffer.alloc(8); +crypto.randomFillSync(buf); crypto.pseudoRandomBytes(8); crypto.pseudoRandomBytes(8, common.mustCall()); crypto.pbkdf2('password', 'salt', 8, 8, 'sha1', common.mustCall()); diff --git a/test/parallel/test-domain-no-error-handler-abort-on-uncaught-9.js b/test/parallel/test-domain-no-error-handler-abort-on-uncaught-9.js index 2e86a2125e86ee..ae30a1dea68b0b 100644 --- a/test/parallel/test-domain-no-error-handler-abort-on-uncaught-9.js +++ b/test/parallel/test-domain-no-error-handler-abort-on-uncaught-9.js @@ -10,8 +10,8 @@ function test() { d.on('error', function errorHandler() { }); - d.run(function() { - d2.run(function() { + d.run(() => { + d2.run(() => { const fs = require('fs'); fs.exists('/non/existing/file', function onExists() { throw new Error('boom!'); diff --git a/test/parallel/test-eslint-lowercase-name-for-primitive.js b/test/parallel/test-eslint-lowercase-name-for-primitive.js new file mode 100644 index 00000000000000..3eb0d838346865 --- /dev/null +++ b/test/parallel/test-eslint-lowercase-name-for-primitive.js @@ -0,0 +1,49 @@ +'use strict'; + +require('../common'); + +const RuleTester = require('../../tools/eslint').RuleTester; +const rule = require('../../tools/eslint-rules/lowercase-name-for-primitive'); + +const valid = [ + 'string', + 'number', + 'boolean', + 'null', + 'undefined' +]; + +new RuleTester().run('lowercase-name-for-primitive', rule, { + valid: [ + 'new errors.TypeError("ERR_INVALID_ARG_TYPE", "a", ["string", "number"])', + ...valid.map((name) => + `new errors.TypeError("ERR_INVALID_ARG_TYPE", "name", "${name}")` + ) + ], + invalid: [ + { + code: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '\'Number\')', + errors: [{ message: 'primitive should use lowercase: Number' }], + output: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '\'number\')' + }, + { + code: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '\'STRING\')', + errors: [{ message: 'primitive should use lowercase: STRING' }], + output: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '\'string\')' + }, + { + code: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '[\'String\', \'Number\']) ', + errors: [ + { message: 'primitive should use lowercase: String' }, + { message: 'primitive should use lowercase: Number' } + ], + output: 'new errors.TypeError(\'ERR_INVALID_ARG_TYPE\', \'a\', ' + + '[\'string\', \'number\']) ' + } + ] +}); diff --git a/test/parallel/test-fs-readfile-empty.js b/test/parallel/test-fs-readfile-empty.js index 632bab5ec8b4fe..ac8d4755d3ff9f 100644 --- a/test/parallel/test-fs-readfile-empty.js +++ b/test/parallel/test-fs-readfile-empty.js @@ -1,5 +1,8 @@ 'use strict'; require('../common'); + +// Trivial test of fs.readFile on an empty file. + const assert = require('assert'); const fs = require('fs'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-fs-readfile-error.js b/test/parallel/test-fs-readfile-error.js index 7c8dd8feb6a770..752339d909ad45 100644 --- a/test/parallel/test-fs-readfile-error.js +++ b/test/parallel/test-fs-readfile-error.js @@ -1,5 +1,8 @@ 'use strict'; const common = require('../common'); + +// Test that fs.readFile fails correctly on a non-existent file. + // `fs.readFile('/')` does not fail on FreeBSD, because you can open and read // the directory there. if (common.isFreeBSD) diff --git a/test/parallel/test-fs-readfile-fd.js b/test/parallel/test-fs-readfile-fd.js index decd578d704bf6..c0e19a63d27ddf 100644 --- a/test/parallel/test-fs-readfile-fd.js +++ b/test/parallel/test-fs-readfile-fd.js @@ -1,5 +1,8 @@ 'use strict'; require('../common'); + +// Test fs.readFile using a file descriptor. + const fixtures = require('../common/fixtures'); const assert = require('assert'); const fs = require('fs'); diff --git a/test/parallel/test-fs-readfile-unlink.js b/test/parallel/test-fs-readfile-unlink.js index 203ea681e5a079..c0e51b9866d4fe 100644 --- a/test/parallel/test-fs-readfile-unlink.js +++ b/test/parallel/test-fs-readfile-unlink.js @@ -1,5 +1,8 @@ 'use strict'; const common = require('../common'); + +// Test that unlink succeeds immediately after readFile completes. + const assert = require('assert'); const fs = require('fs'); const path = require('path'); diff --git a/test/parallel/test-fs-readfile-zero-byte-liar.js b/test/parallel/test-fs-readfile-zero-byte-liar.js index 82037a77e62122..182462d647a0dc 100644 --- a/test/parallel/test-fs-readfile-zero-byte-liar.js +++ b/test/parallel/test-fs-readfile-zero-byte-liar.js @@ -1,5 +1,8 @@ 'use strict'; const common = require('../common'); + +// Test that readFile works even when stat returns size 0. + const assert = require('assert'); const fs = require('fs'); diff --git a/test/parallel/test-http-connect.js b/test/parallel/test-http-connect.js index 8fd17bf136fdb9..854d2d893416a0 100644 --- a/test/parallel/test-http-connect.js +++ b/test/parallel/test-http-connect.js @@ -21,9 +21,9 @@ server.on('connect', common.mustCall((req, socket, firstBodyChunk) => { })); })); -server.listen(0, common.mustCall(function() { +server.listen(0, common.mustCall(() => { const req = http.request({ - port: this.address().port, + port: server.address().port, method: 'CONNECT', path: 'google.com:443' }, common.mustNotCall()); diff --git a/test/parallel/test-http-content-length.js b/test/parallel/test-http-content-length.js index 3d6882d92f04b3..e6ba3719f95ba6 100644 --- a/test/parallel/test-http-content-length.js +++ b/test/parallel/test-http-content-length.js @@ -2,6 +2,7 @@ require('../common'); const assert = require('assert'); const http = require('http'); +const Countdown = require('../common/countdown'); const expectedHeadersMultipleWrites = { 'connection': 'close', @@ -18,8 +19,8 @@ const expectedHeadersEndNoData = { 'content-length': '0', }; -let receivedRequests = 0; -const totalRequests = 3; + +const countdown = new Countdown(3, () => server.close()); const server = http.createServer(function(req, res) { res.removeHeader('Date'); @@ -42,8 +43,7 @@ const server = http.createServer(function(req, res) { throw new Error('Unreachable'); } - receivedRequests++; - if (totalRequests === receivedRequests) server.close(); + countdown.dec(); }); server.listen(0, function() { diff --git a/test/parallel/test-http-date-header.js b/test/parallel/test-http-date-header.js index e14a92b310f2d1..53ab1de51af0ca 100644 --- a/test/parallel/test-http-date-header.js +++ b/test/parallel/test-http-date-header.js @@ -5,7 +5,7 @@ const http = require('http'); const testResBody = 'other stuff!\n'; -const server = http.createServer(function(req, res) { +const server = http.createServer((req, res) => { assert.ok(!('date' in req.headers), 'Request headers contained a Date.'); res.writeHead(200, { @@ -16,16 +16,16 @@ const server = http.createServer(function(req, res) { server.listen(0); -server.addListener('listening', function() { +server.addListener('listening', () => { const options = { - port: this.address().port, + port: server.address().port, path: '/', method: 'GET' }; - const req = http.request(options, function(res) { + const req = http.request(options, (res) => { assert.ok('date' in res.headers, 'Response headers didn\'t contain a Date.'); - res.addListener('end', function() { + res.addListener('end', () => { server.close(); process.exit(); }); diff --git a/test/parallel/test-http-default-encoding.js b/test/parallel/test-http-default-encoding.js index 2c41328749f027..5b88d4de3445d8 100644 --- a/test/parallel/test-http-default-encoding.js +++ b/test/parallel/test-http-default-encoding.js @@ -6,11 +6,11 @@ const http = require('http'); const expected = 'This is a unicode text: سلام'; let result = ''; -const server = http.Server(function(req, res) { +const server = http.Server((req, res) => { req.setEncoding('utf8'); - req.on('data', function(chunk) { + req.on('data', (chunk) => { result += chunk; - }).on('end', function() { + }).on('end', () => { server.close(); res.writeHead(200); res.end('hello world\n'); @@ -23,15 +23,15 @@ server.listen(0, function() { port: this.address().port, path: '/', method: 'POST' - }, function(res) { + }, (res) => { console.log(res.statusCode); res.resume(); - }).on('error', function(e) { + }).on('error', (e) => { console.log(e.message); process.exit(1); }).end(expected); }); -process.on('exit', function() { +process.on('exit', () => { assert.strictEqual(expected, result); }); diff --git a/test/parallel/test-http-keepalive-override.js b/test/parallel/test-http-keepalive-override.js new file mode 100644 index 00000000000000..d25fc319747ad2 --- /dev/null +++ b/test/parallel/test-http-keepalive-override.js @@ -0,0 +1,67 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const http = require('http'); + +const server = http.createServer((req, res) => { + res.end('ok'); +}).listen(0, common.mustCall(() => { + const agent = http.Agent({ + keepAlive: true, + maxSockets: 5, + maxFreeSockets: 2 + }); + + const keepSocketAlive = agent.keepSocketAlive; + const reuseSocket = agent.reuseSocket; + + let called = 0; + let expectedSocket; + agent.keepSocketAlive = common.mustCall((socket) => { + assert(socket); + + called++; + if (called === 1) { + return false; + } else if (called === 2) { + expectedSocket = socket; + return keepSocketAlive.call(agent, socket); + } + + assert.strictEqual(socket, expectedSocket); + return false; + }, 3); + + agent.reuseSocket = common.mustCall((socket, req) => { + assert.strictEqual(socket, expectedSocket); + assert(req); + + return reuseSocket.call(agent, socket, req); + }, 1); + + function req(callback) { + http.request({ + method: 'GET', + path: '/', + agent, + port: server.address().port + }, common.mustCall((res) => { + res.resume(); + res.once('end', common.mustCall(() => { + setImmediate(callback); + })); + })).end(); + } + + // Should destroy socket instead of keeping it alive + req(common.mustCall(() => { + // Should keep socket alive + req(common.mustCall(() => { + // Should reuse the socket + req(common.mustCall(() => { + server.close(); + })); + })); + })); +})); diff --git a/test/parallel/test-http-pipeline-flood.js b/test/parallel/test-http-pipeline-flood.js index d8ef0241542ee6..94a67d827e7041 100644 --- a/test/parallel/test-http-pipeline-flood.js +++ b/test/parallel/test-http-pipeline-flood.js @@ -56,9 +56,9 @@ function parent() { server.close(); })); - server.setTimeout(200, common.mustCall(function() { + server.setTimeout(200, common.mustCallAtLeast(function() { child.kill(); - })); + }, 1)); }); } diff --git a/test/parallel/test-http-pipeline-regr-3332.js b/test/parallel/test-http-pipeline-regr-3332.js index b34ad499850b70..f65df591891b70 100644 --- a/test/parallel/test-http-pipeline-regr-3332.js +++ b/test/parallel/test-http-pipeline-regr-3332.js @@ -1,40 +1,27 @@ 'use strict'; require('../common'); -const assert = require('assert'); const http = require('http'); const net = require('net'); +const Countdown = require('../common/countdown'); const big = Buffer.alloc(16 * 1024, 'A'); const COUNT = 1e4; -let received = 0; +const countdown = new Countdown(COUNT, () => { + server.close(); + client.end(); +}); let client; const server = http.createServer(function(req, res) { res.end(big, function() { - if (++received === COUNT) { - server.close(); - client.end(); - } + countdown.dec(); }); }).listen(0, function() { const req = new Array(COUNT + 1).join('GET / HTTP/1.1\r\n\r\n'); client = net.connect(this.address().port, function() { client.write(req); }); - - // Just let the test terminate instead of hanging - client.on('close', function() { - if (received !== COUNT) - server.close(); - }); client.resume(); }); - -process.on('exit', function() { - // The server should pause connection on pipeline flood, but it shoul still - // resume it and finish processing the requests, when its output queue will - // be empty again. - assert.strictEqual(received, COUNT); -}); diff --git a/test/parallel/test-icu-punycode.js b/test/parallel/test-icu-punycode.js index cf83ac66206806..7abcae461774c0 100644 --- a/test/parallel/test-icu-punycode.js +++ b/test/parallel/test-icu-punycode.js @@ -1,70 +1,46 @@ 'use strict'; - const common = require('../common'); -const icu = getPunycode(); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const icu = process.binding('icu'); const assert = require('assert'); -function getPunycode() { - try { - return process.binding('icu'); - } catch (err) { - return undefined; +const tests = require('../fixtures/url-idna.js'); +const wptToASCIITests = require('../fixtures/url-toascii.js'); + +{ + for (const [i, { ascii, unicode }] of tests.entries()) { + assert.strictEqual(ascii, icu.toASCII(unicode), `toASCII(${i + 1})`); + assert.strictEqual(unicode, icu.toUnicode(ascii), `toUnicode(${i + 1})`); + assert.strictEqual(ascii, icu.toASCII(icu.toUnicode(ascii)), + `toASCII(toUnicode(${i + 1}))`); + assert.strictEqual(unicode, icu.toUnicode(icu.toASCII(unicode)), + `toUnicode(toASCII(${i + 1}))`); } } -if (!icu) - common.skip('icu punycode tests because ICU is not present.'); +{ + const errMessage = /^Error: Cannot convert name to ASCII$/; -// Credit for list: http://www.i18nguy.com/markup/idna-examples.html -const tests = [ - 'افغانستا.icom.museum', - 'الجزائر.icom.museum', - 'österreich.icom.museum', - 'বাংলাদেশ.icom.museum', - 'беларусь.icom.museum', - 'belgië.icom.museum', - 'българия.icom.museum', - 'تشادر.icom.museum', - '中国.icom.museum', - 'القمر.icom.museum', - 'κυπρος.icom.museum', - 'českárepublika.icom.museum', - 'مصر.icom.museum', - 'ελλάδα.icom.museum', - 'magyarország.icom.museum', - 'ísland.icom.museum', - 'भारत.icom.museum', - 'ايران.icom.museum', - 'éire.icom.museum', - 'איקו״ם.ישראל.museum', - '日本.icom.museum', - 'الأردن.icom.museum', - 'қазақстан.icom.museum', - '한국.icom.museum', - 'кыргызстан.icom.museum', - 'ລາວ.icom.museum', - 'لبنان.icom.museum', - 'македонија.icom.museum', - 'موريتانيا.icom.museum', - 'méxico.icom.museum', - 'монголулс.icom.museum', - 'المغرب.icom.museum', - 'नेपाल.icom.museum', - 'عمان.icom.museum', - 'قطر.icom.museum', - 'românia.icom.museum', - 'россия.иком.museum', - 'србијаицрнагора.иком.museum', - 'இலங்கை.icom.museum', - 'españa.icom.museum', - 'ไทย.icom.museum', - 'تونس.icom.museum', - 'türkiye.icom.museum', - 'украина.icom.museum', - 'việtnam.icom.museum' -]; - -// Testing the roundtrip -tests.forEach((i) => { - assert.strictEqual(i, icu.toUnicode(icu.toASCII(i))); -}); + for (const [i, test] of wptToASCIITests.entries()) { + if (typeof test === 'string') + continue; // skip comments + const { comment, input, output } = test; + let caseComment = `case ${i + 1}`; + if (comment) + caseComment += ` (${comment})`; + if (output === null) { + assert.throws(() => icu.toASCII(input), + errMessage, `ToASCII ${caseComment}`); + assert.doesNotThrow(() => icu.toASCII(input, true), + `ToASCII ${caseComment} in lenient mode`); + } else { + assert.strictEqual(icu.toASCII(input), output, `ToASCII ${caseComment}`); + assert.strictEqual(icu.toASCII(input, true), output, + `ToASCII ${caseComment} in lenient mode`); + } + assert.doesNotThrow(() => icu.toUnicode(input), `ToUnicode ${caseComment}`); + } +} diff --git a/test/parallel/test-intl-v8BreakIterator.js b/test/parallel/test-intl-v8BreakIterator.js index 03e0712ba3ece2..7ccdb70f141a8b 100644 --- a/test/parallel/test-intl-v8BreakIterator.js +++ b/test/parallel/test-intl-v8BreakIterator.js @@ -2,8 +2,9 @@ const common = require('../common'); const assert = require('assert'); -if (global.Intl === undefined || Intl.v8BreakIterator === undefined) +if (!common.hasIntl || Intl.v8BreakIterator === undefined) { common.skip('no Intl'); +} try { new Intl.v8BreakIterator(); diff --git a/test/parallel/test-intl.js b/test/parallel/test-intl.js index a5b76538a2e722..907f56f03ac83f 100644 --- a/test/parallel/test-intl.js +++ b/test/parallel/test-intl.js @@ -8,9 +8,6 @@ if (enablei18n === undefined) { enablei18n = 0; } -// is the Intl object present? -const haveIntl = (global.Intl !== undefined); - // Returns true if no specific locale ids were configured (i.e. "all") // Else, returns true if loc is in the configured list // Else, returns false @@ -19,7 +16,7 @@ function haveLocale(loc) { return locs.includes(loc); } -if (!haveIntl) { +if (!common.hasIntl) { const erMsg = `"Intl" object is NOT present but v8_enable_i18n_support is ${enablei18n}`; assert.strictEqual(enablei18n, 0, erMsg); diff --git a/test/parallel/test-listen-fd-ebadf.js b/test/parallel/test-listen-fd-ebadf.js index dfa99e274a76e6..ffb98a9e5eb9be 100644 --- a/test/parallel/test-listen-fd-ebadf.js +++ b/test/parallel/test-listen-fd-ebadf.js @@ -1,11 +1,23 @@ 'use strict'; const common = require('../common'); + const assert = require('assert'); +const fs = require('fs'); const net = require('net'); net.createServer(common.mustNotCall()).listen({fd: 2}) .on('error', common.mustCall(onError)); -net.createServer(common.mustNotCall()).listen({fd: 42}) + +let invalidFd = 2; + +// Get first known bad file descriptor. +try { + while (fs.fstatSync(++invalidFd)); +} catch (e) { + // do nothing; we now have an invalid fd +} + +net.createServer(common.mustNotCall()).listen({ fd: invalidFd }) .on('error', common.mustCall(onError)); function onError(ex) { diff --git a/test/parallel/test-module-builtin.js b/test/parallel/test-module-builtin.js new file mode 100644 index 00000000000000..3897d71ecf4405 --- /dev/null +++ b/test/parallel/test-module-builtin.js @@ -0,0 +1,14 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { builtinModules } = require('module'); + +// Includes modules in lib/ (even deprecated ones) +assert(builtinModules.includes('http')); +assert(builtinModules.includes('sys')); + +// Does not include internal modules +assert.deepStrictEqual( + builtinModules.filter((mod) => mod.startsWith('internal/')), + [] +); diff --git a/test/parallel/test-regress-GH-819.js b/test/parallel/test-net-connect-after-destroy.js similarity index 69% rename from test/parallel/test-regress-GH-819.js rename to test/parallel/test-net-connect-after-destroy.js index 719d4398251eab..6697cf8e32f8e4 100644 --- a/test/parallel/test-regress-GH-819.js +++ b/test/parallel/test-net-connect-after-destroy.js @@ -1,4 +1,6 @@ 'use strict'; +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/819. + require('../common'); const net = require('net'); diff --git a/test/parallel/test-regress-GH-746.js b/test/parallel/test-net-listen-after-destroying-stdin.js similarity index 82% rename from test/parallel/test-regress-GH-746.js rename to test/parallel/test-net-listen-after-destroying-stdin.js index 9a6e0e8980f311..5a4c8f4f65c55f 100644 --- a/test/parallel/test-regress-GH-746.js +++ b/test/parallel/test-net-listen-after-destroying-stdin.js @@ -1,6 +1,7 @@ 'use strict'; // Just test that destroying stdin doesn't mess up listening on a server. -// This is a regression test for GH-746. +// This is a regression test for +// https://github.com/nodejs/node-v0.x-archive/issues/746. const common = require('../common'); const net = require('net'); diff --git a/test/parallel/test-net-pingpong.js b/test/parallel/test-net-pingpong.js index b2f756c1ba7147..1bcf5992124342 100644 --- a/test/parallel/test-net-pingpong.js +++ b/test/parallel/test-net-pingpong.js @@ -16,10 +16,13 @@ function pingPongTest(port, host) { function onSocket(socket) { assert.strictEqual(socket.server, server); - server.getConnections(common.mustCall(function(err, connections) { - assert.ifError(err); - assert.strictEqual(connections, 1); - })); + assert.strictEqual( + server, + server.getConnections(common.mustCall(function(err, connections) { + assert.ifError(err); + assert.strictEqual(connections, 1); + })) + ); socket.setNoDelay(); socket.timeout = 0; diff --git a/test/parallel/test-net-timeout-no-handle.js b/test/parallel/test-net-timeout-no-handle.js new file mode 100644 index 00000000000000..539f661cae8414 --- /dev/null +++ b/test/parallel/test-net-timeout-no-handle.js @@ -0,0 +1,17 @@ +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); + +const socket = new net.Socket(); +socket.setTimeout(common.platformTimeout(50)); + +socket.on('timeout', common.mustCall(() => { + assert.strictEqual(socket._handle, null); +})); + +socket.on('connect', common.mustNotCall()); + +// since the timeout is unrefed, the code will exit without this +setTimeout(() => {}, common.platformTimeout(200)); diff --git a/test/parallel/test-openssl-ca-options.js b/test/parallel/test-openssl-ca-options.js new file mode 100644 index 00000000000000..b51b0ecf698035 --- /dev/null +++ b/test/parallel/test-openssl-ca-options.js @@ -0,0 +1,30 @@ +'use strict'; +// This test checks the usage of --use-bundled-ca and --use-openssl-ca arguments +// to verify that both are not used at the same time. +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const os = require('os'); +const childProcess = require('child_process'); +const result = childProcess.spawnSync( + process.execPath, + [ '--use-bundled-ca', '--use-openssl-ca', '-p', 'process.version' ], + { encoding: 'utf8' } +); + +assert.strictEqual(result.stderr, `${process.execPath +}: either --use-openssl-ca or --use-bundled-ca can be used, not both${os.EOL}` +); +assert.strictEqual(result.status, 9); + +const useBundledCA = childProcess.spawnSync(process.execPath, [ + '--use-bundled-ca', + '-p', 'process.version']); +assert.strictEqual(useBundledCA.status, 0); + +const useOpenSSLCA = childProcess.spawnSync(process.execPath, [ + '--use-openssl-ca', + '-p', 'process.version']); +assert.strictEqual(useOpenSSLCA.status, 0); diff --git a/test/parallel/test-os-userinfo-handles-getter-errors.js b/test/parallel/test-os-userinfo-handles-getter-errors.js new file mode 100644 index 00000000000000..146ab6c812379f --- /dev/null +++ b/test/parallel/test-os-userinfo-handles-getter-errors.js @@ -0,0 +1,18 @@ +'use strict'; +// Tests that os.userInfo correctly handles errors thrown by option property +// getters. See https://github.com/nodejs/node/issues/12370. + +const common = require('../common'); +const assert = require('assert'); +const execFile = require('child_process').execFile; + +const script = `os.userInfo({ + get encoding() { + throw new Error('xyz'); + } +})`; + +const node = process.execPath; +execFile(node, [ '-e', script ], common.mustCall((err, stdout, stderr) => { + assert(stderr.includes('Error: xyz'), 'userInfo crashes'); +})); diff --git a/test/parallel/test-pipe-unref.js b/test/parallel/test-pipe-unref.js index 17caa01ef0149d..cfe7a97ca59fd3 100644 --- a/test/parallel/test-pipe-unref.js +++ b/test/parallel/test-pipe-unref.js @@ -2,10 +2,10 @@ const common = require('../common'); const net = require('net'); +// This test should end immediately after `unref` is called + common.refreshTmpDir(); const s = net.Server(); s.listen(common.PIPE); s.unref(); - -setTimeout(common.mustNotCall(), 1000).unref(); diff --git a/test/parallel/test-process-config.js b/test/parallel/test-process-config.js index 1700eed9640b03..9fd869dceba03c 100644 --- a/test/parallel/test-process-config.js +++ b/test/parallel/test-process-config.js @@ -24,7 +24,9 @@ if (!fs.existsSync(configPath)) { let config = fs.readFileSync(configPath, 'utf8'); // Clean up comment at the first line. -config = config.split('\n').slice(1).join('\n').replace(/'/g, '"'); +config = config.split('\n').slice(1).join('\n'); +config = config.replace(/"/g, '\\"'); +config = config.replace(/'/g, '"'); config = JSON.parse(config, function(key, value) { if (value === 'true') return true; if (value === 'false') return false; diff --git a/test/parallel/test-process-ppid.js b/test/parallel/test-process-ppid.js new file mode 100644 index 00000000000000..d78ef3a2dd9ae7 --- /dev/null +++ b/test/parallel/test-process-ppid.js @@ -0,0 +1,16 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[2] === 'child') { + // The following console.log() call is part of the test's functionality. + console.log(process.ppid); +} else { + const child = cp.spawnSync(process.execPath, [__filename, 'child']); + + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); + assert.strictEqual(+child.stdout.toString().trim(), process.pid); + assert.strictEqual(child.stderr.toString().trim(), ''); +} diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index 27311f7eda84e3..b0377afd4a1899 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -9,7 +9,7 @@ if (common.hasCrypto) { expected_keys.push('openssl'); } -if (typeof Intl !== 'undefined') { +if (common.hasIntl) { expected_keys.push('icu'); } diff --git a/test/parallel/test-promises-unhandled-proxy-rejections.js b/test/parallel/test-promises-unhandled-proxy-rejections.js new file mode 100644 index 00000000000000..9c3f364de2e53b --- /dev/null +++ b/test/parallel/test-promises-unhandled-proxy-rejections.js @@ -0,0 +1,31 @@ +'use strict'; +const common = require('../common'); + +const expectedPromiseWarning = 'Unhandled promise rejection (rejection id: ' + + '1): [object Object]'; + +function throwErr() { + throw new Error('Error from proxy'); +} + +const thorny = new Proxy({}, { + getPrototypeOf: throwErr, + setPrototypeOf: throwErr, + isExtensible: throwErr, + preventExtensions: throwErr, + getOwnPropertyDescriptor: throwErr, + defineProperty: throwErr, + has: throwErr, + get: throwErr, + set: throwErr, + deleteProperty: throwErr, + ownKeys: throwErr, + apply: throwErr, + construct: throwErr +}); + +common.expectWarning('UnhandledPromiseRejectionWarning', + expectedPromiseWarning); + +// ensure this doesn't crash +Promise.reject(thorny); diff --git a/test/parallel/test-repl-definecommand.js b/test/parallel/test-repl-definecommand.js index 80a06af6b077d2..2fac805222885d 100644 --- a/test/parallel/test-repl-definecommand.js +++ b/test/parallel/test-repl-definecommand.js @@ -23,14 +23,14 @@ r.defineCommand('say1', { help: 'help for say1', action: function(thing) { output = ''; - this.write(`hello ${thing}`); + this.output.write(`hello ${thing}\n`); this.displayPrompt(); } }); r.defineCommand('say2', function() { output = ''; - this.write('hello from say2'); + this.output.write('hello from say2\n'); this.displayPrompt(); }); @@ -38,6 +38,12 @@ inputStream.write('.help\n'); assert(/\n.say1 help for say1\n/.test(output), 'help for say1 not present'); assert(/\n.say2\n/.test(output), 'help for say2 not present'); inputStream.write('.say1 node developer\n'); -assert(/> hello node developer/.test(output), 'say1 outputted incorrectly'); +assert.ok(output.startsWith('hello node developer\n'), + `say1 output starts incorrectly: "${output}"`); +assert.ok(output.includes('> '), + `say1 output does not include prompt: "${output}"`); inputStream.write('.say2 node developer\n'); -assert(/> hello from say2/.test(output), 'say2 outputted incorrectly'); +assert.ok(output.startsWith('hello from say2\n'), + `say2 output starts incorrectly: "${output}"`); +assert.ok(output.includes('> '), + `say2 output does not include prompt: "${output}"`); diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 1179259c79d026..d29d816a425976 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -212,6 +212,56 @@ testMe.complete('require(\'n', common.mustCall(function(error, data) { })); } +// Test tab completion for require() relative to the current directory +{ + putIn.run(['.clear']); + + const cwd = process.cwd(); + process.chdir(__dirname); + + ['require(\'.', 'require(".'].forEach((input) => { + testMe.complete(input, common.mustCall((err, data) => { + assert.strictEqual(err, null); + assert.strictEqual(data.length, 2); + assert.strictEqual(data[1], '.'); + assert.strictEqual(data[0].length, 2); + assert.ok(data[0].includes('./')); + assert.ok(data[0].includes('../')); + })); + }); + + ['require(\'..', 'require("..'].forEach((input) => { + testMe.complete(input, common.mustCall((err, data) => { + assert.strictEqual(err, null); + assert.deepStrictEqual(data, [['../'], '..']); + })); + }); + + ['./', './test-'].forEach((path) => { + [`require('${path}`, `require("${path}`].forEach((input) => { + testMe.complete(input, common.mustCall((err, data) => { + assert.strictEqual(err, null); + assert.strictEqual(data.length, 2); + assert.strictEqual(data[1], path); + assert.ok(data[0].includes('./test-repl-tab-complete')); + })); + }); + }); + + ['../parallel/', '../parallel/test-'].forEach((path) => { + [`require('${path}`, `require("${path}`].forEach((input) => { + testMe.complete(input, common.mustCall((err, data) => { + assert.strictEqual(err, null); + assert.strictEqual(data.length, 2); + assert.strictEqual(data[1], path); + assert.ok(data[0].includes('../parallel/test-repl-tab-complete')); + })); + }); + }); + + process.chdir(cwd); +} + // Make sure tab completion works on context properties putIn.run(['.clear']); @@ -294,7 +344,7 @@ const testNonGlobal = repl.start({ const builtins = [['Infinity', '', 'Int16Array', 'Int32Array', 'Int8Array'], 'I']; -if (typeof Intl === 'object') { +if (common.hasIntl) { builtins[0].push('Intl'); } testNonGlobal.complete('I', common.mustCall((error, data) => { diff --git a/test/parallel/test-socket-write-after-fin.js b/test/parallel/test-socket-write-after-fin.js index c8ff56871734af..2551d3f54f651f 100644 --- a/test/parallel/test-socket-write-after-fin.js +++ b/test/parallel/test-socket-write-after-fin.js @@ -2,7 +2,7 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); -const expected = 'hello1hello2hello3\nTHUNDERMUSCLE!'; +const expected = 'hello1hello2hello3\nbye'; const server = net.createServer({ allowHalfOpen: true @@ -35,5 +35,6 @@ server.listen(0, common.mustCall(function() { sock.write('hello1'); sock.write('hello2'); sock.write('hello3\n'); - sock.end('THUNDERMUSCLE!'); + assert.strictEqual(sock.end('bye'), sock); + })); diff --git a/test/parallel/test-tls-buffersize.js b/test/parallel/test-tls-buffersize.js new file mode 100644 index 00000000000000..49848cd865aca5 --- /dev/null +++ b/test/parallel/test-tls-buffersize.js @@ -0,0 +1,43 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const tls = require('tls'); + +const iter = 10; +const overhead = 30; + +const server = tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem') +}, common.mustCall((socket) => { + socket.on('readable', common.mustCallAtLeast(() => { + socket.read(); + }, 1)); + + socket.on('end', common.mustCall(() => { + server.close(); + })); +})); + +server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false + }, common.mustCall(() => { + assert.strictEqual(client.bufferSize, 0); + + for (let i = 1; i < iter; i++) { + client.write('a'); + assert.strictEqual(client.bufferSize, i + overhead); + } + + client.on('finish', common.mustCall(() => { + assert.strictEqual(client.bufferSize, 0); + })); + + client.end(); + })); +})); diff --git a/test/parallel/test-tls-invoke-queued.js b/test/parallel/test-tls-invoke-queued.js index 3d4cb8b00f3af0..159d436e81f7fa 100644 --- a/test/parallel/test-tls-invoke-queued.js +++ b/test/parallel/test-tls-invoke-queued.js @@ -14,12 +14,12 @@ const server = tls.createServer({ key: fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`), cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`) }, common.mustCall(function(c) { - c._write('hello ', null, common.mustCall(function() { - c._write('world!', null, common.mustCall(function() { + c.write('hello ', null, common.mustCall(function() { + c.write('world!', null, common.mustCall(function() { c.destroy(); })); // Data on next _write() will be written but callback will not be invoked - c._write(' gosh', null, common.mustNotCall()); + c.write(' gosh', null, common.mustNotCall()); })); server.close(); diff --git a/test/parallel/test-tls-lookup.js b/test/parallel/test-tls-lookup.js new file mode 100644 index 00000000000000..698f0680767539 --- /dev/null +++ b/test/parallel/test-tls-lookup.js @@ -0,0 +1,32 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const tls = require('tls'); + +const expectedError = /^TypeError: "lookup" option should be a function$/; + +['foobar', 1, {}, []].forEach(function connectThrows(input) { + const opts = { + host: 'localhost', + port: common.PORT, + lookup: input + }; + + assert.throws(function() { + tls.connect(opts); + }, expectedError); +}); + +connectDoesNotThrow(common.mustCall(() => {})); + +function connectDoesNotThrow(input) { + const opts = { + host: 'localhost', + port: common.PORT, + lookup: input + }; + + assert.doesNotThrow(function() { + tls.connect(opts); + }); +} diff --git a/test/parallel/test-tls-securepair-fiftharg.js b/test/parallel/test-tls-securepair-fiftharg.js index 52f8b6c0ee8c13..289e6dd7f429a4 100644 --- a/test/parallel/test-tls-securepair-fiftharg.js +++ b/test/parallel/test-tls-securepair-fiftharg.js @@ -10,10 +10,9 @@ const sslcontext = tls.createSecureContext({ key: fixtures.readSync('test_key.pem') }); -let catchedServername; const pair = tls.createSecurePair(sslcontext, true, false, false, { - SNICallback: common.mustCall(function(servername, cb) { - catchedServername = servername; + SNICallback: common.mustCall((servername, cb) => { + assert.strictEqual(servername, 'www.google.com'); }) }); @@ -21,7 +20,3 @@ const pair = tls.createSecurePair(sslcontext, true, false, false, { const sslHello = fixtures.readSync('google_ssl_hello.bin'); pair.encrypted.write(sslHello); - -process.on('exit', function() { - assert.strictEqual('www.google.com', catchedServername); -}); diff --git a/test/parallel/test-tls-server-verify.js b/test/parallel/test-tls-server-verify.js index f482b87d2308c3..609ac3ddc02afe 100644 --- a/test/parallel/test-tls-server-verify.js +++ b/test/parallel/test-tls-server-verify.js @@ -306,7 +306,7 @@ function runTest(port, testIndex) { } else { server.close(); successfulTests++; - runTest(port, nextTest++); + runTest(0, nextTest++); } } @@ -325,7 +325,7 @@ function runTest(port, testIndex) { if (clientsCompleted === tcase.clients.length) { server.close(); successfulTests++; - runTest(port, nextTest++); + runTest(0, nextTest++); } }); } @@ -337,7 +337,6 @@ function runTest(port, testIndex) { let nextTest = 0; runTest(0, nextTest++); -runTest(0, nextTest++); process.on('exit', function() { diff --git a/test/parallel/test-tls-transport-destroy-after-own-gc.js b/test/parallel/test-tls-transport-destroy-after-own-gc.js new file mode 100644 index 00000000000000..46f630982af643 --- /dev/null +++ b/test/parallel/test-tls-transport-destroy-after-own-gc.js @@ -0,0 +1,30 @@ +// Flags: --expose-gc +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/17475 +// Unfortunately, this tests only "works" reliably when checked with valgrind or +// a similar tool. + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { TLSSocket } = require('tls'); +const makeDuplexPair = require('../common/duplexpair'); + +let { clientSide } = makeDuplexPair(); + +let clientTLS = new TLSSocket(clientSide, { isServer: false }); +// eslint-disable-next-line no-unused-vars +let clientTLSHandle = clientTLS._handle; + +setImmediate(() => { + clientTLS = null; + global.gc(); + clientTLSHandle = null; + global.gc(); + setImmediate(() => { + clientSide = null; + global.gc(); + }); +}); diff --git a/test/parallel/test-url-domain-ascii-unicode.js b/test/parallel/test-url-domain-ascii-unicode.js new file mode 100644 index 00000000000000..49259a7ab0f4c4 --- /dev/null +++ b/test/parallel/test-url-domain-ascii-unicode.js @@ -0,0 +1,31 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) + common.skip('missing Intl'); + +const strictEqual = require('assert').strictEqual; +const url = require('url'); + +const domainToASCII = url.domainToASCII; +const domainToUnicode = url.domainToUnicode; + +const domainWithASCII = [ + ['ıíd', 'xn--d-iga7r'], + ['يٴ', 'xn--mhb8f'], + ['www.ϧƽəʐ.com', 'www.xn--cja62apfr6c.com'], + ['новини.com', 'xn--b1amarcd.com'], + ['名がドメイン.com', 'xn--v8jxj3d1dzdz08w.com'], + ['افغانستا.icom.museum', 'xn--mgbaal8b0b9b2b.icom.museum'], + ['الجزائر.icom.fake', 'xn--lgbbat1ad8j.icom.fake'], + ['भारत.org', 'xn--h2brj9c.org'] +]; + +domainWithASCII.forEach((pair) => { + const domain = pair[0]; + const ascii = pair[1]; + const domainConvertedToASCII = domainToASCII(domain); + strictEqual(domainConvertedToASCII, ascii); + const asciiConvertedToUnicode = domainToUnicode(ascii); + strictEqual(asciiConvertedToUnicode, domain); +}); diff --git a/test/parallel/test-util-format.js b/test/parallel/test-util-format.js index b88bf9754e42c8..05d38c3ee727e2 100644 --- a/test/parallel/test-util-format.js +++ b/test/parallel/test-util-format.js @@ -27,21 +27,51 @@ assert.throws(function() { util.format('%d', symbol); }, /^TypeError: Cannot convert a Symbol value to a number$/); +// Number format specifier +assert.strictEqual(util.format('%d'), '%d'); assert.strictEqual(util.format('%d', 42.0), '42'); assert.strictEqual(util.format('%d', 42), '42'); -assert.strictEqual(util.format('%s', 42), '42'); -assert.strictEqual(util.format('%j', 42), '42'); - -assert.strictEqual(util.format('%d', '42.0'), '42'); assert.strictEqual(util.format('%d', '42'), '42'); -assert.strictEqual(util.format('%s', '42'), '42'); -assert.strictEqual(util.format('%j', '42'), '"42"'); +assert.strictEqual(util.format('%d', '42.0'), '42'); +assert.strictEqual(util.format('%d', 1.5), '1.5'); +assert.strictEqual(util.format('%d', -0.5), '-0.5'); +assert.strictEqual(util.format('%d', ''), '0'); -assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo'); +// Integer format specifier +assert.strictEqual(util.format('%i'), '%i'); +assert.strictEqual(util.format('%i', 42.0), '42'); +assert.strictEqual(util.format('%i', 42), '42'); +assert.strictEqual(util.format('%i', '42'), '42'); +assert.strictEqual(util.format('%i', '42.0'), '42'); +assert.strictEqual(util.format('%i', 1.5), '1'); +assert.strictEqual(util.format('%i', -0.5), '0'); +assert.strictEqual(util.format('%i', ''), 'NaN'); + +// Float format specifier +assert.strictEqual(util.format('%f'), '%f'); +assert.strictEqual(util.format('%f', 42.0), '42'); +assert.strictEqual(util.format('%f', 42), '42'); +assert.strictEqual(util.format('%f', '42'), '42'); +assert.strictEqual(util.format('%f', '42.0'), '42'); +assert.strictEqual(util.format('%f', 1.5), '1.5'); +assert.strictEqual(util.format('%f', -0.5), '-0.5'); +assert.strictEqual(util.format('%f', Math.PI), '3.141592653589793'); +assert.strictEqual(util.format('%f', ''), 'NaN'); +// String format specifier assert.strictEqual(util.format('%s'), '%s'); assert.strictEqual(util.format('%s', undefined), 'undefined'); assert.strictEqual(util.format('%s', 'foo'), 'foo'); +assert.strictEqual(util.format('%s', 42), '42'); +assert.strictEqual(util.format('%s', '42'), '42'); + +// JSON format specifier +assert.strictEqual(util.format('%j'), '%j'); +assert.strictEqual(util.format('%j', 42), '42'); +assert.strictEqual(util.format('%j', '42'), '"42"'); + +// Various format specifiers +assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo'); assert.strictEqual(util.format('%s:%s'), '%s:%s'); assert.strictEqual(util.format('%s:%s', undefined), 'undefined:%s'); assert.strictEqual(util.format('%s:%s', 'foo'), 'foo:%s'); @@ -50,11 +80,9 @@ assert.strictEqual(util.format('%s:%s', 'foo', 'bar', 'baz'), 'foo:bar baz'); assert.strictEqual(util.format('%%%s%%', 'hi'), '%hi%'); assert.strictEqual(util.format('%%%s%%%%', 'hi'), '%hi%%'); assert.strictEqual(util.format('%sbc%%def', 'a'), 'abc%def'); - assert.strictEqual(util.format('%d:%d', 12, 30), '12:30'); assert.strictEqual(util.format('%d:%d', 12), '12:%d'); assert.strictEqual(util.format('%d:%d'), '%d:%d'); - assert.strictEqual(util.format('o: %j, a: %j', {}, []), 'o: {}, a: []'); assert.strictEqual(util.format('o: %j, a: %j', {}), 'o: {}, a: %j'); assert.strictEqual(util.format('o: %j, a: %j'), 'o: %j, a: %j'); diff --git a/test/parallel/test-regress-GH-7511.js b/test/parallel/test-vm-access-process-env.js similarity index 62% rename from test/parallel/test-regress-GH-7511.js rename to test/parallel/test-vm-access-process-env.js index a7ce8311d7237f..081aa642fcff38 100644 --- a/test/parallel/test-regress-GH-7511.js +++ b/test/parallel/test-vm-access-process-env.js @@ -1,4 +1,8 @@ 'use strict'; +// Tests that node does neither crash nor throw an error when accessing +// process.env when inside a VM context. +// See https://github.com/nodejs/node-v0.x-archive/issues/7511. + require('../common'); const assert = require('assert'); const vm = require('vm'); diff --git a/test/parallel/test-regress-GH-12371.js b/test/parallel/test-vm-api-handles-getter-errors.js similarity index 80% rename from test/parallel/test-regress-GH-12371.js rename to test/parallel/test-vm-api-handles-getter-errors.js index 6ab65a8e348e1e..6a74fb29c17c81 100644 --- a/test/parallel/test-regress-GH-12371.js +++ b/test/parallel/test-vm-api-handles-getter-errors.js @@ -1,15 +1,13 @@ 'use strict'; +// Tests that vm.createScript and runInThisContext correctly handle errors +// thrown by option property getters. +// See https://github.com/nodejs/node/issues/12369. + const common = require('../common'); const assert = require('assert'); const execFile = require('child_process').execFile; -const scripts = [ - `os.userInfo({ - get encoding() { - throw new Error('xyz'); - } - })` -]; +const scripts = []; ['filename', 'cachedData', 'produceCachedData', 'lineOffset', 'columnOffset'] .forEach((prop) => { diff --git a/test/parallel/test-whatwg-url-constructor.js b/test/parallel/test-whatwg-url-constructor.js new file mode 100644 index 00000000000000..16bcac74cc6bcd --- /dev/null +++ b/test/parallel/test-whatwg-url-constructor.js @@ -0,0 +1,143 @@ +'use strict'; +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const fixtures = require('../common/fixtures'); +const { URL, URLSearchParams } = require('url'); +const { test, assert_equals, assert_true, assert_throws } = + require('../common/wpt'); + +const request = { + response: require(fixtures.path('url-tests')) +}; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/url-constructor.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +function runURLConstructorTests() { + // var setup = async_test("Loading data…") + // setup.step(function() { + // var request = new XMLHttpRequest() + // request.open("GET", "urltestdata.json") + // request.send() + // request.responseType = "json" + // request.onload = setup.step_func(function() { + runURLTests(request.response) + // setup.done() + // }) + // }) +} + +function bURL(url, base) { + return new URL(url, base || "about:blank") +} + + +function runURLTests(urltests) { + for(var i = 0, l = urltests.length; i < l; i++) { + var expected = urltests[i] + if (typeof expected === "string") continue // skip comments + + test(function() { + if (expected.failure) { + assert_throws(new TypeError(), function() { + bURL(expected.input, expected.base) + }) + return + } + + var url = bURL(expected.input, expected.base) + assert_equals(url.href, expected.href, "href") + assert_equals(url.protocol, expected.protocol, "protocol") + assert_equals(url.username, expected.username, "username") + assert_equals(url.password, expected.password, "password") + assert_equals(url.host, expected.host, "host") + assert_equals(url.hostname, expected.hostname, "hostname") + assert_equals(url.port, expected.port, "port") + assert_equals(url.pathname, expected.pathname, "pathname") + assert_equals(url.search, expected.search, "search") + if ("searchParams" in expected) { + assert_true("searchParams" in url) + assert_equals(url.searchParams.toString(), expected.searchParams, "searchParams") + } + assert_equals(url.hash, expected.hash, "hash") + }, "Parsing: <" + expected.input + "> against <" + expected.base + ">") + } +} + +function runURLSearchParamTests() { + test(function() { + var url = bURL('http://example.org/?a=b') + assert_true("searchParams" in url) + var searchParams = url.searchParams + assert_true(url.searchParams === searchParams, 'Object identity should hold.') + }, 'URL.searchParams getter') + + test(function() { + var url = bURL('http://example.org/?a=b') + assert_true("searchParams" in url) + var searchParams = url.searchParams + assert_equals(searchParams.toString(), 'a=b') + + searchParams.set('a', 'b') + assert_equals(url.searchParams.toString(), 'a=b') + assert_equals(url.search, '?a=b') + url.search = '' + assert_equals(url.searchParams.toString(), '') + assert_equals(url.search, '') + assert_equals(searchParams.toString(), '') + }, 'URL.searchParams updating, clearing') + + test(function() { + 'use strict' + var urlString = 'http://example.org' + var url = bURL(urlString) + assert_throws(TypeError(), function() { url.searchParams = new URLSearchParams(urlString) }) + }, 'URL.searchParams setter, invalid values') + + test(function() { + var url = bURL('http://example.org/file?a=b&c=d') + assert_true("searchParams" in url) + var searchParams = url.searchParams + assert_equals(url.search, '?a=b&c=d') + assert_equals(searchParams.toString(), 'a=b&c=d') + + // Test that setting 'search' propagates to the URL object's query object. + url.search = 'e=f&g=h' + assert_equals(url.search, '?e=f&g=h') + assert_equals(searchParams.toString(), 'e=f&g=h') + + // ..and same but with a leading '?'. + url.search = '?e=f&g=h' + assert_equals(url.search, '?e=f&g=h') + assert_equals(searchParams.toString(), 'e=f&g=h') + + // And in the other direction, altering searchParams propagates + // back to 'search'. + searchParams.append('i', ' j ') + assert_equals(url.search, '?e=f&g=h&i=+j+') + assert_equals(url.searchParams.toString(), 'e=f&g=h&i=+j+') + assert_equals(searchParams.get('i'), ' j ') + + searchParams.set('e', 'updated') + assert_equals(url.search, '?e=updated&g=h&i=+j+') + assert_equals(searchParams.get('e'), 'updated') + + var url2 = bURL('http://example.org/file??a=b&c=d') + assert_equals(url2.search, '??a=b&c=d') + assert_equals(url2.searchParams.toString(), '%3Fa=b&c=d') + + url2.href = 'http://example.org/file??a=b' + assert_equals(url2.search, '??a=b') + assert_equals(url2.searchParams.toString(), '%3Fa=b') + }, 'URL.searchParams and URL.search setters, update propagation') +} +runURLSearchParamTests() +runURLConstructorTests() +/* eslint-enable */ diff --git a/test/parallel/test-whatwg-url-domainto.js b/test/parallel/test-whatwg-url-domainto.js new file mode 100644 index 00000000000000..f8029d8b5d66a4 --- /dev/null +++ b/test/parallel/test-whatwg-url-domainto.js @@ -0,0 +1,51 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); +const { domainToASCII, domainToUnicode } = require('url'); + +// Tests below are not from WPT. +const tests = require('../fixtures/url-idna.js'); +const wptToASCIITests = require('../fixtures/url-toascii.js'); + +{ + assert.throws(() => domainToASCII(), /^TypeError: The "domain" argument must be specified$/); + assert.throws(() => domainToUnicode(), /^TypeError: The "domain" argument must be specified$/); + assert.strictEqual(domainToASCII(undefined), 'undefined'); + assert.strictEqual(domainToUnicode(undefined), 'undefined'); +} + +{ + for (const [i, { ascii, unicode }] of tests.entries()) { + assert.strictEqual(ascii, domainToASCII(unicode), + `domainToASCII(${i + 1})`); + assert.strictEqual(unicode, domainToUnicode(ascii), + `domainToUnicode(${i + 1})`); + assert.strictEqual(ascii, domainToASCII(domainToUnicode(ascii)), + `domainToASCII(domainToUnicode(${i + 1}))`); + assert.strictEqual(unicode, domainToUnicode(domainToASCII(unicode)), + `domainToUnicode(domainToASCII(${i + 1}))`); + } +} + +{ + for (const [i, test] of wptToASCIITests.entries()) { + if (typeof test === 'string') + continue; // skip comments + const { comment, input, output } = test; + let caseComment = `Case ${i + 1}`; + if (comment) + caseComment += ` (${comment})`; + if (output === null) { + assert.strictEqual(domainToASCII(input), '', caseComment); + assert.strictEqual(domainToUnicode(input), '', caseComment); + } else { + assert.strictEqual(domainToASCII(input), output, caseComment); + const roundtripped = domainToASCII(domainToUnicode(input)); + assert.strictEqual(roundtripped, output, caseComment); + } + } +} diff --git a/test/parallel/test-whatwg-url-historical.js b/test/parallel/test-whatwg-url-historical.js new file mode 100644 index 00000000000000..466949cd322d37 --- /dev/null +++ b/test/parallel/test-whatwg-url-historical.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const URL = require('url').URL; +const { test, assert_equals, assert_throws } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/historical.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +// var objects = [ +// [function() { return window.location }, "location object"], +// [function() { return document.createElement("a") }, "a element"], +// [function() { return document.createElement("area") }, "area element"], +// ]; + +// objects.forEach(function(o) { +// test(function() { +// var object = o[0](); +// assert_false("searchParams" in object, +// o[1] + " should not have a searchParams attribute"); +// }, "searchParams on " + o[1]); +// }); + +test(function() { + var url = new URL("./foo", "http://www.example.org"); + assert_equals(url.href, "http://www.example.org/foo"); + assert_throws(new TypeError(), function() { + url.href = "./bar"; + }); +}, "Setting URL's href attribute and base URLs"); + +test(function() { + assert_equals(URL.domainToASCII, undefined); +}, "URL.domainToASCII should be undefined"); + +test(function() { + assert_equals(URL.domainToUnicode, undefined); +}, "URL.domainToUnicode should be undefined"); +/* eslint-enable */ diff --git a/test/parallel/test-whatwg-url-inspect.js b/test/parallel/test-whatwg-url-inspect.js new file mode 100644 index 00000000000000..5758b39b8af83d --- /dev/null +++ b/test/parallel/test-whatwg-url-inspect.js @@ -0,0 +1,66 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const util = require('util'); +const URL = require('url').URL; +const assert = require('assert'); + +// Tests below are not from WPT. +const url = new URL('https://username:password@host.name:8080/path/name/?que=ry#hash'); + +assert.strictEqual( + util.inspect(url), + `URL { + href: 'https://username:password@host.name:8080/path/name/?que=ry#hash', + origin: 'https://host.name:8080', + protocol: 'https:', + username: 'username', + password: 'password', + host: 'host.name:8080', + hostname: 'host.name', + port: '8080', + pathname: '/path/name/', + search: '?que=ry', + searchParams: URLSearchParams { 'que' => 'ry' }, + hash: '#hash' }`); + +assert.strictEqual( + util.inspect(url, { showHidden: true }), + `URL { + href: 'https://username:password@host.name:8080/path/name/?que=ry#hash', + origin: 'https://host.name:8080', + protocol: 'https:', + username: 'username', + password: 'password', + host: 'host.name:8080', + hostname: 'host.name', + port: '8080', + pathname: '/path/name/', + search: '?que=ry', + searchParams: URLSearchParams { 'que' => 'ry' }, + hash: '#hash', + cannotBeBase: false, + special: true, + [Symbol(context)]:\x20 + URLContext { + flags: 2032, + scheme: 'https:', + username: 'username', + password: 'password', + host: 'host.name', + port: 8080, + path: [ 'path', 'name', '', [length]: 3 ], + query: 'que=ry', + fragment: 'hash' } }`); + +assert.strictEqual( + util.inspect({ a: url }, { depth: 0 }), + '{ a: [Object] }'); + +class MyURL extends URL {} +assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {')); diff --git a/test/parallel/test-whatwg-url-origin.js b/test/parallel/test-whatwg-url-origin.js new file mode 100644 index 00000000000000..8a5ad6d3154f8f --- /dev/null +++ b/test/parallel/test-whatwg-url-origin.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const fixtures = require('../common/fixtures'); +const URL = require('url').URL; +const { test, assert_equals } = require('../common/wpt'); + +const request = { + response: require(fixtures.path('url-tests')) +}; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/url-origin.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +function runURLOriginTests() { + // var setup = async_test("Loading data…") + // setup.step(function() { + // var request = new XMLHttpRequest() + // request.open("GET", "urltestdata.json") + // request.send() + // request.responseType = "json" + // request.onload = setup.step_func(function() { + runURLTests(request.response) + // setup.done() + // }) + // }) +} + +function bURL(url, base) { + return new URL(url, base || "about:blank") +} + +function runURLTests(urltests) { + for(var i = 0, l = urltests.length; i < l; i++) { + var expected = urltests[i] + if (typeof expected === "string" || !("origin" in expected)) continue + test(function() { + var url = bURL(expected.input, expected.base) + assert_equals(url.origin, expected.origin, "origin") + }, "Origin parsing: <" + expected.input + "> against <" + expected.base + ">") + } +} + +runURLOriginTests() +/* eslint-enable */ diff --git a/test/parallel/test-whatwg-url-parsing.js b/test/parallel/test-whatwg-url-parsing.js new file mode 100644 index 00000000000000..104f25ff4380bb --- /dev/null +++ b/test/parallel/test-whatwg-url-parsing.js @@ -0,0 +1,62 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const URL = require('url').URL; +const assert = require('assert'); +const fixtures = require('../common/fixtures'); + +// Tests below are not from WPT. +const tests = require(fixtures.path('url-tests')); +const failureTests = tests.filter((test) => test.failure).concat([ + { input: '' }, + { input: 'test' }, + { input: undefined }, + { input: 0 }, + { input: true }, + { input: false }, + { input: null }, + { input: new Date() }, + { input: new RegExp() }, + { input: () => {} } +]); + +const expectedError = (err) => /^TypeError: Invalid URL: /.test(err.toString()); + +for (const test of failureTests) { + assert.throws( + () => new URL(test.input, test.base), + (error) => { + if (!expectedError(error)) + return false; + + // The input could be processed, so we don't do strict matching here + const match = (`${error}`).match(/Invalid URL: (.*)$/); + if (!match) { + return false; + } + return error.input === match[1]; + }); +} + +const additional_tests = + require(fixtures.path('url-tests-additional.js')); + +for (const test of additional_tests) { + const url = new URL(test.url); + if (test.href) assert.strictEqual(url.href, test.href); + if (test.origin) assert.strictEqual(url.origin, test.origin); + if (test.protocol) assert.strictEqual(url.protocol, test.protocol); + if (test.username) assert.strictEqual(url.username, test.username); + if (test.password) assert.strictEqual(url.password, test.password); + if (test.hostname) assert.strictEqual(url.hostname, test.hostname); + if (test.host) assert.strictEqual(url.host, test.host); + if (test.port !== undefined) assert.strictEqual(url.port, test.port); + if (test.pathname) assert.strictEqual(url.pathname, test.pathname); + if (test.search) assert.strictEqual(url.search, test.search); + if (test.hash) assert.strictEqual(url.hash, test.hash); +} diff --git a/test/parallel/test-whatwg-url-properties.js b/test/parallel/test-whatwg-url-properties.js new file mode 100644 index 00000000000000..d6caae511aed47 --- /dev/null +++ b/test/parallel/test-whatwg-url-properties.js @@ -0,0 +1,162 @@ +// Flags: --expose-internals +'use strict'; + +require('../common'); +const URL = require('url').URL; +const assert = require('assert'); +const urlToOptions = require('internal/url').urlToOptions; + +// Tests below are not from WPT. +const url = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test'); +const oldParams = url.searchParams; // for test of [SameObject] + +// To retrieve enumerable but not necessarily own properties, +// we need to use the for-in loop. +const props = []; +for (const prop in url) { + props.push(prop); +} + +// See: https://url.spec.whatwg.org/#api +// https://heycam.github.io/webidl/#es-attributes +// https://heycam.github.io/webidl/#es-stringifier +const expected = ['toString', + 'href', 'origin', 'protocol', + 'username', 'password', 'host', 'hostname', 'port', + 'pathname', 'search', 'searchParams', 'hash', 'toJSON']; + +assert.deepStrictEqual(props, expected); + +// href is writable (not readonly) and is stringifier +assert.strictEqual(url.toString(), url.href); +url.href = 'http://user:pass@foo.bar.com:21/aaa/zzz?l=25#test'; +assert.strictEqual(url.href, + 'http://user:pass@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual(url.toString(), url.href); +// Return true because it's configurable, but because the properties +// are defined on the prototype per the spec, the deletion has no effect +assert.strictEqual((delete url.href), true); +assert.strictEqual(url.href, + 'http://user:pass@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual(url.searchParams, oldParams); // [SameObject] + +// searchParams is readonly. Under strict mode setting a +// non-writable property should throw. +// Note: this error message is subject to change in V8 updates +assert.throws( + () => url.origin = 'http://foo.bar.com:22', + /^TypeError: Cannot set property origin of \[object URL\] which has only a getter$/ +); +assert.strictEqual(url.origin, 'http://foo.bar.com:21'); +assert.strictEqual(url.toString(), + 'http://user:pass@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.origin), true); +assert.strictEqual(url.origin, 'http://foo.bar.com:21'); + +// The following properties should be writable (not readonly) +url.protocol = 'https:'; +assert.strictEqual(url.protocol, 'https:'); +assert.strictEqual(url.toString(), + 'https://user:pass@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.protocol), true); +assert.strictEqual(url.protocol, 'https:'); + +url.username = 'user2'; +assert.strictEqual(url.username, 'user2'); +assert.strictEqual(url.toString(), + 'https://user2:pass@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.username), true); +assert.strictEqual(url.username, 'user2'); + +url.password = 'pass2'; +assert.strictEqual(url.password, 'pass2'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.com:21/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.password), true); +assert.strictEqual(url.password, 'pass2'); + +url.host = 'foo.bar.net:22'; +assert.strictEqual(url.host, 'foo.bar.net:22'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.net:22/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.host), true); +assert.strictEqual(url.host, 'foo.bar.net:22'); + +url.hostname = 'foo.bar.org'; +assert.strictEqual(url.hostname, 'foo.bar.org'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:22/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.hostname), true); +assert.strictEqual(url.hostname, 'foo.bar.org'); + +url.port = '23'; +assert.strictEqual(url.port, '23'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:23/aaa/zzz?l=25#test'); +assert.strictEqual((delete url.port), true); +assert.strictEqual(url.port, '23'); + +url.pathname = '/aaa/bbb'; +assert.strictEqual(url.pathname, '/aaa/bbb'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:23/aaa/bbb?l=25#test'); +assert.strictEqual((delete url.pathname), true); +assert.strictEqual(url.pathname, '/aaa/bbb'); + +url.search = '?k=99'; +assert.strictEqual(url.search, '?k=99'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:23/aaa/bbb?k=99#test'); +assert.strictEqual((delete url.search), true); +assert.strictEqual(url.search, '?k=99'); + +url.hash = '#abcd'; +assert.strictEqual(url.hash, '#abcd'); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:23/aaa/bbb?k=99#abcd'); +assert.strictEqual((delete url.hash), true); +assert.strictEqual(url.hash, '#abcd'); + +// searchParams is readonly. Under strict mode setting a +// non-writable property should throw. +// Note: this error message is subject to change in V8 updates +assert.throws( + () => url.searchParams = '?k=88', + /^TypeError: Cannot set property searchParams of \[object URL\] which has only a getter$/ +); +assert.strictEqual(url.searchParams, oldParams); +assert.strictEqual(url.toString(), + 'https://user2:pass2@foo.bar.org:23/aaa/bbb?k=99#abcd'); +assert.strictEqual((delete url.searchParams), true); +assert.strictEqual(url.searchParams, oldParams); + +// Test urlToOptions +{ + const opts = + urlToOptions(new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test')); + assert.strictEqual(opts instanceof URL, false); + assert.strictEqual(opts.protocol, 'http:'); + assert.strictEqual(opts.auth, 'user:pass'); + assert.strictEqual(opts.hostname, 'foo.bar.com'); + assert.strictEqual(opts.port, 21); + assert.strictEqual(opts.path, '/aaa/zzz?l=24'); + assert.strictEqual(opts.pathname, '/aaa/zzz'); + assert.strictEqual(opts.search, '?l=24'); + assert.strictEqual(opts.hash, '#test'); +} + +// Test special origins +[ + { expected: 'https://whatwg.org', + url: 'blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f' }, + { expected: 'ftp://example.org', url: 'ftp://example.org/foo' }, + { expected: 'gopher://gopher.quux.org', url: 'gopher://gopher.quux.org/1/' }, + { expected: 'http://example.org', url: 'http://example.org/foo' }, + { expected: 'https://example.org', url: 'https://example.org/foo' }, + { expected: 'ws://example.org', url: 'ws://example.org/foo' }, + { expected: 'wss://example.org', url: 'wss://example.org/foo' }, + { expected: 'null', url: 'file:///tmp/mock/path' }, + { expected: 'null', url: 'npm://nodejs/rules' } +].forEach((test) => { + assert.strictEqual(new URL(test.url).origin, test.expected); +}); diff --git a/test/parallel/test-whatwg-url-searchparams-append.js b/test/parallel/test-whatwg-url-searchparams-append.js new file mode 100644 index 00000000000000..6571f570588339 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-append.js @@ -0,0 +1,73 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_equals, assert_true } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-append.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b'); + assert_equals(params + '', 'a=b'); + params.append('a', 'b'); + assert_equals(params + '', 'a=b&a=b'); + params.append('a', 'c'); + assert_equals(params + '', 'a=b&a=b&a=c'); +}, 'Append same name'); +test(function() { + var params = new URLSearchParams(); + params.append('', ''); + assert_equals(params + '', '='); + params.append('', ''); + assert_equals(params + '', '=&='); +}, 'Append empty strings'); +test(function() { + var params = new URLSearchParams(); + params.append(null, null); + assert_equals(params + '', 'null=null'); + params.append(null, null); + assert_equals(params + '', 'null=null&null=null'); +}, 'Append null'); +test(function() { + var params = new URLSearchParams(); + params.append('first', 1); + params.append('second', 2); + params.append('third', ''); + params.append('first', 10); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"'); + assert_equals(params.get('second'), '2', 'Search params object has name "second" with value "2"'); + assert_equals(params.get('third'), '', 'Search params object has name "third" with value ""'); + params.append('first', 10); + assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"'); +}, 'Append multiple'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.append.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.append('a'); + }, /^TypeError: The "name" and "value" arguments must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.set(obj, 'b'), /^Error: toString$/); + assert.throws(() => params.set('a', obj), /^Error: toString$/); + assert.throws(() => params.set(sym, 'b'), + /^TypeError: Cannot convert a Symbol value to a string$/); + assert.throws(() => params.set('a', sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-constructor.js b/test/parallel/test-whatwg-url-searchparams-constructor.js new file mode 100644 index 00000000000000..a3e15875276087 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-constructor.js @@ -0,0 +1,248 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { + test, assert_equals, assert_true, + assert_false, assert_throws, assert_array_equals +} = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/54c3502d7b/url/urlsearchparams-constructor.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +var params; // Strict mode fix for WPT. +test(function() { + var params = new URLSearchParams(); + assert_equals(params + '', ''); + params = new URLSearchParams(''); + assert_equals(params + '', ''); + params = new URLSearchParams('a=b'); + assert_equals(params + '', 'a=b'); + params = new URLSearchParams(params); + assert_equals(params + '', 'a=b'); +}, 'Basic URLSearchParams construction'); + +test(function() { + var params = new URLSearchParams() + assert_equals(params.toString(), "") +}, "URLSearchParams constructor, no arguments") + +// test(() => { +// params = new URLSearchParams(DOMException.prototype); +// assert_equals(params.toString(), "INDEX_SIZE_ERR=1&DOMSTRING_SIZE_ERR=2&HIERARCHY_REQUEST_ERR=3&WRONG_DOCUMENT_ERR=4&INVALID_CHARACTER_ERR=5&NO_DATA_ALLOWED_ERR=6&NO_MODIFICATION_ALLOWED_ERR=7&NOT_FOUND_ERR=8&NOT_SUPPORTED_ERR=9&INUSE_ATTRIBUTE_ERR=10&INVALID_STATE_ERR=11&SYNTAX_ERR=12&INVALID_MODIFICATION_ERR=13&NAMESPACE_ERR=14&INVALID_ACCESS_ERR=15&VALIDATION_ERR=16&TYPE_MISMATCH_ERR=17&SECURITY_ERR=18&NETWORK_ERR=19&ABORT_ERR=20&URL_MISMATCH_ERR=21"A_EXCEEDED_ERR=22&TIMEOUT_ERR=23&INVALID_NODE_TYPE_ERR=24&DATA_CLONE_ERR=25") +// }, "URLSearchParams constructor, DOMException.prototype as argument") + +test(() => { + params = new URLSearchParams(''); + assert_true(params != null, 'constructor returned non-null value.'); + assert_equals(params.__proto__, URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.'); +}, "URLSearchParams constructor, empty string as argument") + +test(() => { + params = new URLSearchParams({}); + assert_equals(params + '', ""); +}, 'URLSearchParams constructor, {} as argument'); + +test(function() { + var params = new URLSearchParams('a=b'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_false(params.has('b'), 'Search params object has not got name "b"'); + var params = new URLSearchParams('a=b&c'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_true(params.has('c'), 'Search params object has name "c"'); + var params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_true(params.has('a b'), 'Search params object has name "a b"'); + assert_true(params.has(' '), 'Search params object has name " "'); + assert_false(params.has('c'), 'Search params object did not have the name "c"'); + assert_true(params.has(' c'), 'Search params object has name " c"'); + assert_true(params.has('møø'), 'Search params object has name "møø"'); +}, 'URLSearchParams constructor, string.'); + +test(function() { + var seed = new URLSearchParams('a=b&c=d'); + var params = new URLSearchParams(seed); + assert_true(params != null, 'constructor returned non-null value.'); + assert_equals(params.get('a'), 'b'); + assert_equals(params.get('c'), 'd'); + assert_false(params.has('d')); + // The name-value pairs are copied when created; later updates + // should not be observable. + seed.append('e', 'f'); + assert_false(params.has('e')); + params.append('g', 'h'); + assert_false(seed.has('g')); +}, 'URLSearchParams constructor, object.'); + +test(function() { + var params = new URLSearchParams('a=b+c'); + assert_equals(params.get('a'), 'b c'); + params = new URLSearchParams('a+b=c'); + assert_equals(params.get('a b'), 'c'); +}, 'Parse +'); + +test(function() { + const testValue = '+15555555555'; + const params = new URLSearchParams(); + params.set('query', testValue); + var newParams = new URLSearchParams(params.toString()); + + assert_equals(params.toString(), 'query=%2B15555555555'); + assert_equals(params.get('query'), testValue); + assert_equals(newParams.get('query'), testValue); +}, 'Parse encoded +'); + +test(function() { + var params = new URLSearchParams('a=b c'); + assert_equals(params.get('a'), 'b c'); + params = new URLSearchParams('a b=c'); + assert_equals(params.get('a b'), 'c'); +}, 'Parse space'); + +test(function() { + var params = new URLSearchParams('a=b%20c'); + assert_equals(params.get('a'), 'b c'); + params = new URLSearchParams('a%20b=c'); + assert_equals(params.get('a b'), 'c'); +}, 'Parse %20'); + +test(function() { + var params = new URLSearchParams('a=b\0c'); + assert_equals(params.get('a'), 'b\0c'); + params = new URLSearchParams('a\0b=c'); + assert_equals(params.get('a\0b'), 'c'); +}, 'Parse \\0'); + +test(function() { + var params = new URLSearchParams('a=b%00c'); + assert_equals(params.get('a'), 'b\0c'); + params = new URLSearchParams('a%00b=c'); + assert_equals(params.get('a\0b'), 'c'); +}, 'Parse %00'); + +test(function() { + var params = new URLSearchParams('a=b\u2384'); + assert_equals(params.get('a'), 'b\u2384'); + params = new URLSearchParams('a\u2384b=c'); + assert_equals(params.get('a\u2384b'), 'c'); +}, 'Parse \u2384'); // Unicode Character 'COMPOSITION SYMBOL' (U+2384) + +test(function() { + var params = new URLSearchParams('a=b%e2%8e%84'); + assert_equals(params.get('a'), 'b\u2384'); + params = new URLSearchParams('a%e2%8e%84b=c'); + assert_equals(params.get('a\u2384b'), 'c'); +}, 'Parse %e2%8e%84'); // Unicode Character 'COMPOSITION SYMBOL' (U+2384) + +test(function() { + var params = new URLSearchParams('a=b\uD83D\uDCA9c'); + assert_equals(params.get('a'), 'b\uD83D\uDCA9c'); + params = new URLSearchParams('a\uD83D\uDCA9b=c'); + assert_equals(params.get('a\uD83D\uDCA9b'), 'c'); +}, 'Parse \uD83D\uDCA9'); // Unicode Character 'PILE OF POO' (U+1F4A9) + +test(function() { + var params = new URLSearchParams('a=b%f0%9f%92%a9c'); + assert_equals(params.get('a'), 'b\uD83D\uDCA9c'); + params = new URLSearchParams('a%f0%9f%92%a9b=c'); + assert_equals(params.get('a\uD83D\uDCA9b'), 'c'); +}, 'Parse %f0%9f%92%a9'); // Unicode Character 'PILE OF POO' (U+1F4A9) + +test(function() { + var params = new URLSearchParams([]); + assert_true(params != null, 'constructor returned non-null value.'); + params = new URLSearchParams([['a', 'b'], ['c', 'd']]); + assert_equals(params.get("a"), "b"); + assert_equals(params.get("c"), "d"); + assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); }); + assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); }); +}, "Constructor with sequence of sequences of strings"); + +[ + { "input": {"+": "%C2"}, "output": [["+", "%C2"]], "name": "object with +" }, + { "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" }, + { "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }, + { "input": {"a\0b": "42", "c\uD83D": "23", "d\u1234": "foo"}, "output": [["a\0b", "42"], ["c\uFFFD", "23"], ["d\u1234", "foo"]], "name": "object with NULL, non-ASCII, and surrogate keys" } +].forEach((val) => { + test(() => { + let params = new URLSearchParams(val.input), + i = 0 + for (let param of params) { + assert_array_equals(param, val.output[i]) + i++ + } + }, "Construct with " + val.name) +}) + +test(() => { + params = new URLSearchParams() + params[Symbol.iterator] = function *() { + yield ["a", "b"] + } + let params2 = new URLSearchParams(params) + assert_equals(params2.get("a"), "b") +}, "Custom [Symbol.iterator]") +/* eslint-enable */ + +// Tests below are not from WPT. +function makeIterableFunc(array) { + return Object.assign(() => {}, { + [Symbol.iterator]() { + return array[Symbol.iterator](); + } + }); +} + +{ + const iterableError = /^TypeError: Query pairs must be iterable$/; + const tupleError = + /^TypeError: Each query pair must be an iterable \[name, value] tuple$/; + + let params; + params = new URLSearchParams(undefined); + assert.strictEqual(params.toString(), ''); + params = new URLSearchParams(null); + assert.strictEqual(params.toString(), ''); + params = new URLSearchParams( + makeIterableFunc([['key', 'val'], ['key2', 'val2']]) + ); + assert.strictEqual(params.toString(), 'key=val&key2=val2'); + params = new URLSearchParams( + makeIterableFunc([['key', 'val'], ['key2', 'val2']].map(makeIterableFunc)) + ); + assert.strictEqual(params.toString(), 'key=val&key2=val2'); + assert.throws(() => new URLSearchParams([[1]]), tupleError); + assert.throws(() => new URLSearchParams([[1, 2, 3]]), tupleError); + assert.throws(() => new URLSearchParams({ [Symbol.iterator]: 42 }), + iterableError); + assert.throws(() => new URLSearchParams([{}]), tupleError); + assert.throws(() => new URLSearchParams(['a']), tupleError); + assert.throws(() => new URLSearchParams([null]), tupleError); + assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]), + tupleError); +} + +{ + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + const toStringError = /^Error: toString$/; + const symbolError = /^TypeError: Cannot convert a Symbol value to a string$/; + + assert.throws(() => new URLSearchParams({ a: obj }), toStringError); + assert.throws(() => new URLSearchParams([['a', obj]]), toStringError); + assert.throws(() => new URLSearchParams(sym), symbolError); + assert.throws(() => new URLSearchParams({ [sym]: 'a' }), symbolError); + assert.throws(() => new URLSearchParams({ a: sym }), symbolError); + assert.throws(() => new URLSearchParams([[sym, 'a']]), symbolError); + assert.throws(() => new URLSearchParams([['a', sym]]), symbolError); +} diff --git a/test/parallel/test-whatwg-url-searchparams-delete.js b/test/parallel/test-whatwg-url-searchparams-delete.js new file mode 100644 index 00000000000000..bd52a13e9174b3 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-delete.js @@ -0,0 +1,92 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const { URL, URLSearchParams } = require('url'); +const { test, assert_equals, assert_true, assert_false } = + require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/70a0898763/url/urlsearchparams-delete.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + params.delete('a'); + assert_equals(params + '', 'c=d'); + params = new URLSearchParams('a=a&b=b&a=a&c=c'); + params.delete('a'); + assert_equals(params + '', 'b=b&c=c'); + params = new URLSearchParams('a=a&=&b=b&c=c'); + params.delete(''); + assert_equals(params + '', 'a=a&b=b&c=c'); + params = new URLSearchParams('a=a&null=null&b=b'); + params.delete(null); + assert_equals(params + '', 'a=a&b=b'); + params = new URLSearchParams('a=a&undefined=undefined&b=b'); + params.delete(undefined); + assert_equals(params + '', 'a=a&b=b'); +}, 'Delete basics'); + +test(function() { + var params = new URLSearchParams(); + params.append('first', 1); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"'); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no "first" name'); + params.append('first', 1); + params.append('first', 10); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no "first" name'); +}, 'Deleting appended multiple'); + +test(function() { + var url = new URL('http://example.com/?param1¶m2'); + url.searchParams.delete('param1'); + url.searchParams.delete('param2'); + assert_equals(url.href, 'http://example.com/', 'url.href does not have ?'); + assert_equals(url.search, '', 'url.search does not have ?'); +}, 'Deleting all params removes ? from URL'); + +test(function() { + var url = new URL('http://example.com/?'); + url.searchParams.delete('param1'); + assert_equals(url.href, 'http://example.com/', 'url.href does not have ?'); + assert_equals(url.search, '', 'url.search does not have ?'); +}, 'Removing non-existent param removes ? from URL'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.delete.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.delete(); + }, /^TypeError: The "name" argument must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.delete(obj), /^Error: toString$/); + assert.throws(() => params.delete(sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} + +// https://github.com/nodejs/node/issues/10480 +// Emptying searchParams should correctly update url's query +{ + const url = new URL('http://domain?var=1&var=2&var=3'); + for (const param of url.searchParams.keys()) { + url.searchParams.delete(param); + } + assert.strictEqual(url.searchParams.toString(), ''); + assert.strictEqual(url.search, ''); + assert.strictEqual(url.href, 'http://domain/'); +} diff --git a/test/parallel/test-whatwg-url-searchparams-entries.js b/test/parallel/test-whatwg-url-searchparams-entries.js new file mode 100644 index 00000000000000..4e73b92b517fd9 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-entries.js @@ -0,0 +1,34 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; + +// Tests below are not from WPT. +const params = new URLSearchParams('a=b&c=d'); +const entries = params.entries(); +assert.strictEqual(typeof entries[Symbol.iterator], 'function'); +assert.strictEqual(entries[Symbol.iterator](), entries); +assert.deepStrictEqual(entries.next(), { + value: ['a', 'b'], + done: false +}); +assert.deepStrictEqual(entries.next(), { + value: ['c', 'd'], + done: false +}); +assert.deepStrictEqual(entries.next(), { + value: undefined, + done: true +}); +assert.deepStrictEqual(entries.next(), { + value: undefined, + done: true +}); + +assert.throws(() => { + entries.next.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParamsIterator$/); +assert.throws(() => { + params.entries.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParams$/); diff --git a/test/parallel/test-whatwg-url-searchparams-foreach.js b/test/parallel/test-whatwg-url-searchparams-foreach.js new file mode 100644 index 00000000000000..06f21723a6cd2f --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-foreach.js @@ -0,0 +1,56 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const { URL, URLSearchParams } = require('url'); +const { test, assert_array_equals, assert_unreached } = + require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/a8b2b1e/url/urlsearchparams-foreach.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +var i; // Strict mode fix for WPT. +test(function() { + var params = new URLSearchParams('a=1&b=2&c=3'); + var keys = []; + var values = []; + params.forEach(function(value, key) { + keys.push(key); + values.push(value); + }); + assert_array_equals(keys, ['a', 'b', 'c']); + assert_array_equals(values, ['1', '2', '3']); +}, "ForEach Check"); + +test(function() { + let a = new URL("http://a.b/c?a=1&b=2&c=3&d=4"); + let b = a.searchParams; + var c = []; + for (i of b) { + a.search = "x=1&y=2&z=3"; + c.push(i); + } + assert_array_equals(c[0], ["a","1"]); + assert_array_equals(c[1], ["y","2"]); + assert_array_equals(c[2], ["z","3"]); +}, "For-of Check"); + +test(function() { + let a = new URL("http://a.b/c"); + let b = a.searchParams; + for (i of b) { + assert_unreached(i); + } +}, "empty"); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.forEach.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-get.js b/test/parallel/test-whatwg-url-searchparams-get.js new file mode 100644 index 00000000000000..b096a69a6071a0 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-get.js @@ -0,0 +1,55 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_equals, assert_true } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-get.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_equals(params.get('a'), 'b'); + assert_equals(params.get('c'), 'd'); + assert_equals(params.get('e'), null); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_equals(params.get('a'), 'b'); + params = new URLSearchParams('=b&c=d'); + assert_equals(params.get(''), 'b'); + params = new URLSearchParams('a=&c=d&a=e'); + assert_equals(params.get('a'), ''); +}, 'Get basics'); + +test(function() { + var params = new URLSearchParams('first=second&third&&'); + assert_true(params != null, 'constructor returned non-null value.'); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_equals(params.get('first'), 'second', 'Search params object has name "first" with value "second"'); + assert_equals(params.get('third'), '', 'Search params object has name "third" with the empty value.'); + assert_equals(params.get('fourth'), null, 'Search params object has no "fourth" name and value.'); +}, 'More get() basics'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.get.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.get(); + }, /^TypeError: The "name" argument must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.get(obj), /^Error: toString$/); + assert.throws(() => params.get(sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-getall.js b/test/parallel/test-whatwg-url-searchparams-getall.js new file mode 100644 index 00000000000000..acf5108459cf61 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-getall.js @@ -0,0 +1,60 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_equals, assert_true, assert_array_equals } = + require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-getall.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_array_equals(params.getAll('a'), ['b']); + assert_array_equals(params.getAll('c'), ['d']); + assert_array_equals(params.getAll('e'), []); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_array_equals(params.getAll('a'), ['b', 'e']); + params = new URLSearchParams('=b&c=d'); + assert_array_equals(params.getAll(''), ['b']); + params = new URLSearchParams('a=&c=d&a=e'); + assert_array_equals(params.getAll('a'), ['', 'e']); +}, 'getAll() basics'); + +test(function() { + var params = new URLSearchParams('a=1&a=2&a=3&a'); + assert_true(params.has('a'), 'Search params object has name "a"'); + var matches = params.getAll('a'); + assert_true(matches && matches.length == 4, 'Search params object has values for name "a"'); + assert_array_equals(matches, ['1', '2', '3', ''], 'Search params object has expected name "a" values'); + params.set('a', 'one'); + assert_equals(params.get('a'), 'one', 'Search params object has name "a" with value "one"'); + var matches = params.getAll('a'); + assert_true(matches && matches.length == 1, 'Search params object has values for name "a"'); + assert_array_equals(matches, ['one'], 'Search params object has expected name "a" values'); +}, 'getAll() multiples'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.getAll.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.getAll(); + }, /^TypeError: The "name" argument must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.getAll(obj), /^Error: toString$/); + assert.throws(() => params.getAll(sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-has.js b/test/parallel/test-whatwg-url-searchparams-has.js new file mode 100644 index 00000000000000..0fdd88af64c5af --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-has.js @@ -0,0 +1,58 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_false, assert_true } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-has.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + assert_true(params.has('a')); + assert_true(params.has('c')); + assert_false(params.has('e')); + params = new URLSearchParams('a=b&c=d&a=e'); + assert_true(params.has('a')); + params = new URLSearchParams('=b&c=d'); + assert_true(params.has('')); + params = new URLSearchParams('null=a'); + assert_true(params.has(null)); +}, 'Has basics'); + +test(function() { + var params = new URLSearchParams('a=b&c=d&&'); + params.append('first', 1); + params.append('first', 2); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_true(params.has('c'), 'Search params object has name "c"'); + assert_true(params.has('first'), 'Search params object has name "first"'); + assert_false(params.has('d'), 'Search params object has no name "d"'); + params.delete('first'); + assert_false(params.has('first'), 'Search params object has no name "first"'); +}, 'has() following delete()'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.has.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.has(); + }, /^TypeError: The "name" argument must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.has(obj), /^Error: toString$/); + assert.throws(() => params.has(sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-inspect.js b/test/parallel/test-whatwg-url-searchparams-inspect.js new file mode 100644 index 00000000000000..163fa185ede58d --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-inspect.js @@ -0,0 +1,29 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const util = require('util'); +const URLSearchParams = require('url').URLSearchParams; + +// Tests below are not from WPT. +const sp = new URLSearchParams('?a=a&b=b&b=c'); +assert.strictEqual(util.inspect(sp), + "URLSearchParams { 'a' => 'a', 'b' => 'b', 'b' => 'c' }"); +assert.strictEqual(util.inspect(sp.keys()), + "URLSearchParamsIterator { 'a', 'b', 'b' }"); +assert.strictEqual(util.inspect(sp.values()), + "URLSearchParamsIterator { 'a', 'b', 'c' }"); +assert.strictEqual(util.inspect(sp.keys(), { breakLength: 1 }), + "URLSearchParamsIterator {\n 'a',\n 'b',\n 'b' }"); + +const iterator = sp.entries(); +assert.strictEqual(util.inspect(iterator), + "URLSearchParamsIterator { [ 'a', 'a' ], [ 'b', 'b' ], " + + "[ 'b', 'c' ] }"); +iterator.next(); +assert.strictEqual(util.inspect(iterator), + "URLSearchParamsIterator { [ 'b', 'b' ], [ 'b', 'c' ] }"); +iterator.next(); +iterator.next(); +assert.strictEqual(util.inspect(iterator), + 'URLSearchParamsIterator { }'); diff --git a/test/parallel/test-whatwg-url-searchparams-keys.js b/test/parallel/test-whatwg-url-searchparams-keys.js new file mode 100644 index 00000000000000..af044a260874ac --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-keys.js @@ -0,0 +1,35 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; + +// Tests below are not from WPT. +const params = new URLSearchParams('a=b&c=d'); +const keys = params.keys(); + +assert.strictEqual(typeof keys[Symbol.iterator], 'function'); +assert.strictEqual(keys[Symbol.iterator](), keys); +assert.deepStrictEqual(keys.next(), { + value: 'a', + done: false +}); +assert.deepStrictEqual(keys.next(), { + value: 'c', + done: false +}); +assert.deepStrictEqual(keys.next(), { + value: undefined, + done: true +}); +assert.deepStrictEqual(keys.next(), { + value: undefined, + done: true +}); + +assert.throws(() => { + keys.next.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParamsIterator$/); +assert.throws(() => { + params.keys.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParams$/); diff --git a/test/parallel/test-whatwg-url-searchparams-set.js b/test/parallel/test-whatwg-url-searchparams-set.js new file mode 100644 index 00000000000000..8a6c31bbe66487 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-set.js @@ -0,0 +1,59 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_equals, assert_true } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-set.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams('a=b&c=d'); + params.set('a', 'B'); + assert_equals(params + '', 'a=B&c=d'); + params = new URLSearchParams('a=b&c=d&a=e'); + params.set('a', 'B'); + assert_equals(params + '', 'a=B&c=d') + params.set('e', 'f'); + assert_equals(params + '', 'a=B&c=d&e=f') +}, 'Set basics'); + +test(function() { + var params = new URLSearchParams('a=1&a=2&a=3'); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"'); + params.set('first', 4); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '1', 'Search params object has name "a" with value "1"'); + params.set('a', 4); + assert_true(params.has('a'), 'Search params object has name "a"'); + assert_equals(params.get('a'), '4', 'Search params object has name "a" with value "4"'); +}, 'URLSearchParams.set'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.set.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); + assert.throws(() => { + params.set('a'); + }, /^TypeError: The "name" and "value" arguments must be specified$/); + + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + assert.throws(() => params.append(obj, 'b'), /^Error: toString$/); + assert.throws(() => params.append('a', obj), /^Error: toString$/); + assert.throws(() => params.append(sym, 'b'), + /^TypeError: Cannot convert a Symbol value to a string$/); + assert.throws(() => params.append('a', sym), + /^TypeError: Cannot convert a Symbol value to a string$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-sort.js b/test/parallel/test-whatwg-url-searchparams-sort.js new file mode 100644 index 00000000000000..1122f08dcc0434 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-sort.js @@ -0,0 +1,105 @@ +'use strict'; + +require('../common'); +const { URL, URLSearchParams } = require('url'); +const { test, assert_equals, assert_array_equals } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/70a0898763/url/urlsearchparams-sort.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +[ + { + "input": "z=b&a=b&z=a&a=a", + "output": [["a", "b"], ["a", "a"], ["z", "b"], ["z", "a"]] + }, + { + "input": "\uFFFD=x&\uFFFC&\uFFFD=a", + "output": [["\uFFFC", ""], ["\uFFFD", "x"], ["\uFFFD", "a"]] + }, + { + "input": "ffi&🌈", // 🌈 > code point, but < code unit because two code units + "output": [["🌈", ""], ["ffi", ""]] + }, + { + "input": "é&e\uFFFD&e\u0301", + "output": [["e\u0301", ""], ["e\uFFFD", ""], ["é", ""]] + }, + { + "input": "z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g", + "output": [["a", "a"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["a", "f"], ["a", "g"], ["z", "z"], ["z", "y"], ["z", "x"], ["z", "w"], ["z", "v"], ["z", "u"], ["z", "t"]] + } +].forEach((val) => { + test(() => { + let params = new URLSearchParams(val.input), + i = 0 + params.sort() + for(let param of params) { + assert_array_equals(param, val.output[i]) + i++ + } + }, `Parse and sort: ${val.input}`) + + test(() => { + let url = new URL(`?${val.input}`, "https://example/") + url.searchParams.sort() + let params = new URLSearchParams(url.search), + i = 0 + for(let param of params) { + assert_array_equals(param, val.output[i]) + i++ + } + }, `URL parse and sort: ${val.input}`) +}) + +test(function() { + const url = new URL("http://example.com/?") + url.searchParams.sort() + assert_equals(url.href, "http://example.com/") + assert_equals(url.search, "") +}, "Sorting non-existent params removes ? from URL") +/* eslint-enable */ + +// Tests below are not from WPT. + +// Test bottom-up iterative stable merge sort +const tests = [{ input: '', output: [] }]; +const pairs = []; +for (let i = 10; i < 100; i++) { + pairs.push([`a${i}`, 'b']); + tests[0].output.push([`a${i}`, 'b']); +} +tests[0].input = pairs.sort(() => Math.random() > 0.5) + .map((pair) => pair.join('=')).join('&'); + +tests.push( + { + 'input': 'z=a&=b&c=d', + 'output': [['', 'b'], ['c', 'd'], ['z', 'a']] + } +); + +tests.forEach((val) => { + test(() => { + const params = new URLSearchParams(val.input); + let i = 0; + params.sort(); + for (const param of params) { + assert_array_equals(param, val.output[i]); + i++; + } + }, `Parse and sort: ${val.input}`); + + test(() => { + const url = new URL(`?${val.input}`, 'https://example/'); + url.searchParams.sort(); + const params = new URLSearchParams(url.search); + let i = 0; + for (const param of params) { + assert_array_equals(param, val.output[i]); + i++; + } + }, `URL parse and sort: ${val.input}`); +}); diff --git a/test/parallel/test-whatwg-url-searchparams-stringifier.js b/test/parallel/test-whatwg-url-searchparams-stringifier.js new file mode 100644 index 00000000000000..c355a2c9a9c29c --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-stringifier.js @@ -0,0 +1,132 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; +const { test, assert_equals } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/urlsearchparams-stringifier.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b c'); + assert_equals(params + '', 'a=b+c'); + params.delete('a'); + params.append('a b', 'c'); + assert_equals(params + '', 'a+b=c'); +}, 'Serialize space'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', ''); + assert_equals(params + '', 'a='); + params.append('a', ''); + assert_equals(params + '', 'a=&a='); + params.append('', 'b'); + assert_equals(params + '', 'a=&a=&=b'); + params.append('', ''); + assert_equals(params + '', 'a=&a=&=b&='); + params.append('', ''); + assert_equals(params + '', 'a=&a=&=b&=&='); +}, 'Serialize empty value'); + +test(function() { + var params = new URLSearchParams(); + params.append('', 'b'); + assert_equals(params + '', '=b'); + params.append('', 'b'); + assert_equals(params + '', '=b&=b'); +}, 'Serialize empty name'); + +test(function() { + var params = new URLSearchParams(); + params.append('', ''); + assert_equals(params + '', '='); + params.append('', ''); + assert_equals(params + '', '=&='); +}, 'Serialize empty name and value'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b+c'); + assert_equals(params + '', 'a=b%2Bc'); + params.delete('a'); + params.append('a+b', 'c'); + assert_equals(params + '', 'a%2Bb=c'); +}, 'Serialize +'); + +test(function() { + var params = new URLSearchParams(); + params.append('=', 'a'); + assert_equals(params + '', '%3D=a'); + params.append('b', '='); + assert_equals(params + '', '%3D=a&b=%3D'); +}, 'Serialize ='); + +test(function() { + var params = new URLSearchParams(); + params.append('&', 'a'); + assert_equals(params + '', '%26=a'); + params.append('b', '&'); + assert_equals(params + '', '%26=a&b=%26'); +}, 'Serialize &'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', '*-._'); + assert_equals(params + '', 'a=*-._'); + params.delete('a'); + params.append('*-._', 'c'); + assert_equals(params + '', '*-._=c'); +}, 'Serialize *-._'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b%c'); + assert_equals(params + '', 'a=b%25c'); + params.delete('a'); + params.append('a%b', 'c'); + assert_equals(params + '', 'a%25b=c'); +}, 'Serialize %'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b\0c'); + assert_equals(params + '', 'a=b%00c'); + params.delete('a'); + params.append('a\0b', 'c'); + assert_equals(params + '', 'a%00b=c'); +}, 'Serialize \\0'); + +test(function() { + var params = new URLSearchParams(); + params.append('a', 'b\uD83D\uDCA9c'); + assert_equals(params + '', 'a=b%F0%9F%92%A9c'); + params.delete('a'); + params.append('a\uD83D\uDCA9b', 'c'); + assert_equals(params + '', 'a%F0%9F%92%A9b=c'); +}, 'Serialize \uD83D\uDCA9'); // Unicode Character 'PILE OF POO' (U+1F4A9) + +test(function() { + var params; + params = new URLSearchParams('a=b&c=d&&e&&'); + assert_equals(params.toString(), 'a=b&c=d&e='); + params = new URLSearchParams('a = b &a=b&c=d%20'); + assert_equals(params.toString(), 'a+=+b+&a=b&c=d+'); + // The lone '=' _does_ survive the roundtrip. + params = new URLSearchParams('a=&a=b'); + assert_equals(params.toString(), 'a=&a=b'); +}, 'URLSearchParams.toString'); +/* eslint-enable */ + +// Tests below are not from WPT. +{ + const params = new URLSearchParams(); + assert.throws(() => { + params.toString.call(undefined); + }, /^TypeError: Value of "this" must be of type URLSearchParams$/); +} diff --git a/test/parallel/test-whatwg-url-searchparams-values.js b/test/parallel/test-whatwg-url-searchparams-values.js new file mode 100644 index 00000000000000..2775231b8bda5d --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-values.js @@ -0,0 +1,35 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URLSearchParams = require('url').URLSearchParams; + +// Tests below are not from WPT. +const params = new URLSearchParams('a=b&c=d'); +const values = params.values(); + +assert.strictEqual(typeof values[Symbol.iterator], 'function'); +assert.strictEqual(values[Symbol.iterator](), values); +assert.deepStrictEqual(values.next(), { + value: 'b', + done: false +}); +assert.deepStrictEqual(values.next(), { + value: 'd', + done: false +}); +assert.deepStrictEqual(values.next(), { + value: undefined, + done: true +}); +assert.deepStrictEqual(values.next(), { + value: undefined, + done: true +}); + +assert.throws(() => { + values.next.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParamsIterator$/); +assert.throws(() => { + params.values.call(undefined); +}, /^TypeError: Value of "this" must be of type URLSearchParams$/); diff --git a/test/parallel/test-whatwg-url-searchparams.js b/test/parallel/test-whatwg-url-searchparams.js new file mode 100644 index 00000000000000..b6861273e71a0e --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams.js @@ -0,0 +1,106 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const { URL, URLSearchParams } = require('url'); +const fixtures = require('../common/fixtures'); + +// Tests below are not from WPT. +const serialized = 'a=a&a=1&a=true&a=undefined&a=null&a=%EF%BF%BD' + + '&a=%EF%BF%BD&a=%F0%9F%98%80&a=%EF%BF%BD%EF%BF%BD' + + '&a=%5Bobject+Object%5D'; +const values = ['a', 1, true, undefined, null, '\uD83D', '\uDE00', + '\uD83D\uDE00', '\uDE00\uD83D', {}]; +const normalizedValues = ['a', '1', 'true', 'undefined', 'null', '\uFFFD', + '\uFFFD', '\uD83D\uDE00', '\uFFFD\uFFFD', + '[object Object]']; + +const m = new URL('http://example.org'); +const sp = m.searchParams; + +assert(sp); +assert.strictEqual(sp.toString(), ''); +assert.strictEqual(m.search, ''); + +assert(!sp.has('a')); +values.forEach((i) => sp.set('a', i)); +assert(sp.has('a')); +assert.strictEqual(sp.get('a'), '[object Object]'); +sp.delete('a'); +assert(!sp.has('a')); + +m.search = ''; +assert.strictEqual(sp.toString(), ''); + +values.forEach((i) => sp.append('a', i)); +assert(sp.has('a')); +assert.strictEqual(sp.getAll('a').length, values.length); +assert.strictEqual(sp.get('a'), 'a'); + +assert.strictEqual(sp.toString(), serialized); + +assert.strictEqual(m.search, `?${serialized}`); + +assert.strictEqual(sp[Symbol.iterator], sp.entries); + +let key, val; +let n = 0; +for ([key, val] of sp) { + assert.strictEqual(key, 'a', n); + assert.strictEqual(val, normalizedValues[n], n); + n++; +} +n = 0; +for (key of sp.keys()) { + assert.strictEqual(key, 'a', n); + n++; +} +n = 0; +for (val of sp.values()) { + assert.strictEqual(val, normalizedValues[n], n); + n++; +} +n = 0; +sp.forEach(function(val, key, obj) { + assert.strictEqual(this, undefined, n); + assert.strictEqual(key, 'a', n); + assert.strictEqual(val, normalizedValues[n], n); + assert.strictEqual(obj, sp, n); + n++; +}); +sp.forEach(function() { + assert.strictEqual(this, m); +}, m); + +{ + const callbackErr = /^TypeError: Callback must be a function$/; + assert.throws(() => sp.forEach(), callbackErr); + assert.throws(() => sp.forEach(1), callbackErr); +} + +m.search = '?a=a&b=b'; +assert.strictEqual(sp.toString(), 'a=a&b=b'); + +const tests = require(fixtures.path('url-searchparams.js')); + +for (const [input, expected, parsed] of tests) { + if (input[0] !== '?') { + const sp = new URLSearchParams(input); + assert.strictEqual(String(sp), expected); + assert.deepStrictEqual(Array.from(sp), parsed); + + m.search = input; + assert.strictEqual(String(m.searchParams), expected); + assert.deepStrictEqual(Array.from(m.searchParams), parsed); + } + + { + const sp = new URLSearchParams(`?${input}`); + assert.strictEqual(String(sp), expected); + assert.deepStrictEqual(Array.from(sp), parsed); + + m.search = `?${input}`; + assert.strictEqual(String(m.searchParams), expected); + assert.deepStrictEqual(Array.from(m.searchParams), parsed); + } +} diff --git a/test/parallel/test-whatwg-url-setters.js b/test/parallel/test-whatwg-url-setters.js new file mode 100644 index 00000000000000..9a25de59e7e55f --- /dev/null +++ b/test/parallel/test-whatwg-url-setters.js @@ -0,0 +1,126 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const assert = require('assert'); +const URL = require('url').URL; +const { test, assert_equals } = require('../common/wpt'); +const fixtures = require('../common/fixtures'); + +const additionalTestCases = + require(fixtures.path('url-setter-tests-additional.js')); + +const request = { + response: require(fixtures.path('url-setter-tests')) +}; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/8791bed/url/url-setters.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +function startURLSettersTests() { +// var setup = async_test("Loading data…") +// setup.step(function() { +// var request = new XMLHttpRequest() +// request.open("GET", "setters_tests.json") +// request.send() +// request.responseType = "json" +// request.onload = setup.step_func(function() { + runURLSettersTests(request.response) +// setup.done() +// }) +// }) +} + +function runURLSettersTests(all_test_cases) { + for (var attribute_to_be_set in all_test_cases) { + if (attribute_to_be_set == "comment") { + continue; + } + var test_cases = all_test_cases[attribute_to_be_set]; + for(var i = 0, l = test_cases.length; i < l; i++) { + var test_case = test_cases[i]; + var name = `Setting <${test_case.href}>.${attribute_to_be_set}` + + ` = '${test_case.new_value}'`; + if ("comment" in test_case) { + name += ` ${test_case.comment}`; + } + test(function() { + var url = new URL(test_case.href); + url[attribute_to_be_set] = test_case.new_value; + for (var attribute in test_case.expected) { + assert_equals(url[attribute], test_case.expected[attribute]) + } + }, `URL: ${name}`); + // test(function() { + // var url = document.createElement("a"); + // url.href = test_case.href; + // url[attribute_to_be_set] = test_case.new_value; + // for (var attribute in test_case.expected) { + // assert_equals(url[attribute], test_case.expected[attribute]) + // } + // }, ": " + name) + // test(function() { + // var url = document.createElement("area"); + // url.href = test_case.href; + // url[attribute_to_be_set] = test_case.new_value; + // for (var attribute in test_case.expected) { + // assert_equals(url[attribute], test_case.expected[attribute]) + // } + // }, ": " + name) + } + } +} + +startURLSettersTests() +/* eslint-enable */ + +// Tests below are not from WPT. + +{ + for (const attributeToBeSet in additionalTestCases) { + if (attributeToBeSet === 'comment') { + continue; + } + const testCases = additionalTestCases[attributeToBeSet]; + for (const testCase of testCases) { + let name = `Setting <${testCase.href}>.${attributeToBeSet}` + + ` = "${testCase.new_value}"`; + if ('comment' in testCase) { + name += ` ${testCase.comment}`; + } + test(function() { + const url = new URL(testCase.href); + url[attributeToBeSet] = testCase.new_value; + for (const attribute in testCase.expected) { + assert_equals(url[attribute], testCase.expected[attribute]); + } + }, `URL: ${name}`); + } + } +} + +{ + const url = new URL('http://example.com/'); + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + for (const name of Reflect.ownKeys(Object.getPrototypeOf(url))) { + if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(url), name).set) { + assert.throws(() => url[name] = obj, + /^Error: toString$/, + `url.${name} = { toString() { throw ... } }`); + assert.throws(() => url[name] = sym, + /^TypeError: Cannot convert a Symbol value to a string$/, + `url.${name} = ${String(sym)}`); + } + } +} diff --git a/test/parallel/test-whatwg-url-toascii.js b/test/parallel/test-whatwg-url-toascii.js new file mode 100644 index 00000000000000..c85b092c1d250c --- /dev/null +++ b/test/parallel/test-whatwg-url-toascii.js @@ -0,0 +1,85 @@ +'use strict'; +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const fixtures = require('../common/fixtures'); +const { URL } = require('url'); +const { test, assert_equals, assert_throws } = require('../common/wpt'); + +const request = { + response: require(fixtures.path('url-toascii')) +}; + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/4839a0a804/url/toascii.window.js + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +// async_test(t => { +// const request = new XMLHttpRequest() +// request.open("GET", "toascii.json") +// request.send() +// request.responseType = "json" +// request.onload = t.step_func_done(() => { + runTests(request.response) +// }) +// }, "Loading data…") + +function makeURL(type, input) { + input = "https://" + input + "/x" + if(type === "url") { + return new URL(input) + } else { + const url = document.createElement(type) + url.href = input + return url + } +} + +function runTests(tests) { + for(var i = 0, l = tests.length; i < l; i++) { + let hostTest = tests[i] + if (typeof hostTest === "string") { + continue // skip comments + } + const typeName = { "url": "URL", "a": "", "area": "" } + // ;["url", "a", "area"].forEach((type) => { + ;["url"].forEach((type) => { + test(() => { + if(hostTest.output !== null) { + const url = makeURL("url", hostTest.input) + assert_equals(url.host, hostTest.output) + assert_equals(url.hostname, hostTest.output) + assert_equals(url.pathname, "/x") + assert_equals(url.href, "https://" + hostTest.output + "/x") + } else { + if(type === "url") { + assert_throws(new TypeError, () => makeURL("url", hostTest.input)) + } else { + const url = makeURL(type, hostTest.input) + assert_equals(url.host, "") + assert_equals(url.hostname, "") + assert_equals(url.pathname, "") + assert_equals(url.href, "https://" + hostTest.input + "/x") + } + } + }, hostTest.input + " (using " + typeName[type] + ")") + ;["host", "hostname"].forEach((val) => { + test(() => { + const url = makeURL(type, "x") + url[val] = hostTest.input + if(hostTest.output !== null) { + assert_equals(url[val], hostTest.output) + } else { + assert_equals(url[val], "x") + } + }, hostTest.input + " (using " + typeName[type] + "." + val + ")") + }) + }) + } +} +/* eslint-enable */ diff --git a/test/parallel/test-whatwg-url-tojson.js b/test/parallel/test-whatwg-url-tojson.js new file mode 100644 index 00000000000000..8e9a30c7e017e4 --- /dev/null +++ b/test/parallel/test-whatwg-url-tojson.js @@ -0,0 +1,17 @@ +'use strict'; + +require('../common'); +const URL = require('url').URL; +const { test, assert_equals } = require('../common/wpt'); + +/* The following tests are copied from WPT. Modifications to them should be + upstreamed first. Refs: + https://github.com/w3c/web-platform-tests/blob/02585db/url/url-tojson.html + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +/* eslint-disable */ +test(() => { + const a = new URL("https://example.com/") + assert_equals(JSON.stringify(a), "\"https://example.com/\"") +}) +/* eslint-enable */ diff --git a/test/parallel/test-whatwg-url-tostringtag.js b/test/parallel/test-whatwg-url-tostringtag.js new file mode 100644 index 00000000000000..689056fd238dda --- /dev/null +++ b/test/parallel/test-whatwg-url-tostringtag.js @@ -0,0 +1,32 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +// Tests below are not from WPT. +const toString = Object.prototype.toString; + +const url = new URL('http://example.org'); +const sp = url.searchParams; +const spIterator = sp.entries(); + +const test = [ + [url, 'URL'], + [sp, 'URLSearchParams'], + [spIterator, 'URLSearchParamsIterator'], + // Web IDL spec says we have to return 'URLPrototype', but it is too + // expensive to implement; therefore, use Chrome's behavior for now, until + // spec is changed. + [Object.getPrototypeOf(url), 'URL'], + [Object.getPrototypeOf(sp), 'URLSearchParams'], + [Object.getPrototypeOf(spIterator), 'URLSearchParamsIterator'], +]; + +test.forEach(([obj, expected]) => { + assert.strictEqual(obj[Symbol.toStringTag], expected, + `${obj[Symbol.toStringTag]} !== ${expected}`); + const str = toString.call(obj); + assert.strictEqual(str, `[object ${expected}]`, + `${str} !== [object ${expected}]`); +}); diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status index 8467864395ff66..d8ef1ad44a4624 100644 --- a/test/sequential/sequential.status +++ b/test/sequential/sequential.status @@ -7,6 +7,7 @@ prefix sequential [true] # This section applies to all platforms [$system==win32] +test-inspector-stop-profile-after-done: PASS, FLAKY [$system==linux] diff --git a/test/sequential/test-child-process-pass-fd.js b/test/sequential/test-child-process-pass-fd.js index 73e469cdede2be..98832c57a3df67 100644 --- a/test/sequential/test-child-process-pass-fd.js +++ b/test/sequential/test-child-process-pass-fd.js @@ -18,26 +18,42 @@ const { fork } = require('child_process'); const net = require('net'); const N = 80; +let messageCallbackCount = 0; -if (process.argv[2] !== 'child') { - for (let i = 0; i < N; ++i) { - const worker = fork(__filename, ['child']); - worker.once('message', common.mustCall((msg, handle) => { - assert.strictEqual(msg, 'handle'); - assert.ok(handle); - worker.send('got'); - - let recvData = ''; - handle.on('data', common.mustCall((data) => { - recvData += data; - })); +function forkWorker() { + const messageCallback = (msg, handle) => { + messageCallbackCount++; + assert.strictEqual(msg, 'handle'); + assert.ok(handle); + worker.send('got'); - handle.on('end', () => { - assert.strictEqual(recvData, 'hello'); - worker.kill(); - }); + let recvData = ''; + handle.on('data', common.mustCall((data) => { + recvData += data; })); + + handle.on('end', () => { + assert.strictEqual(recvData, 'hello'); + worker.kill(); + }); + }; + + const worker = fork(__filename, ['child']); + worker.on('error', (err) => { + if (/\bEAGAIN\b/.test(err.message)) { + forkWorker(); + return; + } + throw err; + }); + worker.once('message', messageCallback); +} + +if (process.argv[2] !== 'child') { + for (let i = 0; i < N; ++i) { + forkWorker(); } + process.on('exit', () => { assert.strictEqual(messageCallbackCount, N); }); } else { let socket; let cbcalls = 0; diff --git a/test/sequential/test-http-keep-alive-large-write.js b/test/sequential/test-http-keep-alive-large-write.js new file mode 100644 index 00000000000000..2cdf539e76b2fc --- /dev/null +++ b/test/sequential/test-http-keep-alive-large-write.js @@ -0,0 +1,80 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// This test assesses whether long-running writes can complete +// or timeout because the socket is not aware that the backing +// stream is still writing. +// To simulate a slow client, we write a really large chunk and +// then proceed through the following cycle: +// 1) Receive first 'data' event and record currently written size +// 2) Once we've read up to currently written size recorded above, +// we pause the stream and wait longer than the server timeout +// 3) Socket.prototype._onTimeout triggers and should confirm +// that the backing stream is still active and writing +// 4) Our timer fires, we resume the socket and start at 1) + +const minReadSize = 250000; +const serverTimeout = common.platformTimeout(500); +let offsetTimeout = common.platformTimeout(100); +let serverConnectionHandle; +let writeSize = 3000000; +let didReceiveData = false; +// this represents each cycles write size, where the cycle consists +// of `write > read > _onTimeout` +let currentWriteSize = 0; + +const server = http.createServer(common.mustCall((req, res) => { + const content = Buffer.alloc(writeSize, 0x44); + + res.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Length': content.length.toString(), + 'Vary': 'Accept-Encoding' + }); + + serverConnectionHandle = res.socket._handle; + res.write(content); + res.end(); +})); +server.setTimeout(serverTimeout); +server.on('timeout', () => { + assert.strictEqual(didReceiveData, false, 'Should not timeout'); +}); + +server.listen(0, common.mustCall(() => { + http.get({ + path: '/', + port: server.address().port + }, common.mustCall((res) => { + const resume = () => res.resume(); + let receivedBufferLength = 0; + let firstReceivedAt; + res.on('data', common.mustCallAtLeast((buf) => { + if (receivedBufferLength === 0) { + currentWriteSize = Math.max( + minReadSize, + writeSize - serverConnectionHandle.writeQueueSize + ); + didReceiveData = false; + firstReceivedAt = Date.now(); + } + receivedBufferLength += buf.length; + if (receivedBufferLength >= currentWriteSize) { + didReceiveData = true; + writeSize = serverConnectionHandle.writeQueueSize; + receivedBufferLength = 0; + res.pause(); + setTimeout( + resume, + serverTimeout + offsetTimeout - (Date.now() - firstReceivedAt) + ); + offsetTimeout = 0; + } + }, 1)); + res.on('end', common.mustCall(() => { + server.close(); + })); + })); +})); diff --git a/test/sequential/test-http-server-consumed-timeout.js b/test/sequential/test-http-server-consumed-timeout.js index abe100c1907e0f..56217226503be1 100644 --- a/test/sequential/test-http-server-consumed-timeout.js +++ b/test/sequential/test-http-server-consumed-timeout.js @@ -1,6 +1,8 @@ 'use strict'; const common = require('../common'); + +const assert = require('assert'); const http = require('http'); let time = Date.now(); @@ -16,7 +18,7 @@ const server = http.createServer((req, res) => { req.setTimeout(TIMEOUT, () => { if (!intervalWasInvoked) return common.skip('interval was not invoked quickly enough for test'); - common.fail('Request timeout should not fire'); + assert.fail('Request timeout should not fire'); }); req.resume(); diff --git a/test/sequential/test-https-keep-alive-large-write.js b/test/sequential/test-https-keep-alive-large-write.js new file mode 100644 index 00000000000000..88468dc03fc99b --- /dev/null +++ b/test/sequential/test-https-keep-alive-large-write.js @@ -0,0 +1,87 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const https = require('https'); + +// This test assesses whether long-running writes can complete +// or timeout because the socket is not aware that the backing +// stream is still writing. +// To simulate a slow client, we write a really large chunk and +// then proceed through the following cycle: +// 1) Receive first 'data' event and record currently written size +// 2) Once we've read up to currently written size recorded above, +// we pause the stream and wait longer than the server timeout +// 3) Socket.prototype._onTimeout triggers and should confirm +// that the backing stream is still active and writing +// 4) Our timer fires, we resume the socket and start at 1) + +const minReadSize = 250000; +const serverTimeout = common.platformTimeout(500); +let offsetTimeout = common.platformTimeout(100); +let serverConnectionHandle; +let writeSize = 2000000; +let didReceiveData = false; +// this represents each cycles write size, where the cycle consists +// of `write > read > _onTimeout` +let currentWriteSize = 0; + +const server = https.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem') +}, common.mustCall((req, res) => { + const content = Buffer.alloc(writeSize, 0x44); + + res.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Length': content.length.toString(), + 'Vary': 'Accept-Encoding' + }); + + serverConnectionHandle = res.socket._handle; + res.write(content); + res.end(); +})); +server.setTimeout(serverTimeout); +server.on('timeout', () => { + assert.strictEqual(didReceiveData, false, 'Should not timeout'); +}); + +server.listen(0, common.mustCall(() => { + https.get({ + path: '/', + port: server.address().port, + rejectUnauthorized: false + }, common.mustCall((res) => { + const resume = () => res.resume(); + let receivedBufferLength = 0; + let firstReceivedAt; + res.on('data', common.mustCallAtLeast((buf) => { + if (receivedBufferLength === 0) { + currentWriteSize = Math.max( + minReadSize, + writeSize - serverConnectionHandle.writeQueueSize + ); + didReceiveData = false; + firstReceivedAt = Date.now(); + } + receivedBufferLength += buf.length; + if (receivedBufferLength >= currentWriteSize) { + didReceiveData = true; + writeSize = serverConnectionHandle.writeQueueSize; + receivedBufferLength = 0; + res.pause(); + setTimeout( + resume, + serverTimeout + offsetTimeout - (Date.now() - firstReceivedAt) + ); + offsetTimeout = 0; + } + }, 1)); + res.on('end', common.mustCall(() => { + server.close(); + })); + })); +})); diff --git a/test/sequential/test-stream-writable-clear-buffer.js b/test/sequential/test-stream-writable-clear-buffer.js new file mode 100644 index 00000000000000..dc859e3fb6b362 --- /dev/null +++ b/test/sequential/test-stream-writable-clear-buffer.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common'); +const Stream = require('stream'); +// This test ensures that the _writeableState.bufferedRequestCount and +// the actual buffered request count are the same +const assert = require('assert'); + +class StreamWritable extends Stream.Writable { + constructor() { + super({ objectMode: true }); + } + + // We need a timeout like on the original issue thread + // otherwise the code will never reach our test case + // this means this should go on the sequential folder. + _write(chunk, encoding, cb) { + setTimeout(cb, common.platformTimeout(10)); + } +} + +const testStream = new StreamWritable(); +testStream.cork(); + +for (let i = 1; i <= 5; i++) { + testStream.write(i, function() { + assert.strictEqual( + testStream._writableState.bufferedRequestCount, + testStream._writableState.getBuffer().length, + 'bufferedRequestCount variable is different from the actual length of' + + ' the buffer'); + }); +} + +testStream.end(); diff --git a/tools/configure.d/nodedownload.py b/tools/configure.d/nodedownload.py index 5cf8e0dbd690e1..3f4bc090f71bdd 100644 --- a/tools/configure.d/nodedownload.py +++ b/tools/configure.d/nodedownload.py @@ -60,7 +60,7 @@ def unpack(packedfile, parent_path): icuzip.extractall(parent_path) return parent_path elif tarfile.is_tarfile(packedfile): - with tarfile.TarFile.open(packedfile, 'r') as icuzip: + with contextlib.closing(tarfile.TarFile.open(packedfile, 'r')) as icuzip: print ' Extracting tarfile: %s' % packedfile icuzip.extractall(parent_path) return parent_path diff --git a/tools/doc/generate.js b/tools/doc/generate.js index 1b04be832d954c..d6bf2d5e378979 100644 --- a/tools/doc/generate.js +++ b/tools/doc/generate.js @@ -9,13 +9,13 @@ const fs = require('fs'); const args = process.argv.slice(2); let format = 'json'; let template = null; -let inputFile = null; +let filename = null; let nodeVersion = null; let analytics = null; args.forEach(function(arg) { if (!arg.startsWith('--')) { - inputFile = arg; + filename = arg; } else if (arg.startsWith('--format=')) { format = arg.replace(/^--format=/, ''); } else if (arg.startsWith('--template=')) { @@ -29,42 +29,33 @@ args.forEach(function(arg) { nodeVersion = nodeVersion || process.version; -if (!inputFile) { +if (!filename) { throw new Error('No input file specified'); } -console.error('Input file = %s', inputFile); -fs.readFile(inputFile, 'utf8', function(er, input) { +console.error('Input file = %s', filename); +fs.readFile(filename, 'utf8', (er, input) => { if (er) throw er; // process the input for @include lines - processIncludes(inputFile, input, next); + processIncludes(filename, input, next); }); function next(er, input) { if (er) throw er; switch (format) { case 'json': - require('./json.js')(input, inputFile, function(er, obj) { + require('./json.js')(input, filename, (er, obj) => { console.log(JSON.stringify(obj, null, 2)); if (er) throw er; }); break; case 'html': - require('./html.js')( - { - input: input, - filename: inputFile, - template: template, - nodeVersion: nodeVersion, - analytics: analytics, - }, - - function(er, html) { - if (er) throw er; - console.log(html); - } - ); + require('./html')({ input, filename, template, nodeVersion, analytics }, + (err, html) => { + if (err) throw err; + console.log(html); + }); break; default: diff --git a/tools/doc/html.js b/tools/doc/html.js index 730031471fcc04..9ce0fdd5080630 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -103,9 +103,7 @@ function toID(filename) { * opts: lexed, filename, template, nodeVersion. */ function render(opts, cb) { - var lexed = opts.lexed; - var filename = opts.filename; - var template = opts.template; + var { lexed, filename, template } = opts; var nodeVersion = opts.nodeVersion || process.version; // get the section diff --git a/tools/doc/preprocess.js b/tools/doc/preprocess.js index 9d56644a67acb5..59ada6c45877a7 100644 --- a/tools/doc/preprocess.js +++ b/tools/doc/preprocess.js @@ -10,11 +10,7 @@ var includeData = {}; function preprocess(inputFile, input, cb) { input = stripComments(input); - processIncludes(inputFile, input, function(err, data) { - if (err) return cb(err); - - cb(null, data); - }); + processIncludes(inputFile, input, cb); } function stripComments(input) { diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js index c2bca80641152e..ee50d48f61d45e 100644 --- a/tools/doc/type-parser.js +++ b/tools/doc/type-parser.js @@ -38,6 +38,8 @@ const typeMap = { 'http.IncomingMessage': 'http.html#http_class_http_incomingmessage', 'http.Server': 'http.html#http_class_http_server', 'http.ServerResponse': 'http.html#http_class_http_serverresponse', + 'URL': 'url.html#url_the_whatwg_url_api', + 'URLSearchParams': 'url.html#url_class_urlsearchparams' }; const arrayPart = /(?:\[])+$/; diff --git a/tools/eslint-rules/lowercase-name-for-primitive.js b/tools/eslint-rules/lowercase-name-for-primitive.js new file mode 100644 index 00000000000000..d3a5243c37895f --- /dev/null +++ b/tools/eslint-rules/lowercase-name-for-primitive.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Check that TypeError[ERR_INVALID_ARG_TYPE] uses + * lowercase for primitive types + * @author Weijia Wang + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const astSelector = 'NewExpression[callee.property.name="TypeError"]' + + '[arguments.0.value="ERR_INVALID_ARG_TYPE"]'; + +const primitives = [ + 'number', 'string', 'boolean', 'null', 'undefined' +]; + +module.exports = function(context) { + function checkNamesArgument(node) { + const names = node.arguments[2]; + + switch (names.type) { + case 'Literal': + checkName(names); + break; + case 'ArrayExpression': + names.elements.forEach((name) => { + checkName(name); + }); + break; + } + } + + function checkName(node) { + const name = node.value; + const lowercaseName = name.toLowerCase(); + if (name !== lowercaseName && primitives.includes(lowercaseName)) { + const msg = `primitive should use lowercase: ${name}`; + context.report({ + node, + message: msg, + fix: (fixer) => { + return fixer.replaceText( + node, + `'${lowercaseName}'` + ); + } + }); + } + + } + + return { + [astSelector]: (node) => checkNamesArgument(node) + }; +}; diff --git a/tools/macos-installer/pkgbuild/npm/scripts/postinstall b/tools/macos-installer/pkgbuild/npm/scripts/postinstall new file mode 100755 index 00000000000000..285ce81e58a34e --- /dev/null +++ b/tools/macos-installer/pkgbuild/npm/scripts/postinstall @@ -0,0 +1,5 @@ +#!/bin/sh + +cd /usr/local/bin || exit 1 +ln -sf ../lib/node_modules/npm/bin/npm-cli.js npm +ln -sf ../lib/node_modules/npm/bin/npx-cli.js npx diff --git a/tools/macos-installer/pkgbuild/npm/scripts/preinstall b/tools/macos-installer/pkgbuild/npm/scripts/preinstall new file mode 100755 index 00000000000000..848da677b4d825 --- /dev/null +++ b/tools/macos-installer/pkgbuild/npm/scripts/preinstall @@ -0,0 +1,5 @@ +#!/bin/sh + +[[ -d /usr/local/lib/node_modules/npm ]] \ + && rm -rf /usr/local/lib/node_modules/npm +exit 0 diff --git a/tools/macos-installer/productbuild/Resources/en.lproj/conclusion.html.tmpl b/tools/macos-installer/productbuild/Resources/en.lproj/conclusion.html.tmpl new file mode 100644 index 00000000000000..1157d9720816fe --- /dev/null +++ b/tools/macos-installer/productbuild/Resources/en.lproj/conclusion.html.tmpl @@ -0,0 +1,23 @@ + + + + + +

+ + diff --git a/tools/macos-installer/productbuild/Resources/en.lproj/welcome.html.tmpl b/tools/macos-installer/productbuild/Resources/en.lproj/welcome.html.tmpl new file mode 100644 index 00000000000000..3790894e39727d --- /dev/null +++ b/tools/macos-installer/productbuild/Resources/en.lproj/welcome.html.tmpl @@ -0,0 +1,19 @@ + + + + + +
+

This package will install:

+
    +
  • Node.js {nodeversion} to /usr/local/bin/node
  • +
  • npm {npmversion} to /usr/local/bin/npm
  • +
+
+ + diff --git a/tools/macos-installer/productbuild/distribution.xml.tmpl b/tools/macos-installer/productbuild/distribution.xml.tmpl new file mode 100644 index 00000000000000..0b9d84701bb01a --- /dev/null +++ b/tools/macos-installer/productbuild/distribution.xml.tmpl @@ -0,0 +1,23 @@ + + + Node.js + + + + + + + + + + + + + + + node-{nodeversion}.pkg + + + + npm-{npmversion}.pkg + diff --git a/tools/osx-pkg.pmdoc/01local-contents.xml b/tools/osx-pkg.pmdoc/01local-contents.xml deleted file mode 100644 index ccbb4189961b0a..00000000000000 --- a/tools/osx-pkg.pmdoc/01local-contents.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tools/osx-pkg.pmdoc/01local.xml b/tools/osx-pkg.pmdoc/01local.xml deleted file mode 100644 index 537b35508bb714..00000000000000 --- a/tools/osx-pkg.pmdoc/01local.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - org.nodejs.node.pkg - 1.0 - - - - ../out/dist-osx/usr/local/ - /usr/local - - - - - installTo.isRelativeType - installTo - locationType - relocatable - installFrom.path - installTo.isAbsoluteType - identifier - parent - installTo.path - installFrom.isRelativeType - - diff --git a/tools/osx-pkg.pmdoc/02npm-contents.xml b/tools/osx-pkg.pmdoc/02npm-contents.xml deleted file mode 100644 index ccbb4189961b0a..00000000000000 --- a/tools/osx-pkg.pmdoc/02npm-contents.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tools/osx-pkg.pmdoc/02npm.xml b/tools/osx-pkg.pmdoc/02npm.xml deleted file mode 100644 index fca97e5c27dd11..00000000000000 --- a/tools/osx-pkg.pmdoc/02npm.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - org.nodejs.npm.pkg - 1.0 - - - - ../deps/npm - /usr/local/lib/node_modules/npm - - - - - installTo.path - installFrom.isRelativeType - installTo - scripts.postinstall.isRelativeType - parent - installTo.isAbsoluteType - - - osx-pkg-postinstall.sh - - diff --git a/tools/osx-pkg.pmdoc/index.xml.tmpl b/tools/osx-pkg.pmdoc/index.xml.tmpl deleted file mode 100644 index e3b14b2112d694..00000000000000 --- a/tools/osx-pkg.pmdoc/index.xml.tmpl +++ /dev/null @@ -1,55 +0,0 @@ - - - Node.js - /Users/nodejs/Desktop/node.pkg - org.nodejs - - - - - - - - - - - - - - - - - - - ../doc/osx_installer_logo.png - ../LICENSE - - - - - - 01local.xml - 02npm.xml - properties.title - properties.userDomain - properties.anywhereDomain - properties.systemDomain -