Open mjwbyrne opened 6 years ago
CC: @bradfitz
Related bug: #14952 (net/http: add switches to accept/send bogus HTTP?)
The signature func(error, http.ResponseWriter, []byte)
is a bit bizarre, especially that last argument. If it's malformed, that means we don't really understand it, so it's not clear we even know what's wrong. And how does this work with HTTP/2? Give you the whole binary frame? Or is this an HTTP/1-only thing, with the idea that HTTP/2 requests are all well formed since they haven't degraded over years yet?
I think I'd be fine adding in some HTTP1-specific hook for malformed requests, but I don't think I'd want to involve ResponseWriter at all. I'd prefer to just provide the net.Conn
and the bytes consumed. It'd probably look more like the Hijacker
interface. i.e. "On bogus HTTP/1.x request, the connection is all yours... have fun!"
If somebody wants to make a prototype along those lines and send a CL we can take a look.
Regarding the signature, the idea is that control is passed to the application to do whatever it wants. It might just discard the []byte (if, as you say, it just doesn't know what to do with a malformed request), but why not let the application decide whether the request is so broken it can't be dealt with? For example, in the "literal space in the URL" example I gave originally, it's conceivable that application code could in fact rescue the request.
I can't comment on how this would interact with HTTP/2 because I don't know enough (i.e. anything, really) about HTTP/2.
The Hijacker
-style solution sounds great to me. Happy to knock out a first draft and send it for review.
it's conceivable that application code could in fact rescue the request.
Maybe, but you won't be able to pass it back to net/http
to continue using. But yeah, you might be able to fire back a response that the client is happy with & then close the TCP connection. If you're cool with that.
Yes, that's what I had in mind - when a malformed request comes in, the hook gets called and from then on it's up to the application to handle it entirely - if it wants to rescue the request it has to do all the header parsing and so on by itself.
I expect the real-life use cases would simply be to respond with a 400 Bad Request, but in a more customisable way than literally putting "400 Bad Request" on the wire. Also logging.
Change https://golang.org/cl/111695 mentions this issue: net/http: add a malformed HTTP request callback to http.Server
Proposed change submitted. This is a somewhat simpler design than what we've been discussing - it does the minimum required to give the application a reasonable say in how malformed requests are handled.
Just to pile on here:
A user is adamant that not being able to supress this is a security vulnerability, because when they allow users to manually configure URLs, those users can then specify URLs that are not actually HTTP endpoints, leading to lines like net/http: HTTP/1.x transport connection broken: malformed HTTP response \"SSH-2.0-OpenSSH_7.4\"
. This allows the users that they have allowed to craft URLs to then craft URLs that disclose to them information about the local and/or remote systems.
The proposed change only seems to affect the http.Server
. Addressing this user's concern would require being able to specify such a handler per-request and/or per-client and/or globally.
The net/http HTTP server handles certain malformed HTTP requests completely autonomously, without ever passing control to non-library code. This means that the application is never made aware of them, so it cannot take any action, e.g. logging that a malformed request was received. It also prevents the application from customising the response.
For example, running a minimal Go HTTP server at localhost:8000 and doing this:
results in
(The error is caused by the whitespace in the first line of the request - the parser naively splits it on space and expects [method, URI, protocol]).
This is a problem because:
I propose adding an exported variable of type
to http.Server. It can be set by the application before ListenAndServe or ListenAndServeTLS is called. If this is nil (the default), the current behaviour stays. If it is not nil, then it is called (with the error, responseWriter and, optionally, a byte slice containing the offending part of the request - or even all of it?) when the server encounters a malformed request which it cannot pass on to the handler.