aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ciro Spaciari <ciro.spaciari@gmail.com> 2023-09-04 21:37:47 -0300
committerGravatar GitHub <noreply@github.com> 2023-09-04 17:37:47 -0700
commitf1afd5833874e64822560a834c3484b2aa993656 (patch)
treeb62431b12410dfae2120332c9eb727bafbe79bd2
parent5f34c44ec6af175889e26aa5f42c817dd3de61c4 (diff)
downloadbun-f1afd5833874e64822560a834c3484b2aa993656.tar.gz
bun-f1afd5833874e64822560a834c3484b2aa993656.tar.zst
bun-f1afd5833874e64822560a834c3484b2aa993656.zip
fix zlib deflate on fetch (#4483)
* fix zlib deflate on fetch * mention issue on test * more tests * oops
-rw-r--r--src/http_client_async.zig4
-rw-r--r--src/zlib.zig10
-rw-r--r--test/js/web/fetch/fetch.stream.test.ts41
3 files changed, 47 insertions, 8 deletions
diff --git a/src/http_client_async.zig b/src/http_client_async.zig
index 8a0e6548c..160a35716 100644
--- a/src/http_client_async.zig
+++ b/src/http_client_async.zig
@@ -1155,9 +1155,9 @@ pub const InternalState = struct {
// TODO: add br support today we support gzip and deflate only
// zlib.MAX_WBITS = 15
// to (de-)compress deflate format, use wbits = -zlib.MAX_WBITS
- // to (de-)compress zlib format, use wbits = zlib.MAX_WBITS
+ // to (de-)compress deflate format with headers we use wbits = 0 (we can detect the first byte using 120)
// to (de-)compress gzip format, use wbits = zlib.MAX_WBITS | 16
- .windowBits = if (this.encoding == Encoding.gzip) Zlib.MAX_WBITS | 16 else -Zlib.MAX_WBITS,
+ .windowBits = if (this.encoding == Encoding.gzip) Zlib.MAX_WBITS | 16 else (if (buffer.len > 1 and buffer[0] == 120) 0 else -Zlib.MAX_WBITS),
},
);
this.zlib_reader = reader;
diff --git a/src/zlib.zig b/src/zlib.zig
index 6b2e9dc48..090d0f3a0 100644
--- a/src/zlib.zig
+++ b/src/zlib.zig
@@ -439,7 +439,8 @@ pub const ZlibReaderArrayList = struct {
}
pub fn end(this: *ZlibReader) void {
- if (this.state == State.Inflating) {
+ // always free with `inflateEnd`
+ if (this.state != State.End) {
_ = inflateEnd(&this.zlib);
this.state = State.End;
}
@@ -846,7 +847,7 @@ pub const ZlibCompressorArrayList = struct {
}
pub fn end(this: *ZlibCompressor) void {
- if (this.state == State.Inflating) {
+ if (this.state != State.End) {
_ = deflateEnd(&this.zlib);
this.state = State.End;
}
@@ -981,13 +982,13 @@ pub const ZlibCompressorArrayList = struct {
switch (rc) {
ReturnCode.StreamEnd => {
- this.state = State.End;
this.list.items.len = this.zlib.total_out;
-
this.end();
+
return;
},
ReturnCode.MemError => {
+ this.end();
this.state = State.Error;
return error.OutOfMemory;
},
@@ -998,6 +999,7 @@ pub const ZlibCompressorArrayList = struct {
ReturnCode.VersionError,
ReturnCode.ErrNo,
=> {
+ this.end();
this.state = State.Error;
return error.ZlibError;
},
diff --git a/test/js/web/fetch/fetch.stream.test.ts b/test/js/web/fetch/fetch.stream.test.ts
index dc082fd2f..49cc0dd6a 100644
--- a/test/js/web/fetch/fetch.stream.test.ts
+++ b/test/js/web/fetch/fetch.stream.test.ts
@@ -3,7 +3,7 @@ import { readFileSync } from "fs";
import { join } from "path";
import { describe, expect, it } from "bun:test";
import { gcTick } from "harness";
-
+import zlib from "zlib";
const fixtures = {
"fixture": readFileSync(join(import.meta.dir, "fixture.html")),
"fixture.png": readFileSync(join(import.meta.dir, "fixture.png")),
@@ -17,6 +17,40 @@ const smallText = Buffer.from("Hello".repeat(16));
const empty = Buffer.alloc(0);
describe("fetch() with streaming", () => {
+ it("can deflate with and without headers #4478", async () => {
+ let server: Server | null = null;
+ try {
+ server = Bun.serve({
+ port: 0,
+ fetch(req) {
+ if (req.url.endsWith("/with_headers")) {
+ const content = zlib.deflateSync(Buffer.from("Hello, World"));
+ return new Response(content, {
+ headers: {
+ "Content-Type": "text/plain",
+ "Content-Encoding": "deflate",
+ "Access-Control-Allow-Origin": "*",
+ },
+ });
+ }
+ const content = zlib.deflateRawSync(Buffer.from("Hello, World"));
+ return new Response(content, {
+ headers: {
+ "Content-Type": "text/plain",
+ "Content-Encoding": "deflate",
+ "Access-Control-Allow-Origin": "*",
+ },
+ });
+ },
+ });
+ const url = `http://${server.hostname}:${server.port}/`;
+ expect(await fetch(`${url}with_headers`).then(res => res.text())).toBe("Hello, World");
+ expect(await fetch(url).then(res => res.text())).toBe("Hello, World");
+ } finally {
+ server?.stop();
+ }
+ });
+
it("stream still works after response get out of scope", async () => {
let server: Server | null = null;
try {
@@ -467,12 +501,13 @@ describe("fetch() with streaming", () => {
}
}
- type CompressionType = "no" | "gzip" | "deflate" | "br";
+ type CompressionType = "no" | "gzip" | "deflate" | "br" | "deflate_with_headers";
type TestType = { headers: Record<string, string>; compression: CompressionType; skip?: boolean };
const types: Array<TestType> = [
{ headers: {}, compression: "no" },
{ headers: { "Content-Encoding": "gzip" }, compression: "gzip" },
{ headers: { "Content-Encoding": "deflate" }, compression: "deflate" },
+ { headers: { "Content-Encoding": "deflate" }, compression: "deflate_with_headers" },
// { headers: { "Content-Encoding": "br" }, compression: "br", skip: true }, // not implemented yet
];
@@ -482,6 +517,8 @@ describe("fetch() with streaming", () => {
return Bun.gzipSync(data);
case "deflate":
return Bun.deflateSync(data);
+ case "deflate_with_headers":
+ return zlib.deflateSync(data);
default:
return data;
}