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

fix(http): enable httptools lenient data #2488

Merged
merged 3 commits into from
Nov 20, 2024
Merged

Conversation

vvanglro
Copy link
Contributor

@vvanglro vvanglro commented Oct 17, 2024

Summary

#2486

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

…ndency- Set dangerous leniency for HTTP parsing to handle data received after connection close

- Upgrade httptools dependency from >=0.5.0 to >=0.6.3 for improved functionality and security
@vvanglro
Copy link
Contributor Author

@Kludex The process is hanging again. #2431

@Kludex
Copy link
Member

Kludex commented Oct 17, 2024

Can you explain a bit what we are doing here?

@Kludex
Copy link
Member

Kludex commented Oct 17, 2024

@Kludex The process is hanging again. #2431

I'll merge that after we fix this. 👍

@vvanglro
Copy link
Contributor Author

Can you explain a bit what we are doing here?

Use a relaxed approach to parsing messages.

@vvanglro
Copy link
Contributor Author

vvanglro commented Oct 18, 2024

If we follow the discussion in #2238 and the RFC standard we should use lenient mode to parse the data.
Until then, we have been using the lenient mode to parse the data.

From RFC 9112, section 9.6:

A server that receives a "close" connection option MUST initiate closure of the connection (see below) after it sends the final response to the request that contained the "close" connection option. The server SHOULD send a "close" connection option in its final response on that connection. The server MUST NOT process any further requests received on that connection.

@vvanglro
Copy link
Contributor Author

As a record.

Httptools v0.6.3 upgrades dependency llhttp from 8.1.1 to 9.2.1, due to the lenient mode used before llhttp v9.0, some messages could not be parsed and responded to in this upgrade.

The following is a list of methods that support lenient parsing in llhttp, facilitate subsequent troubleshooting and development of more lenient methods as needed.
https://github.com/nodejs/llhttp/tree/v9.2.1?tab=readme-ov-file#void-llhttp_set_lenient_headersllhttp_t-parser-int-enabled

@Kludex
Copy link
Member

Kludex commented Oct 22, 2024

Should we enable this flag tho? https://llhttp.org/

Screenshot 2024-10-22 at 12 52 39

Maybe we should fix the test instead?

@vvanglro
Copy link
Contributor Author

Should we enable this flag tho? https://llhttp.org/

message = 'GET / HTTP/1.1\r\nConnection: close\r\n\r\nInvalid\r\n\r\n'

For enabling or not, I think it depends on whether the production environment receives such messages. If such messages are not generated except for malicious attacks, then we do not need to enable them.

Maybe we should fix the test instead?

Since h11 is now capable of handling such messages, if you want to modify it, keep the processing logic of h11 and httptools the same.

@vvanglro
Copy link
Contributor Author

How about leaving it up to the user to decide whether or not to use a lenient flag. 🤔

@Kludex
Copy link
Member

Kludex commented Nov 11, 2024

No. I don't think we should add a new flag.

@vvanglro
Copy link
Contributor Author

Do you have any good suggestions?

import httptools

REQUEST_AFTER_CONNECTION_CLOSE = b"\r\n".join(
    [
        b"GET / HTTP/1.1",
        b"Host: example1.org",
        b"Connection: close",
        b"",
        b"",
        b"GET / HTTP/1.1",
        b"Host: example2.org",
        b"",
        b"",
    ]
)
class MyHandler:
    def on_url(self, url: bytes) -> None:
        print(f"Target: {url}")
    def on_headers_complete(self) -> None:
        http_version = parser.get_http_version()
        method = parser.get_method()
        print(f"HTTP version: {http_version}, method: {method}")
    def on_header(self, name: bytes, value: bytes) -> None:
        print(f"Header: {name.decode()}: {value.decode()}")

parser = httptools.HttpRequestParser(MyHandler())
# Adding this line works fine.
parser.set_dangerous_leniencies(lenient_data_after_close=True)
parser.feed_data(REQUEST_AFTER_CONNECTION_CLOSE)


import h11

conn = h11.Connection(h11.SERVER)
conn.receive_data(REQUEST_AFTER_CONNECTION_CLOSE)
print(conn.next_event())

Or we can turn this flag on by default, keeping h11 and httptools the same processing logic.

@Kludex
Copy link
Member

Kludex commented Nov 20, 2024

Sorry, I shouldn't have force-pushed. I should have just added the revert commits. I used your first commit here.

Comment on lines 65 to 67
except AttributeError:
# httptools < 0.6.3
pass
Copy link
Member

Choose a reason for hiding this comment

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

Some people don't use uvicorn[standard] because it install more than needed on production e.g. watchfiles.

Copy link
Member

@Kludex Kludex left a comment

Choose a reason for hiding this comment

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

Thanks.

@Kludex Kludex enabled auto-merge (squash) November 20, 2024 18:56
@Kludex Kludex merged commit 2aea835 into encode:master Nov 20, 2024
18 checks passed
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.

2 participants