diff options
author | 2023-09-04 21:37:47 -0300 | |
---|---|---|
committer | 2023-09-04 17:37:47 -0700 | |
commit | f1afd5833874e64822560a834c3484b2aa993656 (patch) | |
tree | b62431b12410dfae2120332c9eb727bafbe79bd2 | |
parent | 5f34c44ec6af175889e26aa5f42c817dd3de61c4 (diff) | |
download | bun-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.zig | 4 | ||||
-rw-r--r-- | src/zlib.zig | 10 | ||||
-rw-r--r-- | test/js/web/fetch/fetch.stream.test.ts | 41 |
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; } |