From 5cd01ea49428da714ef76016decc8ce37295119b Mon Sep 17 00:00:00 2001 From: anusrnm <104902038+anusrnm@users.noreply.github.com> Date: Sun, 29 Jan 2023 16:57:04 +0530 Subject: [PATCH] gzip: fix flag location in header (#17140) --- vlib/compress/gzip/gzip.v | 82 +++++++++++++++++++++++----------- vlib/compress/gzip/gzip_test.v | 16 +++---- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/vlib/compress/gzip/gzip.v b/vlib/compress/gzip/gzip.v index c8cfd25c0..b99ab8cda 100644 --- a/vlib/compress/gzip/gzip.v +++ b/vlib/compress/gzip/gzip.v @@ -47,23 +47,44 @@ pub struct DecompressParams { verify_checksum bool = true } -// decompresses an array of bytes using zlib and returns the decompressed bytes in a new array -// Example: decompressed := gzip.decompress(b)? -pub fn decompress(data []u8, params DecompressParams) ![]u8 { - if data.len < 18 { +pub const ( + reserved_bits = 0b1110_0000 + ftext = 0b0000_0001 + fextra = 0b0000_0100 + fname = 0b0000_1000 + fcomment = 0b0001_0000 + fhcrc = 0b0000_0010 +) + +const min_header_length = 18 + +[noinit] +pub struct GzipHeader { +pub mut: + length int = 10 + extra []u8 + filename []u8 + comment []u8 + modification_time u32 + operating_system u8 +} + +// validate validates the header and returns its details if valid +pub fn validate(data []u8, params DecompressParams) !GzipHeader { + if data.len < gzip.min_header_length { return error('data is too short, not gzip compressed?') } else if data[0] != 0x1f || data[1] != 0x8b { return error('wrong magic numbers, not gzip compressed?') } else if data[2] != 0x08 { return error('gzip data is not compressed with DEFLATE') } - mut header_length := 10 + mut header := GzipHeader{} // parse flags, we ignore most of them, but we still need to parse them // correctly, so we dont accidently decompress something that belongs // to the header - if data[4] & 0b1110_0000 > 0 { // reserved bits + if data[3] & gzip.reserved_bits > 0 { // rfc 1952 2.3.1.2 Compliance // A compliant decompressor must give an error indication if any // reserved bit is non-zero, since such a bit could indicate the @@ -72,40 +93,51 @@ pub fn decompress(data []u8, params DecompressParams) ![]u8 { return error('reserved flags are set, unsupported field detected') } - // if data[4] & 0b0000_0001 {} // FTEXT - if data[4] & 0b0000_0100 > 0 { // FEXTRA, extra data - xlen := data[header_length] - header_length += xlen + 1 + if data[3] & gzip.fextra > 0 { + xlen := data[header.length] + header.extra = data[header.length + 1..header.length + 1 + xlen] + header.length += xlen + 1 } - if data[4] & 0b0000_1000 > 0 { // FNAME, file name + if data[3] & gzip.fname > 0 { // filename is zero-terminated, so skip until we hit a zero byte - for header_length < data.len && data[header_length] != 0x00 { - header_length++ + for header.length < data.len && data[header.length] != 0x00 { + header.filename << data[header.length] + header.length++ } - header_length++ + header.length++ } - if data[4] & 0b0001_0000 > 0 { // FCOMMENT + if data[3] & gzip.fcomment > 0 { // comment is zero-terminated, so skip until we hit a zero byte - for header_length < data.len && data[header_length] != 0x00 { - header_length++ + for header.length < data.len && data[header.length] != 0x00 { + header.comment << data[header.length] + header.length++ } - header_length++ + header.length++ } - if data[4] & 0b0000_0010 > 0 { // FHCRC, flag header crc - if header_length + 12 > data.len { + if data[3] & gzip.fhcrc > 0 { + if header.length + 12 > data.len { return error('data too short') } - checksum_header := crc32.sum(data[..header_length]) - checksum_header_expected := (u32(data[header_length]) << 24) | (u32(data[header_length + 1]) << 16) | (u32(data[ - header_length + 2]) << 8) | data[header_length + 3] + checksum_header := crc32.sum(data[..header.length]) + checksum_header_expected := (u32(data[header.length]) << 24) | (u32(data[header.length + 1]) << 16) | (u32(data[ + header.length + 2]) << 8) | data[header.length + 3] if params.verify_header_checksum && checksum_header != checksum_header_expected { return error('header checksum verification failed') } - header_length += 4 + header.length += 4 } - if header_length + 8 > data.len { + if header.length + 8 > data.len { return error('data too short') } + header.operating_system = data[9] + return header +} + +// decompresses an array of bytes using zlib and returns the decompressed bytes in a new array +// Example: decompressed := gzip.decompress(b)? +pub fn decompress(data []u8, params DecompressParams) ![]u8 { + gzip_header := validate(data, params)! + header_length := gzip_header.length decompressed := compress.decompress(data[header_length..data.len - 8], 0)! length_expected := (u32(data[data.len - 4]) << 24) | (u32(data[data.len - 3]) << 16) | (u32(data[data.len - 2]) << 8) | data[data.len - 1] diff --git a/vlib/compress/gzip/gzip_test.v b/vlib/compress/gzip/gzip_test.v index 232422f2f..87ed44aec 100644 --- a/vlib/compress/gzip/gzip_test.v +++ b/vlib/compress/gzip/gzip_test.v @@ -35,7 +35,7 @@ fn test_gzip_invalid_compression() { fn test_gzip_with_ftext() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0000_0001 // FTEXT + compressed[3] |= ftext decompressed := decompress(compressed)! assert decompressed == uncompressed.bytes() } @@ -43,7 +43,7 @@ fn test_gzip_with_ftext() { fn test_gzip_with_fname() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0000_1000 + compressed[3] |= fname compressed.insert(10, `h`) compressed.insert(11, `i`) compressed.insert(12, 0x00) @@ -54,7 +54,7 @@ fn test_gzip_with_fname() { fn test_gzip_with_fcomment() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0001_0000 + compressed[3] |= fcomment compressed.insert(10, `h`) compressed.insert(11, `i`) compressed.insert(12, 0x00) @@ -65,7 +65,7 @@ fn test_gzip_with_fcomment() { fn test_gzip_with_fname_fcomment() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0001_1000 + compressed[3] |= (fname | fcomment) compressed.insert(10, `h`) compressed.insert(11, `i`) compressed.insert(12, 0x00) @@ -79,7 +79,7 @@ fn test_gzip_with_fname_fcomment() { fn test_gzip_with_fextra() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0000_0100 + compressed[3] |= fextra compressed.insert(10, 2) compressed.insert(11, `h`) compressed.insert(12, `i`) @@ -90,7 +90,7 @@ fn test_gzip_with_fextra() { fn test_gzip_with_hcrc() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0000_0010 + compressed[3] |= fhcrc checksum := crc32.sum(compressed[..10]) compressed.insert(10, u8(checksum >> 24)) compressed.insert(11, u8(checksum >> 16)) @@ -103,7 +103,7 @@ fn test_gzip_with_hcrc() { fn test_gzip_with_invalid_hcrc() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b0000_0010 + compressed[3] |= fhcrc checksum := crc32.sum(compressed[..10]) compressed.insert(10, u8(checksum >> 24)) compressed.insert(11, u8(checksum >> 16)) @@ -129,6 +129,6 @@ fn test_gzip_with_invalid_length() { fn test_gzip_with_invalid_flags() { uncompressed := 'Hello world!' mut compressed := compress(uncompressed.bytes())! - compressed[4] |= 0b1000_0000 + compressed[3] |= 0b1000_0000 assert_decompress_error(compressed, 'reserved flags are set, unsupported field detected')! } -- 2.30.2