Skip to content

Commit 7a6567e

Browse files
committedDec 30, 2023
Disallow bracketed hostnames.
Fixes #235
1 parent 05629af commit 7a6567e

File tree

2 files changed

+151
-33
lines changed

2 files changed

+151
-33
lines changed
 

‎index.js

+42-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ var assert = require("assert");
77
var debug = require("./debug");
88

99
// Whether to use the native URL object or the legacy url module
10-
var hasNativeURL = typeof URL !== "undefined";
10+
var useNativeURL = false;
11+
try {
12+
assert(new URL());
13+
}
14+
catch (error) {
15+
useNativeURL = error.code === "ERR_INVALID_URL";
Has conversations. Original line has conversations.
16+
}
1117

1218
// URL fields to preserve in copy operations
1319
var preservedUrlFields = [
@@ -493,27 +499,16 @@ function wrap(protocols) {
493499

494500
// Executes a request, following redirects
495501
function request(input, options, callback) {
496-
// Parse parameters
497-
if (isString(input)) {
498-
var parsed;
499-
try {
500-
parsed = spreadUrlObject(new URL(input));
501-
}
502-
catch (err) {
503-
/* istanbul ignore next */
504-
parsed = url.parse(input);
505-
}
506-
if (!isString(parsed.protocol)) {
507-
throw new InvalidUrlError({ input });
508-
}
509-
input = parsed;
510-
}
511-
else if (hasNativeURL && (input instanceof URL)) {
502+
// Parse parameters, ensuring that input is an object
503+
if (isURL(input)) {
512504
input = spreadUrlObject(input);
513505
}
506+
else if (isString(input)) {
507+
input = spreadUrlObject(parseUrl(input));
508+
}
514509
else {
515510
callback = options;
516-
options = input;
511+
options = validateUrl(input);
517512
input = { protocol: protocol };
518513
}
519514
if (isFunction(options)) {
@@ -554,14 +549,35 @@ function wrap(protocols) {
554549

555550
function noop() { /* empty */ }
556551

557-
function parseUrl(string) {
558-
/* istanbul ignore next */
559-
return hasNativeURL ? new URL(string) : url.parse(string);
552+
function parseUrl(input) {
553+
var parsed;
554+
/* istanbul ignore else */
555+
if (useNativeURL) {
556+
parsed = new URL(input);
557+
}
558+
else {
559+
// Ensure the URL is valid and absolute
560+
parsed = validateUrl(url.parse(input));
561+
if (!isString(parsed.protocol)) {
562+
throw new InvalidUrlError({ input });
563+
}
564+
}
565+
return parsed;
560566
}
561567

562568
function resolveUrl(relative, base) {
563569
/* istanbul ignore next */
564-
return hasNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
570+
return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
571+
}
572+
573+
function validateUrl(input) {
574+
if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
575+
throw new InvalidUrlError({ input: input.href || input });
576+
}
577+
if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
578+
throw new InvalidUrlError({ input: input.href || input });
579+
}
580+
return input;
565581
}
566582

567583
function spreadUrlObject(urlObject, target) {
@@ -646,6 +662,10 @@ function isBuffer(value) {
646662
return typeof value === "object" && ("length" in value);
647663
}
648664

665+
function isURL(value) {
666+
return URL && value instanceof URL;
667+
}
668+
649669
// Exports
650670
module.exports = wrap({ http: http, https: https });
651671
module.exports.wrap = wrap;

‎test/test.js

+109-11
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,67 @@ describe("follow-redirects", function () {
213213
});
214214
});
215215

216+
it("http.get to bracketed IPv4 address", function () {
217+
var error = null;
218+
try {
219+
http.get("http://[127.0.0.1]:3600/a");
220+
}
221+
catch (err) {
222+
error = err;
223+
}
224+
assert(error instanceof Error);
225+
assert(error instanceof TypeError);
226+
assert.equal(error.code, "ERR_INVALID_URL");
227+
assert.equal(error.input, "http://[127.0.0.1]:3600/a");
228+
});
229+
230+
it("http.get to bracketed IPv4 address specified as host", function () {
231+
var error = null;
232+
try {
233+
http.get({
234+
host: "[127.0.0.1]:3600",
235+
path: "/a",
236+
});
237+
}
238+
catch (err) {
239+
error = err;
240+
}
241+
assert(error instanceof Error);
242+
assert(error instanceof TypeError);
243+
assert.equal(error.code, "ERR_INVALID_URL");
244+
});
245+
246+
it("http.get to bracketed IPv4 address specified as hostname", function () {
247+
var error = null;
248+
try {
249+
http.get({
250+
hostname: "[127.0.0.1]",
251+
port: 3600,
252+
path: "/a",
253+
});
254+
}
255+
catch (err) {
256+
error = err;
257+
}
258+
assert(error instanceof Error);
259+
assert(error instanceof TypeError);
260+
assert.equal(error.code, "ERR_INVALID_URL");
261+
});
262+
263+
it("http.get to bracketed hostname", function () {
264+
var error = null;
265+
try {
266+
http.get("http://[localhost]:3600/a");
267+
}
268+
catch (err) {
269+
error = err;
270+
}
271+
assert(error instanceof Error);
272+
assert(error instanceof TypeError);
273+
assert.equal(error.code, "ERR_INVALID_URL");
274+
assert.equal(error.input, "http://[localhost]:3600/a");
275+
});
276+
216277
it("http.get redirecting to IPv4 address", function () {
217278
app.get("/a", redirectsTo("http://127.0.0.1:3600/b"));
218279
app.get("/b", sendsJson({ a: "b" }));
@@ -241,6 +302,46 @@ describe("follow-redirects", function () {
241302
});
242303
});
243304

305+
it("http.get redirecting to bracketed IPv4 address", function () {
306+
app.get("/a", redirectsTo("http://[127.0.0.1]:3600/b"));
307+
app.get("/b", sendsJson({ a: "b" }));
308+
309+
return server.start(app)
310+
.then(asPromise(function (resolve, reject) {
311+
http.get("http://localhost:3600/a", concatJson(reject)).on("error", resolve);
312+
}))
313+
.then(function (error) {
314+
assert(error instanceof Error);
315+
assert.equal(error.code, "ERR_FR_REDIRECTION_FAILURE");
316+
317+
var cause = error.cause;
318+
assert(cause instanceof Error);
319+
assert(cause instanceof TypeError);
320+
assert.equal(cause.code, "ERR_INVALID_URL");
321+
assert.equal(cause.input, "http://[127.0.0.1]:3600/b");
322+
});
323+
});
324+
325+
it("http.get redirecting to bracketed hostname", function () {
326+
app.get("/a", redirectsTo("http://[localhost]:3600/b"));
327+
app.get("/b", sendsJson({ a: "b" }));
328+
329+
return server.start(app)
330+
.then(asPromise(function (resolve, reject) {
331+
http.get("http://localhost:3600/a", concatJson(reject)).on("error", resolve);
332+
}))
333+
.then(function (error) {
334+
assert(error instanceof Error);
335+
assert.equal(error.code, "ERR_FR_REDIRECTION_FAILURE");
336+
337+
var cause = error.cause;
338+
assert(cause instanceof Error);
339+
assert(cause instanceof TypeError);
340+
assert.equal(cause.code, "ERR_INVALID_URL");
341+
assert.equal(cause.input, "http://[localhost]:3600/b");
342+
});
343+
});
344+
244345
it("http.get with response event", function () {
245346
app.get("/a", redirectsTo("/b"));
246347
app.get("/b", redirectsTo("/c"));
@@ -266,8 +367,8 @@ describe("follow-redirects", function () {
266367
try {
267368
http.get("/relative");
268369
}
269-
catch (e) {
270-
error = e;
370+
catch (err) {
371+
error = err;
271372
}
272373
assert(error instanceof Error);
273374
assert(error instanceof TypeError);
@@ -963,9 +1064,9 @@ describe("follow-redirects", function () {
9631064
.then(asPromise(function (resolve, reject) {
9641065
http.get("http://localhost:3600/a")
9651066
.on("response", function () { return reject(new Error("unexpected response")); })
966-
.on("error", reject);
1067+
.on("error", resolve);
9671068
}))
968-
.catch(function (error) {
1069+
.then(function (error) {
9691070
assert(error instanceof Error);
9701071
assert.equal(error.message, "Redirected request failed: Unsupported protocol about:");
9711072

@@ -1266,8 +1367,8 @@ describe("follow-redirects", function () {
12661367
try {
12671368
req.write(12345678);
12681369
}
1269-
catch (e) {
1270-
error = e;
1370+
catch (err) {
1371+
error = err;
12711372
}
12721373
req.destroy();
12731374
assert(error instanceof Error);
@@ -2132,12 +2233,9 @@ describe("follow-redirects", function () {
21322233
throw new Error("no redirects!");
21332234
},
21342235
};
2135-
http.get(options, concatJson(resolve, reject)).on("error", reject);
2236+
http.get(options, concatJson(reject)).on("error", resolve);
21362237
}))
2137-
.then(function () {
2138-
assert.fail("request chain should have been aborted");
2139-
})
2140-
.catch(function (error) {
2238+
.then(function (error) {
21412239
assert(!redirected);
21422240
assert(error instanceof Error);
21432241
assert.equal(error.message, "Redirected request failed: no redirects!");

0 commit comments

Comments
 (0)
Please sign in to comment.