diff --git a/Tests/LibCompress/TestBrotli.cpp b/Tests/LibCompress/TestBrotli.cpp index e5e14ebf032..2617863759f 100644 --- a/Tests/LibCompress/TestBrotli.cpp +++ b/Tests/LibCompress/TestBrotli.cpp @@ -80,6 +80,11 @@ TEST_CASE(brotli_single_z) run_test("single-z.txt"sv); } +TEST_CASE(brotli_single_x) +{ + run_test("single-x.txt"sv); +} + TEST_CASE(brotli_decompress_zero_one_bin) { // This makes sure that the tests will run both on target and in Lagom. diff --git a/Tests/LibCompress/brotli-test-files/single-x.txt b/Tests/LibCompress/brotli-test-files/single-x.txt new file mode 100644 index 00000000000..500c0709ca2 --- /dev/null +++ b/Tests/LibCompress/brotli-test-files/single-x.txt @@ -0,0 +1 @@ +X \ No newline at end of file diff --git a/Tests/LibCompress/brotli-test-files/single-x.txt.br b/Tests/LibCompress/brotli-test-files/single-x.txt.br new file mode 100644 index 00000000000..2a44b5def22 Binary files /dev/null and b/Tests/LibCompress/brotli-test-files/single-x.txt.br differ diff --git a/Userland/Libraries/LibCompress/Brotli.cpp b/Userland/Libraries/LibCompress/Brotli.cpp index 2f150fea816..16344f09469 100644 --- a/Userland/Libraries/LibCompress/Brotli.cpp +++ b/Userland/Libraries/LibCompress/Brotli.cpp @@ -589,22 +589,59 @@ ErrorOr BrotliDecompressionStream::read(Bytes output_buffer) if (m_read_final_block) break; + // RFC 7932 section 9.1 + // + // 1 bit: ISLAST, set to 1 if this is the last meta-block m_read_final_block = TRY(m_input_stream.read_bit()); if (m_read_final_block) { + // 1 bit: ISLASTEMPTY, if set to 1, the meta-block is empty; this + // field is only present if ISLAST bit is set -- if it is 1, + // then the meta-block and the brotli stream ends at that + // bit, with any remaining bits in the last byte of the + // compressed stream filled with zeros (if the fill bits are + // not zero, then the stream should be rejected as invalid) bool is_last_block_empty = TRY(m_input_stream.read_bit()); // If the last block is empty we are done decompressing if (is_last_block_empty) break; } + // 2 bits: MNIBBLES, number of nibbles to represent the uncompressed + // length size_t size_number_of_nibbles = TRY(read_size_number_of_nibbles()); + + // If MNIBBLES is 0, the meta-block is empty, i.e., it does + // not generate any uncompressed data. In this case, the + // rest of the meta-block has the following format: if (size_number_of_nibbles == 0) { - // This block only contains meta-data + + // 1 bit: reserved, must be zero bool reserved = TRY(m_input_stream.read_bit()); if (reserved) return Error::from_string_literal("invalid reserved bit"); + // 2 bits: MSKIPBYTES, number of bytes to represent + // metadata length + // + // MSKIPBYTES * 8 bits: MSKIPLEN - 1, where MSKIPLEN is + // the number of metadata bytes; this field is + // only present if MSKIPBYTES is positive; + // otherwise, MSKIPLEN is 0 (if MSKIPBYTES is + // greater than 1, and the last byte is all + // zeros, then the stream should be rejected as + // invalid) size_t skip_bytes = TRY(m_input_stream.read_bits(2)); + if (skip_bytes == 0) { + // 0..7 bits: fill bits until the next byte boundary, + // must be all zeros + u8 remainder = m_input_stream.align_to_byte_boundary(); + if (remainder != 0) + return Error::from_string_literal("remainder bits are non-zero"); + continue; + } + + // MSKIPLEN bytes of metadata, not part of the + // uncompressed data or the sliding window size_t skip_length = 1 + TRY(m_input_stream.read_bits(8 * skip_bytes)); u8 remainder = m_input_stream.align_to_byte_boundary(); @@ -619,6 +656,8 @@ ErrorOr BrotliDecompressionStream::read(Bytes output_buffer) auto metadata_bytes = TRY(m_input_stream.read(temp_bytes_slice)); if (metadata_bytes.is_empty()) return Error::from_string_literal("eof"); + if (metadata_bytes.last() == 0) + return Error::from_string_literal("invalid stream"); skip_length -= metadata_bytes.size(); } @@ -626,6 +665,13 @@ ErrorOr BrotliDecompressionStream::read(Bytes output_buffer) } size_t uncompressed_size = 1 + TRY(m_input_stream.read_bits(4 * size_number_of_nibbles)); + + // 1 bit: ISUNCOMPRESSED, if set to 1, any bits of compressed data + // up to the next byte boundary are ignored, and the rest of + // the meta-block contains MLEN bytes of literal data; this + // field is only present if the ISLAST bit is not set (if the + // ignored bits are not all zeros, the stream should be + // rejected as invalid) bool is_uncompressed = false; if (!m_read_final_block) is_uncompressed = TRY(m_input_stream.read_bit());