Skip to content

Commit 12b7d78

Browse files
fix: properly parse relative URL with a "@" character
A query parameter with a "@" character could be incorrectly parsed. Example: "/[email protected]" => host: example.com The parse() method is also used in the `socket.io-client` package, to extract the namespace and the query parameters. Notes: - this bug does not seem exploitable, as an attacker would need to inject the query parameter in the code executed by the client. - we might use the URL object in the next major version, but that means dropping support for some platforms such as IE Reference: https://caniuse.com/url Thanks to Li Jiantao of STAR Labs (@starlabs_sg) for the responsible disclosure.
1 parent ed6d016 commit 12b7d78

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

lib/contrib/parseuri.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
// imported from https://github.com/galkn/parseuri
22
/**
3-
* Parses an URI
3+
* Parses a URI
4+
*
5+
* Note: we could also have used the built-in URL object, but it isn't supported on all platforms.
6+
*
7+
* See:
8+
* - https://developer.mozilla.org/en-US/docs/Web/API/URL
9+
* - https://caniuse.com/url
10+
* - https://www.rfc-editor.org/rfc/rfc3986#appendix-B
11+
*
12+
* History of the parse() method:
13+
* - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c
14+
* - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3
15+
* - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242
416
*
517
* @author Steven Levithan <stevenlevithan.com> (MIT license)
618
* @api private
719
*/
8-
const re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
20+
const re = /^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
921

1022
const parts = [
1123
'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require("./socket");
1414
require("./transport");
1515
require("./connection");
1616
require("./xmlhttprequest");
17+
require("./parseuri");
1718

1819
if (typeof ArrayBuffer !== "undefined") {
1920
require("./arraybuffer");

test/parseuri.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// imported from https://github.com/galkn/parseuri
2+
const expect = require("expect.js");
3+
const parseuri = require("..").parse;
4+
5+
describe("parseuri", function () {
6+
it("should parse an uri", function () {
7+
const http = parseuri("http://google.com"),
8+
https = parseuri("https://www.google.com:80"),
9+
query = parseuri("google.com:8080/foo/bar?foo=bar"),
10+
localhost = parseuri("localhost:8080"),
11+
ipv6 = parseuri("2001:0db8:85a3:0042:1000:8a2e:0370:7334"),
12+
ipv6short = parseuri("2001:db8:85a3:42:1000:8a2e:370:7334"),
13+
ipv6port = parseuri("2001:db8:85a3:42:1000:8a2e:370:7334:80"),
14+
ipv6abbrev = parseuri("2001::7334:a:80"),
15+
ipv6http = parseuri("http://[2001::7334:a]:80"),
16+
ipv6query = parseuri("http://[2001::7334:a]:80/foo/bar?foo=bar");
17+
18+
expect(http.protocol).to.be("http");
19+
expect(http.port).to.be("");
20+
expect(http.host).to.be("google.com");
21+
expect(https.protocol).to.be("https");
22+
expect(https.port).to.be("80");
23+
expect(https.host).to.be("www.google.com");
24+
expect(query.port).to.be("8080");
25+
expect(query.query).to.be("foo=bar");
26+
expect(query.path).to.be("/foo/bar");
27+
expect(query.relative).to.be("/foo/bar?foo=bar");
28+
expect(query.queryKey.foo).to.be("bar");
29+
expect(query.pathNames[0]).to.be("foo");
30+
expect(query.pathNames[1]).to.be("bar");
31+
expect(localhost.protocol).to.be("");
32+
expect(localhost.host).to.be("localhost");
33+
expect(localhost.port).to.be("8080");
34+
expect(ipv6.protocol).to.be("");
35+
expect(ipv6.host).to.be("2001:0db8:85a3:0042:1000:8a2e:0370:7334");
36+
expect(ipv6.port).to.be("");
37+
expect(ipv6short.protocol).to.be("");
38+
expect(ipv6short.host).to.be("2001:db8:85a3:42:1000:8a2e:370:7334");
39+
expect(ipv6short.port).to.be("");
40+
expect(ipv6port.protocol).to.be("");
41+
expect(ipv6port.host).to.be("2001:db8:85a3:42:1000:8a2e:370:7334");
42+
expect(ipv6port.port).to.be("80");
43+
expect(ipv6abbrev.protocol).to.be("");
44+
expect(ipv6abbrev.host).to.be("2001::7334:a:80");
45+
expect(ipv6abbrev.port).to.be("");
46+
expect(ipv6http.protocol).to.be("http");
47+
expect(ipv6http.port).to.be("80");
48+
expect(ipv6http.host).to.be("2001::7334:a");
49+
expect(ipv6query.protocol).to.be("http");
50+
expect(ipv6query.port).to.be("80");
51+
expect(ipv6query.host).to.be("2001::7334:a");
52+
expect(ipv6query.relative).to.be("/foo/bar?foo=bar");
53+
54+
const withUserInfo = parseuri("ws://foo:[email protected]");
55+
56+
expect(withUserInfo.protocol).to.eql("ws");
57+
expect(withUserInfo.userInfo).to.eql("foo:bar");
58+
expect(withUserInfo.user).to.eql("foo");
59+
expect(withUserInfo.password).to.eql("bar");
60+
expect(withUserInfo.host).to.eql("google.com");
61+
62+
const relativeWithQuery = parseuri("/[email protected]");
63+
64+
expect(relativeWithQuery.host).to.be("");
65+
expect(relativeWithQuery.path).to.be("/foo");
66+
expect(relativeWithQuery.query).to.be("[email protected]");
67+
});
68+
});

0 commit comments

Comments
 (0)