-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix invalid url escaping - replaced url parsing from url.parse to new… #178
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import _ = require('lodash'); | ||
import url = require('url'); | ||
import type dns = require('dns'); | ||
import net = require('net'); | ||
import tls = require('tls'); | ||
|
@@ -405,7 +404,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
|
||
// Capture raw request data: | ||
let { method, url: reqUrl, rawHeaders } = clientReq as OngoingRequest; | ||
let { protocol, hostname, port, path } = url.parse(reqUrl); | ||
let { protocol, hostname, port, pathname, search } = new URL(reqUrl); | ||
|
||
// Check if this request is a request loop: | ||
if (isSocketLoop(this.outgoingSockets, (<any> clientReq).socket)) { | ||
|
@@ -430,7 +429,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
hostname, | ||
clientReq.remoteIpAddress, | ||
getDnsLookupFunction(this.lookupOptions) | ||
); | ||
) || ""; | ||
|
||
if (this.forwarding) { | ||
const { targetHost, updateHostHeader } = this.forwarding; | ||
|
@@ -439,7 +438,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
[hostname, port] = targetHost.split(':'); | ||
} else { | ||
// We're forwarding to a fully specified URL; override the host etc, but never the path. | ||
({ protocol, hostname, port } = url.parse(targetHost)); | ||
({ protocol, hostname, port } = new URL(targetHost)); | ||
} | ||
|
||
const hostHeaderName = isH2Downstream ? ':authority' : 'host'; | ||
|
@@ -465,7 +464,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
hostHeader[1] = updateHostHeader; | ||
} // Otherwise: falsey means don't touch it. | ||
|
||
reqUrl = new URL(`${protocol}//${hostname}${(port ? `:${port}` : '')}${path}`).toString(); | ||
reqUrl = new URL(`${protocol}//${hostname}${(port ? `:${port}` : '')}${pathname}${search}`).toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, there are other differences here where the console.log(url.parse('https://example.com/?').search);
// '?'
console.log(new URL('https://example.com/?').search)
// '' These might be semantically equivalent in theory, but they're not actually the same in practice (a server will literally receive a different string, and can legitimately do different things based on that if it so chooses). With this change, when proxying a request to that URL through Node, the client will send a slightly different request to the server that it expected. For more context, there's a WHATWG discussion on this that you might find interesting at whatwg/url#779 (I've just added a comment there with my perspective). This is not great, but of course you do make a good argument about the other differences where |
||
} | ||
|
||
// Override the request details, if a transform or callback is specified: | ||
|
@@ -645,7 +644,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
// Reparse the new URL, if necessary | ||
if (modifiedReq?.url) { | ||
if (!isAbsoluteUrl(modifiedReq?.url)) throw new Error("Overridden request URLs must be absolute"); | ||
({ protocol, hostname, port, path } = url.parse(reqUrl)); | ||
({ protocol, hostname, port, pathname, search } = new URL(reqUrl)); | ||
} | ||
|
||
rawHeaders = objectHeadersToRaw(headers); | ||
|
@@ -748,7 +747,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
hostname, | ||
port, | ||
family, | ||
path, | ||
path: pathname + search, | ||
headers: shouldTryH2Upstream | ||
? rawHeadersToObjectPreservingCase(rawHeaders) | ||
: flattenPairedRawHeaders(rawHeaders) as any, | ||
|
@@ -1110,7 +1109,7 @@ export class PassThroughHandler extends PassThroughHandlerDefinition { | |
protocol: protocol!.replace(/:$/, ''), | ||
hostname, | ||
port, | ||
path, | ||
path: pathname + search, | ||
rawHeaders | ||
}); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? Currently it looks like we previously assumed hostname really could be
null
here, and accepted that (andgetClientRelativeHostname
preserves that). That's not great - I think in reality it is always set, sincereq.url
is always an absolute URL in our case (we preprocess to ensure this in MockttpServer).However, setting it to
""
isn't very good either and might do all sorts of weird things.In reality, with
new URL()
I thinkhostname
is now always set, and that's recognized in the types, so really we should changegetClientRelativeHostname
here - either to remove thenull
support entirely so it's always set, or to improve the types so we know that string hostname in => string hostname out.