Files
bun.sh/src/http/zlib.zig
robobun 72490281e5 fix: handle empty chunked gzip responses correctly (#22360)
## Summary
Fixes #18413 - Empty chunked gzip responses were causing `Decompression
error: ShortRead`

## The Issue
When a server sends an empty response with `Content-Encoding: gzip` and
`Transfer-Encoding: chunked`, Bun was throwing a `ShortRead` error. This
occurred because the code was checking if `avail_in == 0` (no input
data) and immediately returning an error, without attempting to
decompress what could be a valid empty gzip stream.

## The Fix
Instead of checking `avail_in == 0` before calling `inflate()`, we now:
1. Always call `inflate()` even when `avail_in == 0` 
2. Check the return code from `inflate()`
3. If it returns `BufError` with `avail_in == 0`, then we truly need
more data and return `ShortRead`
4. If it returns `StreamEnd`, it was a valid empty gzip stream and we
finish successfully

This approach correctly distinguishes between "no data yet" and "valid
empty gzip stream".

## Why This Works
- A valid empty gzip stream still has headers and trailers (~20 bytes)
- The zlib `inflate()` function can handle empty streams correctly  
- `BufError` with `avail_in == 0` specifically means "need more input
data"

## Test Plan
 Added regression test in `test/regression/issue/18413.test.ts`
covering:
- Empty chunked gzip response
- Empty non-chunked gzip response  
- Empty chunked response without gzip

 Verified all existing gzip-related tests still pass
 Tested with the original failing case from the issue

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-03 18:57:39 -07:00

35 lines
1010 B
Zig

fn initMutableString(allocator: std.mem.Allocator) anyerror!MutableString {
return MutableString.initEmpty(allocator);
}
const BufferPool = bun.ObjectPool(MutableString, initMutableString, false, 4);
pub fn get(allocator: std.mem.Allocator) *MutableString {
return &BufferPool.get(allocator).data;
}
pub fn put(mutable: *MutableString) void {
mutable.reset();
var node: BufferPool.Node = @fieldParentPtr("data", mutable);
node.release();
}
pub fn decompress(compressed_data: []const u8, output: *MutableString, allocator: std.mem.Allocator) Zlib.ZlibError!void {
var reader = try Zlib.ZlibReaderArrayList.initWithOptionsAndListAllocator(
compressed_data,
&output.list,
output.allocator,
allocator,
.{
.windowBits = 15 + 32,
},
);
try reader.readAll(true);
reader.deinit();
}
const Zlib = @import("../zlib.zig");
const std = @import("std");
const bun = @import("bun");
const MutableString = bun.MutableString;