Skip to content
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

Allow header value to be an Array of String instances. #1793

Merged
merged 2 commits into from Mar 2, 2022

Conversation

ioquatix
Copy link
Member

Addresses #1598

@ioquatix ioquatix marked this pull request as draft January 25, 2022 06:46
@ioquatix ioquatix force-pushed the spec-headers-array branch 7 times, most recently from 1439795 to 3e8e2ad Compare February 23, 2022 08:04
@ioquatix
Copy link
Member Author

@jeremyevans @tenderlove Looking at the RFC, there should be no need to delete an existing cookie by scanning the list of set-cookie headers before adding one with value= and expires in the past. The browser will simply add and then remove a cookie.

I believe that there would be very few cases where this is actually useful behaviour. i.e. someone would have to add and then delete a cookie with the same details to hit this case.

Bearing in mind, this means that every time someone deletes the cookie, it's O(N) scan, and even worse on the current implementation which has to unpack, parse and then recombine all the cookies.

I'd personally like to simplify this in this PR.

If someone deletes a cookie, we simply append the appropriate deletion (expires in past, value=) without trying to unpack or parse existing cookies.

The only disadvantage I can see with this is if someone sets, say, a 1MiB cookie and then deletes it, in my proposed implementation it would send still 1MiB of cookie data.

I think the simplicity (and probably performance) win is big enough to justify the difference. That being said, if there are buggy behaviours that don't process set-cookie in order, it could be an issue... but then it's a bug in the browser.

@ioquatix
Copy link
Member Author

By the way, I've also removed a bunch of Utils methods that don't really make sense and were very specific to the newline encoded format. We can try to add compatibility shims, not sure if it's necessary?

@ioquatix
Copy link
Member Author

Doing some digging, found that it looks like we did previously support header value being an Array:

def delete_cookie(key, value={})
unless Array === self["Set-Cookie"]
self["Set-Cookie"] = [self["Set-Cookie"]].compact
end
self["Set-Cookie"].reject! { |cookie|
cookie =~ /\A#{Utils.escape(key)}=/
}
set_cookie(key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
end

@ioquatix
Copy link
Member Author

@leahneukirchen I see your first implementation here: 7ed819a#diff-cfcc6d2645b6f77c5ec05acd448f280ce3004d3dba85b8aef3b3c81faf6d1373R53-R55

It looks like you decided to delete cookies with the given name using a regexp. While I understand that it's a fairly "complete" implementation, my impression of reading the RFC is that it's unnecessary, as adding a later header with value= and expires=1970 is sufficient as the user agent should process it in order. Do you remember why you implemented it this way?

@ioquatix ioquatix marked this pull request as ready for review February 23, 2022 08:40
@tenderlove
Copy link
Member

This is definitely better than splitting on \n, but I have a few concerns. First, we should make the webrick handler backwards compatible. Second, it's very very bad if an outgoing header contains a newline as it can lead to header splitting attacks. If an attacker is able to control header values, they can use \n to set cookies or whatever. The fact that the Rack SPEC requires webservers to split on \n surprisingly helps us avoid that particular issue as it means output header values can never have a newline.

I realize the spec says values below 037 are not allowed but it looks like we're not verifying that in the webrick server.

I'm not sure what we should do exactly, but the spec needs to be quite strongly worded with regard to the data allowed in headers, and we might want to add exceptions and checking in Rack::Response.

Copy link
Contributor

@jeremyevans jeremyevans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with the protocol change. I think we need to properly deprecate the Utils methods, unless doing so is not feasible.

lib/rack/lint.rb Show resolved Hide resolved
lib/rack/handler/webrick.rb Show resolved Hide resolved
lib/rack/response.rb Show resolved Hide resolved
lib/rack/utils.rb Show resolved Hide resolved
@ioquatix
Copy link
Member Author

@tenderlove I actually had a similar discussion and we introduced checks here as a result (which is used by Falcon):

https://github.com/socketry/protocol-http1/blob/main/lib/protocol/http1/connection.rb#L160

This problem doesn’t apply to HTTP/2 w.r.t. potential security issues so I don’t bother with a similar check there.

@ioquatix
Copy link
Member Author

@tenderlove I checked this and Webrick already fails if you give it a header containing an invalid value:

e.g.

# config.ru

run lambda{|env| [200, {"test" => "a\nb: c"}, {}]}

Result:

> curl -v http://localhost:9292
*   Trying 127.0.0.1:9292...
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET / HTTP/1.1
> Host: localhost:9292
> User-Agent: curl/7.80.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< Content-Type: text/html; charset=ISO-8859-1
* no chunk, no close, no size. Assume close to signal end
< 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
  <HEAD><TITLE>Internal Server Error</TITLE></HEAD>
  <BODY>
    <H1>Internal Server Error</H1>
    WEBrick::HTTPResponse::InvalidHeader
    <HR>
    <ADDRESS>
     WEBrick/1.7.0 (Ruby/3.0.3/2021-11-24) at
     localhost:9292
    </ADDRESS>
  </BODY>
</HTML>
* Closing connection 0

So, I don't think we need to do anything here, as it's already correctly failing. WDYT?

@ioquatix
Copy link
Member Author

This is definitely better than splitting on \n, but I have a few concerns. First, we should make the webrick handler backwards compatible

As @jeremyevans mentioned, if someone includes Rack 2 they will get the Rack 2 implementation, and if they include Rack 3 they will get the Rack 3 implementation. I don't think backwards compatibility is needed here?

I realize the spec says values below 037 are not allowed but it looks like we're not verifying that in the webrick server.

Webrick appears to validate this internally now.

I'm not sure what we should do exactly, but the spec needs to be quite strongly worded with regard to the data allowed in headers, and we might want to add exceptions and checking in Rack::Response.

My feeling is it's better to check this in the server since it reduces the performance cost (doesn't double up on checks).

Copy link
Contributor

@jeremyevans jeremyevans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good, just need to modify the warn calls.

lib/rack/utils.rb Outdated Show resolved Hide resolved
@ioquatix ioquatix merged commit 4600828 into rack:main Mar 2, 2022
@ioquatix ioquatix deleted the spec-headers-array branch March 2, 2022 22:55
dentarg added a commit to dentarg/sinatra that referenced this pull request Dec 28, 2022
dentarg added a commit to dentarg/sinatra that referenced this pull request Dec 30, 2022
dentarg added a commit to dentarg/sinatra that referenced this pull request Feb 13, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Feb 24, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Mar 4, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Mar 5, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Apr 11, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request May 15, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request May 16, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Aug 7, 2023
geoffharcourt pushed a commit to commonlit/sinatra that referenced this pull request Nov 6, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Dec 23, 2023
dentarg added a commit to dentarg/sinatra that referenced this pull request Jan 2, 2024
dentarg added a commit to dentarg/sinatra that referenced this pull request Jan 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants