Skip to content

Commit

Permalink
Do not allow bracketed hostnames.
Browse files Browse the repository at this point in the history
Fixes #235
  • Loading branch information
RubenVerborgh committed Dec 30, 2023
1 parent bc9319a commit e0906af
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 40 deletions.
75 changes: 46 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,27 +490,16 @@ function wrap(protocols) {

// Executes a request, following redirects
function request(input, options, callback) {
// Parse parameters
if (isString(input)) {
var parsed;
try {
parsed = spreadUrlObject(new URL(input));
}
catch (err) {
/* istanbul ignore next */
parsed = url.parse(input);
}
if (!isString(parsed.protocol)) {
throw new InvalidUrlError({ input });
}
input = parsed;
}
else if (URL && (input instanceof URL)) {
// Parse parameters, ensuring that input is an object
if (isURL(input)) {
input = spreadUrlObject(input);
}
else if (isString(input)) {
input = spreadUrlObject(parseUrl(input));
}
else {
callback = options;
options = input;
options = validateUrl(input);
input = { protocol: protocol };
}
if (isFunction(options)) {
Expand Down Expand Up @@ -551,32 +540,56 @@ function wrap(protocols) {

function noop() { /* empty */ }

function parseUrl(string) {
/* istanbul ignore next */
return URL ? new URL(string) : url.parse(string);
function parseUrl(input) {
var parsed;
/* istanbul ignore else */
if (URL) {
parsed = new URL(input);
}
else {
// Ensure the URL is valid and absolute
parsed = validateUrl(url.parse(input));
if (!isString(parsed.protocol)) {
throw new InvalidUrlError({ input });
}
}
return parsed;
}

function resolveUrl(relative, base) {
/* istanbul ignore next */
return URL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
}

function validateUrl(input) {
if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
throw new InvalidUrlError({ input: input.href || input });
}
if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
throw new InvalidUrlError({ input: input.href || input });
}
return input;
}

function spreadUrlObject(urlObject, target) {
var options = target || {};
var spread = target || {};
for (var key of preservedUrlFields) {
options[key] = urlObject[key];
spread[key] = urlObject[key];
}

if (options.hostname.startsWith("[")) {
/* istanbul ignore next */
options.hostname = options.hostname.slice(1, -1);
// Fix IPv6 hostname
var hostname = spread.hostname;
if (hostname.startsWith("[")) {
spread.hostname = hostname.slice(1, -1);
}
if (options.port !== "") {
options.port = Number(options.port);
// Ensure port is a number
if (spread.port !== "") {
spread.port = Number(spread.port);
}
options.path = options.search ? options.pathname + options.search : options.pathname;
// Concatenate path
spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;

return options;
return spread;
}

function removeMatchingHeaders(regex, headers) {
Expand Down Expand Up @@ -641,6 +654,10 @@ function isBuffer(value) {
return typeof value === "object" && ("length" in value);
}

function isURL(value) {
return URL && value instanceof URL;
}

// Exports
module.exports = wrap({ http: http, https: https });
module.exports.wrap = wrap;
120 changes: 109 additions & 11 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,67 @@ describe("follow-redirects", function () {
});
});

it("http.get to bracketed IPv4 address", function () {
var error = null;
try {
http.get("http://[127.0.0.1]:3600/a");
}
catch (err) {
error = err;
}
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equal(error.code, "ERR_INVALID_URL");
assert.equal(error.input, "http://[127.0.0.1]:3600/a");
});

it("http.get to bracketed IPv4 address specified as host", function () {
var error = null;
try {
http.get({
host: "[127.0.0.1]:3600",
path: "/a",
});
}
catch (err) {
error = err;
}
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equal(error.code, "ERR_INVALID_URL");
});

it("http.get to bracketed IPv4 address specified as hostname", function () {
var error = null;
try {
http.get({
hostname: "[127.0.0.1]",
port: 3600,
path: "/a",
});
}
catch (err) {
error = err;
}
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equal(error.code, "ERR_INVALID_URL");
});

it("http.get to bracketed hostname", function () {
var error = null;
try {
http.get("http://[localhost]:3600/a");
}
catch (err) {
error = err;
}
assert(error instanceof Error);
assert(error instanceof TypeError);
assert.equal(error.code, "ERR_INVALID_URL");
assert.equal(error.input, "http://[localhost]:3600/a");
});

it("http.get redirecting to IPv4 address", function () {
app.get("/a", redirectsTo("http://127.0.0.1:3600/b"));
app.get("/b", sendsJson({ a: "b" }));
Expand Down Expand Up @@ -241,6 +302,46 @@ describe("follow-redirects", function () {
});
});

it("http.get redirecting to bracketed IPv4 address", function () {
app.get("/a", redirectsTo("http://[127.0.0.1]:3600/b"));
app.get("/b", sendsJson({ a: "b" }));

return server.start(app)
.then(asPromise(function (resolve, reject) {
http.get("http://localhost:3600/a", concatJson(reject)).on("error", resolve);
}))
.then(function (error) {
assert(error instanceof Error);
assert.equal(error.code, "ERR_FR_REDIRECTION_FAILURE");

var cause = error.cause;
assert(cause instanceof Error);
assert(cause instanceof TypeError);
assert.equal(cause.code, "ERR_INVALID_URL");
assert.equal(cause.input, "http://[127.0.0.1]:3600/b");
});
});

it("http.get redirecting to bracketed hostname", function () {
app.get("/a", redirectsTo("http://[localhost]:3600/b"));
app.get("/b", sendsJson({ a: "b" }));

return server.start(app)
.then(asPromise(function (resolve, reject) {
http.get("http://localhost:3600/a", concatJson(reject)).on("error", resolve);
}))
.then(function (error) {
assert(error instanceof Error);
assert.equal(error.code, "ERR_FR_REDIRECTION_FAILURE");

var cause = error.cause;
assert(cause instanceof Error);
assert(cause instanceof TypeError);
assert.equal(cause.code, "ERR_INVALID_URL");
assert.equal(cause.input, "http://[localhost]:3600/b");
});
});

it("http.get with response event", function () {
app.get("/a", redirectsTo("/b"));
app.get("/b", redirectsTo("/c"));
Expand All @@ -266,8 +367,8 @@ describe("follow-redirects", function () {
try {
http.get("/relative");
}
catch (e) {
error = e;
catch (err) {
error = err;
}
assert(error instanceof Error);
assert(error instanceof TypeError);
Expand Down Expand Up @@ -963,9 +1064,9 @@ describe("follow-redirects", function () {
.then(asPromise(function (resolve, reject) {
http.get("http://localhost:3600/a")
.on("response", function () { return reject(new Error("unexpected response")); })
.on("error", reject);
.on("error", resolve);
}))
.catch(function (error) {
.then(function (error) {
assert(error instanceof Error);
assert.equal(error.message, "Redirected request failed: Unsupported protocol about:");

Expand Down Expand Up @@ -1266,8 +1367,8 @@ describe("follow-redirects", function () {
try {
req.write(12345678);
}
catch (e) {
error = e;
catch (err) {
error = err;
}
req.destroy();
assert(error instanceof Error);
Expand Down Expand Up @@ -2132,12 +2233,9 @@ describe("follow-redirects", function () {
throw new Error("no redirects!");
},
};
http.get(options, concatJson(resolve, reject)).on("error", reject);
http.get(options, concatJson(reject)).on("error", resolve);
}))
.then(function () {
assert.fail("request chain should have been aborted");
})
.catch(function (error) {
.then(function (error) {
assert(!redirected);
assert(error instanceof Error);
assert.equal(error.message, "Redirected request failed: no redirects!");
Expand Down

0 comments on commit e0906af

Please sign in to comment.