Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node:zlib fails to decompress brotli data larger than 4096 bytes #19816

Closed
honnip opened this issue Jul 13, 2023 · 4 comments
Closed

node:zlib fails to decompress brotli data larger than 4096 bytes #19816

honnip opened this issue Jul 13, 2023 · 4 comments

Comments

@honnip
Copy link

honnip commented Jul 13, 2023

Steps to reproduce

import { brotliCompressSync, brotliDecompressSync } from "node:zlib";

const a = "a".repeat(4097);
const compressed = brotliCompressSync(a);
const decompressed = brotliDecompressSync(compressed); // error: Uncaught TypeError: Failed to decompress

This works with "a".repeat(4096)

version

$ deno -V
deno 1.35.0
@honnip honnip changed the title Brotli (de)compression of data larger than 4096 bytes with node:zlib fails node:zlib fails to decompress brotli data larger than 4096 bytes Jul 14, 2023
@lachrimae
Copy link
Contributor

lachrimae commented Aug 26, 2023

I have determined why this happens. The Rust code FFIs out to this C function. On line 2283, you can see that all non-success results are coerced to the same generic error code BROTLI_DECODER_RESULT_ERROR. On the Rust side, we only attempt to double the buffer size when we get a BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT result from the C code. But as mentioned, that result is always coerced away to the generic case.

Possible solutions are to upstream a change to google/brotli that stops coercing errors to the same generic case. Or to use CBrotliDecoderDecompressStream, which does not perform this error-coercing step.

@lachrimae
Copy link
Contributor

lachrimae commented Aug 27, 2023

Upstreaming seems like a bad idea, this has been the API for something like 8 years and it's documented, so not a mistake.

When I rewrite brotli_decompress as

fn brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> {
  let mut output = Vec::with_capacity(4096);
  let mut decompressor = Decompressor::new(buffer, buffer.len());
  decompressor.read_to_end(&mut output)?;
  Ok(output.into())
}

it passes the test in this issue. @littledivy is there a reason you wrote new unsafe FFI code here instead of using the Rust API in the brotli crate?

@lachrimae
Copy link
Contributor

This is the PR where this code was introduced by the way: #19223

@lachrimae
Copy link
Contributor

Okay I have looked a little bit more closely into the brotli crate being used, and I realized it's actually Dropbox's rust-brotli library. The rust-brotli crate provides a C FFI interface, allowing rust-brotli to replace google/brotli as a drop-in replacement. What we are doing here in Deno is calling into the rust-brotli C FFI layer, which is immediately calling back into Rust. Everything I said about error types being coerced is true, but it's happening here in rust-brotli.

We should definitely switch this over to the Rust API because the unsafe C API is just a layer of indirection on top of Rust.

bartlomieju pushed a commit to bartlomieju/deno that referenced this issue Sep 8, 2023
…es (denoland#20301)

Fixes denoland#19816

In that issue, I suggest switching over the other brotli functionality
to the Rust API provided by the `brotli` crate. Here, I only do that
with the `brotli_decompress` function to fix the bug with buffers longer
than 4096 bytes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants