Affects: spring-web 5.3.27

This class writes the content length in two places: org.springframework.http.server.ServletServerHttpResponse#writeHeaders

    private void writeHeaders() {
        if (!this.headersWritten) {
            getHeaders().forEach((headerName, headerValues) -> {
                for (String headerValue : headerValues) {
                    this.servletResponse.addHeader(headerName, headerValue);
                }
            });
            // HttpServletResponse exposes some headers as properties: we should include those if not already present
            if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
                this.servletResponse.setContentType(this.headers.getContentType().toString());
            }
            if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
                    this.headers.getContentType().getCharset() != null) {
                this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
            }
            long contentLength = getHeaders().getContentLength();
            if (contentLength != -1) {
                this.servletResponse.setContentLengthLong(contentLength);
            }
            this.headersWritten = true;
        }
    }

One time in the for loop on top without any validation and a second time in the setContentLengthLong with validation. I guess the first one is unintentional.

What I try to do: I have another filter before, that does compression (gzip and brotli) and compression is required to have a chunked encoding. The servlet engine switches to chunked encoding, if it does not have a content length and you send out more than one chunk or flush. If a content-length is set, the server cannot respond with a chunked encoding and the response will hang in a load balancer as it would expect more bytes than it gets due to the compression.

For that, the code blocks access to setContentLengthLong and setContentLengthLong

Workaround: Catch set and add header calls as well:


    @Override
    public void setHeader(String name, String value) {
        if ("Content-Length".equalsIgnoreCase(name)) return;
        super.setHeader(name, value);
    }

    @Override
    public void addHeader(String name, String value) {
        if ("Content-Length".equalsIgnoreCase(name)) return;
        super.addHeader(name, value);
    }

Comment From: bclozel

Any Spring controller could get injected with the HttpServletResponse and set a content-length header the same way. I think your filter should wrap the Servlet response and guard against these cases anyway.