Bug 2038516

Summary: OpenJDK does not comply with RFC7231#4.3.6
Product: Red Hat Enterprise Linux 7 Reporter: andrew
Component: java-1.8.0-openjdkAssignee: Andrew John Hughes <ahughes>
Status: CLOSED WONTFIX QA Contact: OpenJDK QA <java-qa>
Severity: unspecified Docs Contact:
Priority: unspecified    
Version: 7.9CC: jvanek
Target Milestone: rc   
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2023-07-08 07:27:58 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description andrew 2022-01-08 04:20:55 UTC
Description of problem:

RFC 7321#4.3.6 states that:

A client sending a CONNECT request MUST send the authority form of
request-target (Section 5.3 of [RFC7230]); i.e., the request-target
consists of only the host name and port number of the tunnel
destination, separated by a colon.  For example,

     CONNECT server.example.com:80 HTTP/1.1
     Host: server.example.com:80

The HttpURLConnection API in OpenJDK 8 implements CONNECT support as:

    /**
     * send a CONNECT request for establishing a tunnel to proxy server
     */
    private void sendCONNECTRequest() throws IOException {
        int port = url.getPort();

        requests.set(0, HTTP_CONNECT + " " + connectRequestURI(url)
                         + " " + httpVersion, null);
        requests.setIfNotSet("User-Agent", userAgent);

        String host = url.getHost();
        if (port != -1 && port != url.getDefaultPort()) {
            host += ":" + String.valueOf(port);
        }
        requests.setIfNotSet("Host", host);

The logic for deciding when to append ":" and the port number will not append them when it is the default port for the given protocol (80 for http, 443 for https), and thus will generate requests in these formats:

CONNECT example.com:443 HTTP/1.1
User-Agent: Java/1.8.0_232
Host: example.com

CONNECT example.com:80 HTTP/1.1
User-Agent: Java/1.8.0_232
Host: example.com

Note that neither of these request includes the port number in the Host header per the RFC example, causing the the request-target and host header to not match.

Starting in version 2.2, HAproxy implemented strict RFC compliance checking that will reject these requests with BADREQ:

https://github.com/haproxy/haproxy/commit/531b83e039bbe369e4fe6e775e9bfa310d780da1

The only current work-around for this non-compliant behavior by Java is to use the "accept-invalid-http-request" option, which is sub-optimal.

Version-Release number of selected component (if applicable):

Tested under 1.8.0.232.b09-0.el7_7, but this issue exists in all current OpenJDK releases

How reproducible: 100%

Steps to Reproduce:
1. Setup a HAproxy 2.4 http frontend that will accept CONNECT requests 
2. Use HttpURLConnection to generate a CONNECT request
3. Observe the rejection of the CONNECT request by HAproxy

Actual results:

A <BADREQ> is logged by HAproxy due to the mismatch between the authority and the Host header in the request generated by Java:

127.0.0.1:35039 127.0.0.1:80 [28/Dec/2021:04:19:12 +0000] tcp-80 tcp-80/<NOSRV> -1/-1/-1/-1/0 400 0 - - PR-- 1/1/0/0/0 0/0 "<BADREQ>"

and the CONNECT request is rejected by HAproxy with a 400 error:

HTTP/1.1 400 Bad request
Content-length: 90
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>400 Bad request</h1>
Your browser sent an invalid request.
</body></html>

Expected results:

A properly formatted RFC compliant CONNECT request being generated by Java that can be serviced by HAproxy without requiring enabling the "accept-invalid-http-request" option.

Additional info:

RFC compliant behavior should be able to be achieved by removing the port logic and simplifying the sendCONNECTRequest() method:

    /**
     * send a CONNECT request for establishing a tunnel to proxy server
     */
    private void sendCONNECTRequest() throws IOException {

        requests.set(0, HTTP_CONNECT + " " + connectRequestURI(url)
                         + " " + httpVersion, null);
        requests.setIfNotSet("User-Agent", userAgent);
        requests.setIfNotSet("Host", connectRequestURI(url));

Unfortunately there is not a good public conduit to report Java bugs directly to the OpenJDK project for resolution, so hopefully Red Hat can help facilitate this, as this issue has not been fixed in any newer JDK version either, making this a generic JDK issue across multiple supported JDK long-term releases and RHEL versions.

JDK 11: 

https://github.com/openjdk/jdk11/blob/37115c8ea4aff13a8148ee2b8832b20888a5d880/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java#L2197

JDK 17:

https://github.com/openjdk/jdk17u/blob/master/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java#L2301

Comment 4 RHEL Program Management 2023-07-08 07:27:58 UTC
After evaluating this issue, there are no plans to address it further or fix it in an upcoming release.  Therefore, it is being closed.  If plans change such that this issue will be fixed in an upcoming release, then the bug can be reopened.