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

Implement RFC-7230 section 5.3.3 #66

Merged
merged 7 commits into from
May 10, 2023

Conversation

brian-mcnamara
Copy link
Contributor

@brian-mcnamara brian-mcnamara commented May 5, 2023

if ("CONNECT".equalsIgnoreCase(method)) {
String host = uri.getHost();
int port = uri.getPort();
assert host != null : "Host is missing from RequestLine uri";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed this library does not use asserts or throws, so curious to hear what the suggestion is here. https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 seems to require host to be provided in a CONNECT case

Copy link
Owner

Choose a reason for hiding this comment

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

host can be null here. The Host header may be used for defining that.

Copy link
Owner

Choose a reason for hiding this comment

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

Oh, you're about this:

A client sending a CONNECT request MUST send the authority form of
   request-target ([Section 5.3 of [RFC7230]](https://datatracker.ietf.org/doc/html/rfc7230#section-5.3))

In this case, this shouldn't be an assert, it should be straight out an Exception, because the caller should be notified of the problem.
But because I want RawHTTP to be usable for testing "bad" requests, I normally make this kind of thing optional, so you would need to add an option to the RawHttpOptions to allow for requests missing the host.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! I'll implement this.

Copy link
Owner

Choose a reason for hiding this comment

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

Looks like the server side also has some "obligations" to make CONNECT work.
There's also some other small requirements on the client, for example:

A server MUST NOT send any Transfer-Encoding or Content-Length header
   fields in a 2xx (Successful) response to CONNECT.  A client MUST
   ignore any Content-Length or Transfer-Encoding header fields received
   in a successful response to CONNECT.

   A payload within a CONNECT request message has no defined semantics;
   sending a payload body on a CONNECT request might cause some existing
   implementations to reject the request.

To support CONNECT correctly, we would need to do this as well :) . But I can finish the job, just let me know if you can help and how you're using this, so I can understand the use cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can take a look. I'm implementing a tunnel solution in java which connects to pomerium. As part of their tcp tunnel implementation, I create a socket and send a CONNECT request to which it replies with a response followed by keeping the socket open and switches over to proxying the streams. So I'm using this library to construct the initial message and parse the response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went through and added move validations and some changes to requestHasBody (responseHasBody is already handling connect). Let me know if there were other areas of the code you can think of that may need this (new to the project 😊)

@brian-mcnamara brian-mcnamara marked this pull request as draft May 5, 2023 16:27
if (path == null || path.isEmpty()) {
outputStream.write('/');
//RFC-7230 section 5.3.3
if ("CONNECT".equalsIgnoreCase(method)) {
Copy link
Owner

Choose a reason for hiding this comment

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

I think the easier way to do this logic would be to already check the options here, like this:

if (!options.allowIllegalConnectAuthority() && "CONNECT.equalsIgnoreCase(method)) ...

There's no need to enter this branch in any other case. Also notice the code below is checking the wrong option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Jeez, good catch, starts with allowIllegal and they all looked the same 🙈

Ok so the thinking is if allowIllegalConnectAuthority is set, the logic will fall back into the original implementation.

@brian-mcnamara brian-mcnamara marked this pull request as ready for review May 8, 2023 23:58
@renatoathaydes
Copy link
Owner

I'll merge pending the build results.
Is it correct to assume CONNECT is fully supported by the server now, given you have it working with a real implementation?

@brian-mcnamara
Copy link
Contributor Author

Looking at the failure.

I have only tested HttpResponse via RawHttp().parseResponse(inputStream) which works. I have not tried using the TcpRawHttpServer

@renatoathaydes
Copy link
Owner

Errors are on the Javadocs:

/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttp.java:229: error: reference not found
     * {@link RawHttp#requestHasBody(RawHttpHeaders)} or {@link RawHttp#responseHasBody(StatusLine, RequestLine)}.
              ^
/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttp.java:279: warning: no @param for requestLine
    public static boolean requestHasBody(RawHttpHeaders headers, RequestLine requestLine) {
                          ^
/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttpHeaders.java:47: warning: no comment
    public static final RawHttpHeaders CONTENT_LENGTH_ZERO = newBuilderSkippingValidation()
                                       ^
/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttpHeaders.java:315: warning: no comment
    public static RawHttpHeaders empty() {
                                 ^
/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttpOptions.java:317: error: unknown tag: quote
         * <quote>
           ^
/home/runner/work/rawhttp/rawhttp/rawhttp-core/src/main/java/rawhttp/core/RawHttpOptions.java:320: error: unknown tag: quote
         * </quote>
           ^

@@ -276,7 +276,7 @@ public FramedBody getFramedBody(StartLine startLine, RawHttpHeaders headers) {
* @param headers HTTP request's headers
* @return true if the headers indicate the request should have a body, false otherwise
*/
public static boolean requestHasBody(RawHttpHeaders headers) {
public static boolean requestHasBody(RawHttpHeaders headers, RequestLine requestLine) {
Copy link
Owner

Choose a reason for hiding this comment

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

This is a breaking change. We need an overload without the RequestLine so old code continues working.

Copy link
Owner

Choose a reason for hiding this comment

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

You can add @Deprecated to the method without RequestLine so we can remove it in the future.

* Allows bypassing requirements for CONNECT requests which require host and port to be sent
* <p>
* RFC-7230 section 5.3.3 defines CONNECT requests must:
* <quote>
Copy link
Owner

Choose a reason for hiding this comment

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

Javadoc does not support the full HTML spec.
I think you can do {@code } here.

Copy link
Owner

Choose a reason for hiding this comment

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

Or use <pre>, that should work.

Copy link
Owner

Choose a reason for hiding this comment

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

When you're done, run gradle javadoc from the root folder. If that passes, everything should be fine.
Run gradle check to run all tests. Also should be fine as I ran locally and it seems fine.

@renatoathaydes renatoathaydes merged commit ff052e4 into renatoathaydes:master May 10, 2023
9 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.

None yet

2 participants