-
-
Notifications
You must be signed in to change notification settings - Fork 315
Normalize HTTP request path. #228
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
Conversation
The HTTP/2.0 specification requires that the path pseudo header is never empty for requests unless the request uses the OPTIONS method. This is currently not correctly enforced. This patch provides a test and a fix.
.unwrap_or_else(|| Bytes::from_static(b"/")); | ||
.unwrap_or_else(|| Bytes::new()); | ||
|
||
if path.is_empty() && method != Method::OPTIONS { |
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.
When the URI is *
it isn't clear to me what path
is supposed to be here. is it *
like Uri::path()
says or is it ``?
When URI is *
and the method isn't OPTIONS then it seems like we should return an error somewhere here, but that doesn't seem to be done. Or maybe I'm misunderstanding some details of how *
is handled in the parsing code?
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.
The relevant spec section:
The :path pseudo-header field includes the path and query parts of the target URI (the path-absolute production and optionally a '?' character followed by the query production (see Sections 3.3 and 3.4 of [RFC3986]). A request in asterisk form includes the value '*' for the :path pseudo-header field.
This pseudo-header field MUST NOT be empty for http or https URIs; http or https URIs that do not contain a path component MUST include a value of '/'. The exception to this rule is an OPTIONS request for an http or https URI that does not include a path component; these MUST include a :path pseudo-header field with a value of '*' (see [RFC7230], Section 5.3.4).
Does this make sense?
I also saw you opened hyperium/http#176, so we can discuss the details of the http
path API there.
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.
So, the path can also be not set of the scheme is not http
or https
, but those cases are not really supported yet.
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.
I think this is fine, but it should also check for `Method::CONNECT.
The :scheme and :path pseudo-header fields MUST be omitted.
.expect("handshake") | ||
.and_then(move |(mut client, conn)| { | ||
// Note the lack of trailing slash. | ||
let request = Request::get("http://example.com") |
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.
It would be good to see tests for OPTIONS *
and GET *
as well as OPTIONS /*
and GET /*
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.
I tried to write a test for OPTIONS, but there seems to be some problems unrelated to this change. So, I will open a new issue to track this.
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.
Ok, I figured out how to write a test, but #229 still applies as it is kind of janky.
#[test] | ||
fn multiple_streams_with_payload_greater_than_default_window() { | ||
let _ = ::env_logger::init(); | ||
let _ = ::env_logger::try_init(); |
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.
This is unrelated to the PR but it can cause tests to fail and it didn't seem worth splitting it out.
I reviewed the initial PR without thinking too much. But, I actually think that the proposed fix is not a good design. We shouldn't default every request path to "/" when the request path isn't provided by the caller. Instead, we should refuse to issue the request. If the caller forgot to provide the request path then that's a logic failure on the caller's part. Defaulting to "/" will result in applications accidentally sending requests to the wrong path, which is dangerous. In the case of OPTIONS, it is especially confusing: should the default be "*", or should it be "/" like everything else? It seems wrong for it to be inconsistent with the others, but "/" also seems wrong. It's better to require the user to be explicit. |
src/frame/headers.rs
Outdated
.field("stream_id", &self.stream_id) | ||
.field("stream_dep", &self.stream_dep) | ||
.field("flags", &self.flags) | ||
.field("header_block", &self.header_block) |
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.
The comment right after this (while slight outdated) suggests that this field is purposefully not included :)
Is there an instance where that is true? I cannot think of any. I actually think that perhaps the behavior should be fixed in |
I'm sure there are many cases. One that comes to mind: Some domain ownership verification procedures require one to add a token somewhere in the file at
That's basically hyperium/http#176. |
I don't see how this bug occurs from assuming that the path of |
Also, assuming that I would say that constructing a URI from parts should possibly not make that assumption though. |
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.
Overall looks fine, just want the header_block
removed from the debug output, at least.
We could fix this for CONNECT
also, or this can stay focused on just OPTIONS *
, and connect can be a separate PR.
tests/client_request.rs
Outdated
let client = client::handshake(io) | ||
.expect("handshake") | ||
.and_then(move |(mut client, conn)| { | ||
// Note the lack of trailing slash. |
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.
I'd move this comment up to the let uri = ...
line, since here it just left me confused ("Note? But I don't see any lack of trailing slash here!")
let mut parts = uri::Parts::default(); | ||
parts.scheme = Some(uri::Scheme::HTTP); | ||
parts.authority = Some(uri::Authority::from_shared("example.com".into()).unwrap()); | ||
parts.path_and_query = Some(uri::PathAndQuery::from_static("*")); |
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.
Should we add a PathAndQuery::STAR
const in the http crate?
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.
It might be useful!
.unwrap_or_else(|| Bytes::from_static(b"/")); | ||
.unwrap_or_else(|| Bytes::new()); | ||
|
||
if path.is_empty() && method != Method::OPTIONS { |
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.
I think this is fine, but it should also check for `Method::CONNECT.
The :scheme and :path pseudo-header fields MUST be omitted.
@seanmonstar generally, I don't think |
Re the It seems like this is a general question and I am unsure if it merits holding up this PR. |
The HTTP/2.0 specification requires that the path pseudo header is never
empty for requests unless the request uses the OPTIONS method.
This is currently not correctly enforced.
This patch provides a test and a fix.