Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,18 @@
|
||||
## Description
|
||||
|
||||
`compress.gzip` is a module that assists in the compression and
|
||||
decompression of binary data using `gzip` compression
|
||||
|
||||
|
||||
## Examples:
|
||||
|
||||
```v
|
||||
import compress.gzip
|
||||
|
||||
fn main() {
|
||||
uncompressed := 'Hello world!'
|
||||
compressed := gzip.compress(uncompressed.bytes())!
|
||||
decompressed := gzip.decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
// [rfc1952](https://datatracker.ietf.org/doc/html/rfc1952) compliant
|
||||
// gzip compression/decompression
|
||||
|
||||
module gzip
|
||||
|
||||
import compress.deflate
|
||||
|
||||
// CompressFlags
|
||||
@[deprecated: 'never used']
|
||||
@[deprecated_after: '2026-07-31']
|
||||
@[flag]
|
||||
pub enum CompressFlags {
|
||||
// The low 12 bits will be overwritten by `compression_level`
|
||||
compression_level_overwrite_flag01
|
||||
compression_level_overwrite_flag02
|
||||
compression_level_overwrite_flag03
|
||||
compression_level_overwrite_flag04
|
||||
compression_level_overwrite_flag05
|
||||
compression_level_overwrite_flag06
|
||||
compression_level_overwrite_flag07
|
||||
compression_level_overwrite_flag08
|
||||
compression_level_overwrite_flag09
|
||||
compression_level_overwrite_flag10
|
||||
compression_level_overwrite_flag11
|
||||
compression_level_overwrite_flag12
|
||||
write_zlib_header
|
||||
compute_adler32
|
||||
greedy_parsing_flag
|
||||
nondeterministic_parsing_flag
|
||||
rle_matches
|
||||
filter_matches
|
||||
force_all_static_blocks
|
||||
force_all_raw_blocks
|
||||
}
|
||||
|
||||
// CompressParams set compression_level for compression:
|
||||
@[deprecated: 'never used']
|
||||
@[deprecated_after: '2026-07-31']
|
||||
@[params]
|
||||
pub struct CompressParams {
|
||||
pub:
|
||||
compression_level int
|
||||
flags CompressFlags
|
||||
}
|
||||
|
||||
// compress compresses an array of bytes using gzip and returns the compressed bytes in a new array.
|
||||
pub fn compress(data []u8) ![]u8 {
|
||||
// Delegate to deflate.compress_gzip() which implements RFC 1952
|
||||
return deflate.compress_gzip(data)
|
||||
}
|
||||
|
||||
// DecompressFlags
|
||||
// N.B.: only retained for API compatibility.
|
||||
@[deprecated: 'never used']
|
||||
@[deprecated_after: '2026-07-31']
|
||||
@[flag]
|
||||
pub enum DecompressFlags {
|
||||
parse_zlib_header
|
||||
has_more_input
|
||||
using_non_wrapping_output_buf
|
||||
compute_adler32
|
||||
}
|
||||
|
||||
// DecompressParams controls gzip decompression behavior.
|
||||
// N.B.: only retained for API compatibility.
|
||||
@[deprecated: 'never used']
|
||||
@[deprecated_after: '2026-07-31']
|
||||
@[params]
|
||||
pub struct DecompressParams {
|
||||
pub:
|
||||
verify_header_checksum bool = true
|
||||
verify_length bool = true
|
||||
verify_checksum bool = true
|
||||
flags DecompressFlags
|
||||
}
|
||||
|
||||
// validate validates the gzip header of data and returns its parsed details if valid.
|
||||
// N.B.: only retained for API compatibility, all validation is now performed by the deflate backend.
|
||||
// The returned header details are not used by the decompressor.
|
||||
// TODO: remove after the deprecation period.
|
||||
@[deprecated: 'never used']
|
||||
@[deprecated_after: '2026-07-31']
|
||||
pub fn validate(data []u8, _ DecompressParams) !deflate.GzipHeader {
|
||||
return deflate.validate_gzip_header(data)!
|
||||
}
|
||||
|
||||
// decompress decompresses a gzip stream and returns the decompressed bytes in a new array.
|
||||
pub fn decompress(data []u8) ![]u8 {
|
||||
return deflate.decompress_gzip(data)
|
||||
}
|
||||
|
||||
// decompress_with_callback decompresses a gzip stream (RFC 1952) using a callback for chunked delivery.
|
||||
// The callback receives chunks of decompressed data and should return the chunk length to continue, or 0 to abort.
|
||||
// Returns the total decompressed length.
|
||||
pub fn decompress_with_callback(data []u8, cb deflate.ChunkCallback, userdata voidptr) !int {
|
||||
deflate.validate_gzip_header(data)!
|
||||
return deflate.decompress_with_callback(data, cb, userdata)
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
module gzip
|
||||
|
||||
import hash.crc32
|
||||
import os
|
||||
|
||||
const test_ftext = u8(0b0000_0001)
|
||||
const test_fhcrc = u8(0b0000_0010)
|
||||
const test_fextra = u8(0b0000_0100)
|
||||
const test_fname = u8(0b0000_1000)
|
||||
const test_fcomment = u8(0b0001_0000)
|
||||
const samples_folder = os.join_path(os.dir(@FILE), 'samples')
|
||||
|
||||
fn test_gzip() {
|
||||
uncompressed := 'Hello world!'
|
||||
compressed := compress(uncompressed.bytes())!
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn assert_decompress_error(data []u8, reason string) ! {
|
||||
decompress(data) or {
|
||||
assert err.msg() == reason
|
||||
return
|
||||
}
|
||||
return error('did not error')
|
||||
}
|
||||
|
||||
fn test_gzip_invalid_too_short() {
|
||||
assert_decompress_error([]u8{}, 'invalid gzip stream: too short')!
|
||||
}
|
||||
|
||||
fn test_gzip_invalid_magic_numbers() {
|
||||
assert_decompress_error([]u8{len: 100}, 'invalid gzip stream: bad magic')!
|
||||
}
|
||||
|
||||
fn test_gzip_invalid_compression() {
|
||||
mut data := []u8{len: 100}
|
||||
data[0] = 0x1f
|
||||
data[1] = 0x8b
|
||||
assert_decompress_error(data, 'invalid gzip stream: unsupported compression method')!
|
||||
}
|
||||
|
||||
fn test_gzip_with_ftext() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_ftext
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_fname() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_fname
|
||||
compressed.insert(10, `h`)
|
||||
compressed.insert(11, `i`)
|
||||
compressed.insert(12, 0x00)
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_fcomment() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_fcomment
|
||||
compressed.insert(10, `h`)
|
||||
compressed.insert(11, `i`)
|
||||
compressed.insert(12, 0x00)
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_fname_fcomment() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= (test_fname | test_fcomment)
|
||||
compressed.insert(10, `h`)
|
||||
compressed.insert(11, `i`)
|
||||
compressed.insert(12, 0x00)
|
||||
compressed.insert(10, `h`)
|
||||
compressed.insert(11, `i`)
|
||||
compressed.insert(12, 0x00)
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_fextra() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_fextra
|
||||
// XLEN is 2-byte little-endian value
|
||||
xlen := u16(2)
|
||||
compressed.insert(10, u8(xlen))
|
||||
compressed.insert(11, u8(xlen >> 8))
|
||||
compressed.insert(12, `h`)
|
||||
compressed.insert(13, `i`)
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_hcrc() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_fhcrc
|
||||
// FHCRC is 2-byte CRC-16 (low 16 bits of CRC32) in little-endian format
|
||||
checksum := crc32.sum(compressed[..10])
|
||||
crc16 := u16(checksum & 0xffff)
|
||||
compressed.insert(10, u8(crc16))
|
||||
compressed.insert(11, u8(crc16 >> 8))
|
||||
decompressed := decompress(compressed)!
|
||||
assert decompressed == uncompressed.bytes()
|
||||
}
|
||||
|
||||
fn test_gzip_with_invalid_hcrc() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= test_fhcrc
|
||||
// FHCRC is 2-byte CRC-16 (low 16 bits of CRC32) in little-endian format
|
||||
checksum := crc32.sum(compressed[..10])
|
||||
crc16 := u16(checksum & 0xffff)
|
||||
compressed.insert(10, u8(crc16))
|
||||
compressed.insert(11, u8((crc16 >> 8) + 1)) // corrupt high byte
|
||||
assert_decompress_error(compressed, 'invalid gzip stream: header crc16 mismatch')!
|
||||
}
|
||||
|
||||
fn test_gzip_with_invalid_checksum() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[compressed.len - 5] += 1
|
||||
assert_decompress_error(compressed, 'invalid gzip stream: crc32 mismatch')!
|
||||
}
|
||||
|
||||
fn test_gzip_with_invalid_length() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[compressed.len - 1] += 1
|
||||
assert_decompress_error(compressed, 'invalid gzip stream: size mismatch')!
|
||||
}
|
||||
|
||||
fn test_gzip_with_invalid_flags() {
|
||||
uncompressed := 'Hello world!'
|
||||
mut compressed := compress(uncompressed.bytes())!
|
||||
compressed[3] |= 0b1000_0000
|
||||
assert_decompress_error(compressed, 'invalid gzip stream: reserved flags set')!
|
||||
}
|
||||
|
||||
fn test_gzip_decompress_callback() {
|
||||
uncompressed := '321323'.repeat(10_000)
|
||||
gz := compress(uncompressed.bytes())!
|
||||
mut size := 0
|
||||
mut ref := &size
|
||||
decoded := decompress_with_callback(gz, fn (chunk []u8, ref &int) int {
|
||||
unsafe {
|
||||
*ref += chunk.len
|
||||
}
|
||||
return chunk.len
|
||||
}, ref)!
|
||||
assert decoded == size
|
||||
assert decoded == uncompressed.len
|
||||
}
|
||||
|
||||
fn test_gzip_decompress_callback_rejects_non_gzip() {
|
||||
z := [u8(0x78), 0x9c, 0x03, 0x00, 0x00, 0x00, 0x01]
|
||||
decompress_with_callback(z, fn (chunk []u8, _ voidptr) int {
|
||||
return chunk.len
|
||||
}, unsafe { nil }) or {
|
||||
assert err.msg() == 'invalid gzip stream: too short'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn s(fname string) string {
|
||||
return os.join_path(samples_folder, fname)
|
||||
}
|
||||
|
||||
fn read_and_decode_file(fpath string) !([]u8, string) {
|
||||
compressed := os.read_bytes(fpath)!
|
||||
decoded := decompress(compressed)!
|
||||
content := decoded.bytestr()
|
||||
return compressed, content
|
||||
}
|
||||
|
||||
fn test_reading_and_decoding_a_known_gziped_file() {
|
||||
compressed, content := read_and_decode_file(s('known.gz'))!
|
||||
assert compressed#[0..3] == [u8(31), 139, 8]
|
||||
assert compressed#[-5..] == [u8(127), 115, 1, 0, 0]
|
||||
assert content.contains('## Description')
|
||||
assert content.contains('## Examples:')
|
||||
assert content.ends_with('```\n')
|
||||
}
|
||||
|
||||
fn test_decoding_all_samples_files() {
|
||||
for gz_file in os.walk_ext(samples_folder, '.gz') {
|
||||
_, content := read_and_decode_file(gz_file)!
|
||||
assert content.len > 0, 'decoded content should not be empty: `${content}`'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_reading_gzip_files_compressed_with_different_options() {
|
||||
_, content1 := read_and_decode_file(s('readme_level_1.gz'))!
|
||||
_, content5 := read_and_decode_file(s('readme_level_5.gz'))!
|
||||
_, content9 := read_and_decode_file(s('readme_level_9.gz'))!
|
||||
_, content9_rsyncable := read_and_decode_file(s('readme_level_9_rsyncable.gz'))!
|
||||
assert content9_rsyncable == content9
|
||||
assert content9 == content5
|
||||
assert content5 == content1
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user