mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
* stdlib: implement `std::compression::zip` and `std::compression::deflate` - C3 implementation of DEFLATE (RFC 1951) and ZIP archive handling. - Support for reading and writing archives using STORE and DEFLATE methods. - Decompression supports both fixed and dynamic Huffman blocks. - Compression using greedy LZ77 matching. - Zero dependencies on libc. - Stream-based entry reading and writing. - Full unit test coverage. NOTE: This is an initial implementation. Future improvements could be: - Optimization of the LZ77 matching (lazy matching). - Support for dynamic Huffman blocks in compression. - ZIP64 support for large files/archives. - Support for encryption and additional compression methods. * optimizations+refactoring deflate: - replace linear search with hash-based match finding. - implement support for dynamic Huffman blocks using the Package-Merge algorithm. - add streaming decompression. - add buffered StreamBitReader. zip: - add ZIP64 support. - add CP437 and UTF-8 filename encoding detection. - add DOS date/time conversion and timestamp preservation. - add ZipEntryReader for streaming entry reads. - implement ZipArchive.extract and ZipArchive.recover helpers. other: - Add `set_modified_time` to std::io; - Add benchmarks and a few more unit tests. * zip: add archive comment support add tests * forgot to rename the benchmark :( * detect utf8 names on weird zips fix method not passed to open_writer * another edge case where directory doesn't end with / * testing utilities - detect encrypted zip - `ZipArchive.open_writer` default to DEFLATE * fix zip64 creation, add tests * fix ZIP header endianness for big-endian compatibility Update ZipLFH, ZipCDH, ZipEOCD, Zip64EOCD, and Zip64Locator structs to use little-endian bitstruct types from std::core::bitorder * fix ZipEntryReader position tracking and seek logic ZIP_METHOD_STORE added a test to track this * add package-merge algorithm attribution Thanks @konimarti * standalone deflate_benchmark.c3 against `miniz` * fix integer overflows, leaks and improve safety * a few safety for 32-bit systems and tests * deflate compress optimization * improve match finding, hash updates, and buffer usage * use ulong for zip offsets * style changes (#18) * style changes * update tests * style changes in `deflate.c3` * fix typo * Allocator first. Some changes to deflate to use `copy_to` * Fix missing conversion on 32 bits. * Fix deflate stream. Formatting. Prefer switch over if-elseif * - Stream functions now use long/ulong rather than isz/usz for seek/available. - `instream.seek` is replaced by `set_cursor` and `cursor`. - `instream.available`, `cursor` etc are long/ulong rather than isz/usz to be correct on 32-bit. * Update to constdef * Fix test --------- Co-authored-by: Book-reader <thevoid@outlook.co.nz> Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
195 lines
4.2 KiB
Plaintext
195 lines
4.2 KiB
Plaintext
<*
|
|
Compare `C3 zip` vs `7z` extraction
|
|
External dependencies: 7z, diff
|
|
*>
|
|
module verify_zip;
|
|
import std;
|
|
import libc;
|
|
|
|
fn int main(String[] args)
|
|
{
|
|
if (args.len < 2)
|
|
{
|
|
io::printfn("Usage: %s [-r|--recursive] [-o|--output <dir>] <zip_dir>", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
bool recursive = false;
|
|
String zip_dir;
|
|
String output_dir;
|
|
|
|
for (int i = 1; i < args.len; i++)
|
|
{
|
|
String arg = args[i];
|
|
switch (arg)
|
|
{
|
|
case "-r":
|
|
case "--recursive":
|
|
recursive = true;
|
|
case "-o":
|
|
case "--output":
|
|
if (++i >= args.len)
|
|
{
|
|
io::printfn("Error: %s requires a directory path", arg);
|
|
return 1;
|
|
}
|
|
output_dir = args[i];
|
|
default:
|
|
if (arg.starts_with("-"))
|
|
{
|
|
io::printfn("Error: unknown option %s", arg);
|
|
return 1;
|
|
}
|
|
if (zip_dir)
|
|
{
|
|
io::printfn("Error: multiple zip directories specified ('%s' and '%s')", zip_dir, arg);
|
|
return 1;
|
|
}
|
|
zip_dir = arg;
|
|
}
|
|
}
|
|
|
|
if (!zip_dir)
|
|
{
|
|
io::printfn("Error: no zip directory specified.");
|
|
return 1;
|
|
}
|
|
|
|
return process_dir(zip_dir, recursive, output_dir);
|
|
}
|
|
|
|
fn int process_dir(String dir, bool recursive, String output_dir)
|
|
{
|
|
PathList? files = path::ls(tmem, path::temp(dir)!!);
|
|
if (catch excuse = files)
|
|
{
|
|
io::printfn("Could not open directory: %s (Excuse: %s)", dir, excuse);
|
|
return 1;
|
|
}
|
|
|
|
foreach (p : files)
|
|
{
|
|
String name = p.basename();
|
|
if (name == "." || name == "..") continue;
|
|
|
|
String zip_path = path::temp(dir)!!.tappend(name)!!.str_view();
|
|
|
|
if (file::is_dir(zip_path))
|
|
{
|
|
if (recursive)
|
|
{
|
|
if (process_dir(zip_path, recursive, output_dir) != 0) return 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!name.ends_with(".zip")) continue;
|
|
|
|
ulong size = 0;
|
|
File? f = file::open(zip_path, "rb");
|
|
if (try fh = f)
|
|
{
|
|
(void)fh.seek(0, Seek.END);
|
|
size = fh.seek(0, Seek.CURSOR) ?? 0;
|
|
fh.close()!!;
|
|
}
|
|
io::printf("Verifying %-40s [%7d KB] ", name[:(min(name.len, 40))], size / 1024);
|
|
|
|
switch (verify_one(zip_path, output_dir))
|
|
{
|
|
case 0:
|
|
io::printfn("%sFAILED%s ❌", Ansi.RED, Ansi.RESET);
|
|
return 1;
|
|
case 1:
|
|
io::printfn("%sPASSED%s ✅", Ansi.GREEN, Ansi.RESET);
|
|
default:
|
|
io::printn();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
fn int verify_one(String zip_path, String output_dir)
|
|
{
|
|
Path extract_root;
|
|
if (output_dir)
|
|
{
|
|
extract_root = path::temp(output_dir)!!;
|
|
}
|
|
else
|
|
{
|
|
extract_root = path::temp_directory(tmem)!!;
|
|
}
|
|
|
|
String name = (String)path::temp(zip_path)!!.basename();
|
|
|
|
Path temp_c3 = extract_root.tappend(name.tconcat("_c3"))!!;
|
|
Path temp_7z = extract_root.tappend(name.tconcat("_7z"))!!;
|
|
|
|
(void)path::mkdir(temp_c3, true);
|
|
(void)path::mkdir(temp_7z, true);
|
|
|
|
ZipArchive? archive = zip::open(zip_path, "r");
|
|
if (catch excuse = archive)
|
|
{
|
|
io::printfn("%sFAIL%s (open: %s)", Ansi.RED, Ansi.RESET, excuse);
|
|
return 0;
|
|
}
|
|
defer (void)archive.close();
|
|
|
|
Time start = time::now();
|
|
if (catch excuse = archive.extract(temp_c3.str_view()))
|
|
{
|
|
if (excuse == zip::ENCRYPTED_FILE)
|
|
{
|
|
io::printf("%sSKIPPED%s (Encrypted)", Ansi.YELLOW, Ansi.RESET);
|
|
return 2;
|
|
}
|
|
io::printfn("%sFAIL%s (extract: %s)", Ansi.RED, Ansi.RESET, excuse);
|
|
return 0;
|
|
}
|
|
Duration c3_time = time::now() - start;
|
|
|
|
start = time::now();
|
|
if (!extract_7z(zip_path, temp_7z.str_view()))
|
|
{
|
|
io::printfn("%sFAIL%s (7z extract)", Ansi.RED, Ansi.RESET);
|
|
return 0;
|
|
}
|
|
Duration p7_time = time::now() - start;
|
|
|
|
io::printf(" [C3: %5d ms, 7z: %5d ms]", (long)c3_time / 1000, (long)p7_time / 1000);
|
|
|
|
io::print(" Comparing... ");
|
|
if (!compare_dirs(temp_c3.str_view(), temp_7z.str_view()))
|
|
{
|
|
io::printfn("%sFAIL%s (Differences found)", Ansi.RED, Ansi.RESET);
|
|
return 0;
|
|
}
|
|
|
|
// keep files on error for manual verification
|
|
(void)path::rmtree(temp_c3);
|
|
(void)path::rmtree(temp_7z);
|
|
|
|
return 1;
|
|
}
|
|
|
|
fn bool extract_7z(String zip_path, String output_dir)
|
|
{
|
|
String out_opt = "-o".tconcat(output_dir);
|
|
String[] cmd = { "7z", "x", zip_path, out_opt, "-y", "-bb0" };
|
|
SubProcess? proc = process::create(cmd, { .search_user_path = true });
|
|
if (catch excuse = proc) return false;
|
|
return (int)proc.join()!! == 0;
|
|
}
|
|
|
|
fn bool compare_dirs(String dir1, String dir2)
|
|
{
|
|
String[] cmd = { "diff", "-r", dir1, dir2 };
|
|
SubProcess? proc = process::create(cmd, { .search_user_path = true, .inherit_stdio = true });
|
|
if (catch excuse = proc) return false;
|
|
int res = (int)proc.join()!!;
|
|
return res == 0;
|
|
}
|