Skip to content

On-the-fly deflate compression sends raw DEFLATE instead of the zlib format #763

Description

@frenzzy

Search for duplicate issues

  • I already searched, and this issue is not a duplicate.

Issue scope

Other: on-the-fly compression (compression)

Describe the bug

With compression = true (the default), a response compressed on the fly for Accept-Encoding: deflate is sent with Content-Encoding: deflate, but its body is a raw DEFLATE stream (RFC 1951). The HTTP deflate content coding is the zlib format (RFC 1950): a 2-byte header (0x78 ..), the DEFLATE data and an Adler-32 checksum (RFC 9110, section 8.4.1.2).

Cause: src/compression.rs uses async_compression::tokio::bufread::DeflateEncoder, which writes raw DEFLATE, instead of ZlibEncoder:

use async_compression::tokio::bufread::DeflateEncoder;

let body = crate::body::stream(ReaderStream::new(DeflateEncoder::with_quality(
StreamReader::new(body.into_data_stream()),
level,
)));

Low severity: browsers and curl accept raw DEFLATE for deflate as a legacy workaround, and they normally prefer gzip, br or zstd anyway. Strict zlib decoders (for example Python's zlib.decompress or other HTTP client libraries) fail on the body.

How to reproduce it

mkdir public
for i in $(seq 50); do echo 'body { color: red; }'; done > public/main.css

docker run --rm -d --name sws-deflate -p 8787:8787 -v "$PWD/public:/public:ro" \
  joseluisq/static-web-server:3.0.0-beta.1 --root /public

curl -sS -o body.bin -D - -H 'Accept-Encoding: deflate' http://127.0.0.1:8787/main.css | grep -iE '^(HTTP|content-encoding)'
head -c 16 body.bin | xxd
python3 -c '
import zlib
d = open("body.bin", "rb").read()
try:
    zlib.decompress(d); print("zlib (RFC 1950): ok")
except zlib.error as e:
    print("zlib (RFC 1950):", e)
print("raw DEFLATE (RFC 1951):", len(zlib.decompress(d, -15)), "bytes")
'

Observed:

HTTP/1.1 200 OK
content-encoding: deflate
00000000: edc8 b10d 0020 0804 c0de 297e 0edd 06b1  ..... ....)~....
zlib (RFC 1950): Error -3 while decompressing data: incorrect header check
raw DEFLATE (RFC 1951): 1050 bytes

Expected behavior

The body of a Content-Encoding: deflate response is a zlib stream: it starts with 0x78 and decodes with a zlib decoder.

Complementary information

Suggested fix: use async_compression::tokio::bufread::ZlibEncoder (the zlib feature of async-compression) for deflate, as tower-http and actix-web do. The 2.x branch has the same encoder (src/compression.rs, DeflateEncoder).

Pre-compressed files (compression-static) are a separate issue, #761.

Build target

Docker linux/arm64

Environment and specs

  • static-web-server: v3.0.0-beta.1 (joseluisq/static-web-server:3.0.0-beta.1, digest sha256:6e453304835955e47fc62dce4ff7050c707bce2d82b5f9f4687ba588df81bebd) and master 66b1288
  • OS: macOS host running the Linux container
  • Arch: ARM64
  • Client: curl 8.7.1, Python 3.14 zlib

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions