// ignore_for_file: avoid_print import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:archive/archive_io.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'test_utils.dart'; Uint8List? fileData; void writeFile(String path, int size) { if (fileData == null) { const oneMeg = 1024 * 1024; fileData = Uint8List(oneMeg); for (var i = 0, l = fileData!.length; i < l; ++i) { fileData![i] = i % 256; } } final fp = File(path); fp.createSync(recursive: true); fp.openSync(mode: FileMode.writeOnly); while (size > fileData!.length) { fp.writeAsBytesSync(fileData!); size -= fileData!.length; } if (size > 0) { final remaining = Uint8List.view(fileData!.buffer, 0, size); fp.writeAsBytesSync(remaining); } } void generateDataDirectory(String path, {required int fileSize, required int numFiles}) { for (var i = 0; i < numFiles; ++i) { writeFile('$path/$i.bin', fileSize); } } Future _buildFileIFS(String path, [int? bufferSize]) async { if (bufferSize == null) { return InputFileStream(path); } else { return InputFileStream(path, bufferSize: bufferSize); } } Future _buildRamIfs(String path, [int? bufferSize]) async { final File file = File(path); final int fileLength = file.lengthSync(); final rawFileStream = file.openRead(); final fileStream = rawFileStream.transform( StreamTransformer, Uint8List>.fromHandlers( handleData: (List data, EventSink sink) { final uint8List = Uint8List.fromList(data); sink.add(uint8List); }, ), ); final RamFileHandle fileHandle = await RamFileHandle.fromStream(fileStream, fileLength); if (bufferSize == null) { return InputFileStream.withFileBuffer(FileBuffer(fileHandle)); } else { return InputFileStream.withFileBuffer( FileBuffer(fileHandle, bufferSize: bufferSize)); } } Future _buildFileOFS(String path) async { return OutputFileStream(path); } Future _buildRamOfs(String path) async { return OutputFileStream.toRamFile(RamFileHandle.asWritableRamBuffer()); } void _testInputFileStream( String description, dynamic Function( Future Function(String, [int?]) ifsConstructor, ) testFunction, ) { test('$description (file)', () => testFunction(_buildFileIFS)); test('$description (ram)', () => testFunction(_buildRamIfs)); } void _testInputOutputFileStream( String description, dynamic Function( Future Function(String, [int?]) ifsConstructor, Future Function(String) ofsConstructor, ) testFunction, ) { test('$description (file > file)', () => testFunction(_buildFileIFS, _buildFileOFS)); test('$description (file > ram)', () => testFunction(_buildFileIFS, _buildRamOfs)); test('$description (ram > file)', () => testFunction(_buildRamIfs, _buildFileOFS)); test('$description (ram > ram)', () => testFunction(_buildRamIfs, _buildRamOfs)); } void main() { final testPath = p.join(testDirPath, 'out/test_123.bin'); final testData = Uint8List(120); for (var i = 0; i < testData.length; ++i) { testData[i] = i; } // Add an empty directory to test2 Directory('$testDirPath/res/test2/empty').createSync(recursive: true); final testFile = File(testPath); testFile.createSync(recursive: true); testFile.openSync(mode: FileMode.write); testFile.writeAsBytesSync(testData); test('FileHandle', () async {}); test('FileBuffer', () async { FileBuffer fb = FileBuffer(FileHandle(testPath), bufferSize: 5); expect(fb.length, equals(testData.length)); var indices = [5, 110, 0, 64]; for (final i in indices) { var b = fb.readUint8(i, fb.length); expect(b, equals(testData[i])); } final bytes = fb.readBytes(5, 10, fb.length); expect(bytes, equals(testData.sublist(5, 5 + 10))); final bytes2 = fb.readBytes(115, 10, fb.length); expect(bytes2.length, equals(5)); expect(bytes2, equals(testData.sublist(115, 115 + 5))); final u16 = fb.readUint16(8, fb.length); expect(u16, equals(2312)); final u24 = fb.readUint24(50, fb.length); expect(u24, equals(3420978)); final u32 = fb.readUint32(15, fb.length); expect(u32, equals(303108111)); // make sure re-reading the same position is consistent final u32_2 = fb.readUint32(15, fb.length); expect(u32_2, equals(303108111)); final u64 = fb.readUint64(0, fb.length); expect(u64, equals(0x0706050403020100)); }); group('InputFileStream', () { _testInputFileStream('length', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); expect(fs.length, testData.length); }); _testInputFileStream('readByte', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); for (var i = 0; i < testData.length; ++i) { expect(fs.readByte(), testData[i], reason: 'Byte at index $i was incorrect'); } }); _testInputFileStream('readBytes', (ifsConstructor) async { final input = await ifsConstructor(testPath); expect(input.length, equals(120)); var ai = 0; while (!input.isEOS) { final bs = input.readBytes(40); expect(bs.length, 40); final bytes = bs.toUint8List(); expect(bytes.length, 40); for (var i = 0; i < bytes.length; ++i) { expect(bytes[i], equals(ai + i)); } ai += bytes.length; } }); _testInputFileStream('position', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); fs.position = 50; final bs = fs.readBytes(50); final b = bs.toUint8List(); expect(b.length, 50); for (var i = 0; i < b.length; ++i) { expect(b[i], testData[50 + i]); } }); _testInputFileStream('skip', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); fs.skip(50); final bs = fs.readBytes(50); final b = bs.toUint8List(); expect(b.length, 50); for (var i = 0; i < b.length; ++i) { expect(b[i], testData[50 + i]); } }); _testInputFileStream('rewind', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); fs.skip(50); fs.rewind(10); final bs = fs.readBytes(50); final b = bs.toUint8List(); expect(b.length, 50); for (var i = 0; i < b.length; ++i) { expect(b[i], testData[40 + i]); } }); _testInputFileStream('rewind 2', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); final bs = fs.readBytes(50); final b = bs.toUint8List(); fs.rewind(50); expect(b.length, 50); for (var i = 0; i < b.length; ++i) { expect(b[i], fs.readByte()); } }); _testInputFileStream('peakBytes', (ifsConstructor) async { final fs = await ifsConstructor(testPath, 2); final bs = fs.peekBytes(10); final b = bs.toUint8List(); expect(fs.position, 0); expect(b.length, 10); for (var i = 0; i < b.length; ++i) { expect(b[i], testData[i]); } }); _testInputFileStream("clone", (ifsConstructor) async { final input = await ifsConstructor(testPath); final input2 = InputFileStream.clone(input, position: 6, length: 5); final bs = input2.readBytes(5); final b = bs.toUint8List(); expect(b.length, 5); for (var i = 0; i < b.length; ++i) { expect(b[i], testData[6 + i]); } }); }); test('InputFileStream/OutputFileStream (files)', () { var input = InputFileStream(p.join(testDirPath, 'res/cat.jpg')); var output = OutputFileStream(p.join(testDirPath, 'out/cat2.jpg')); var offset = 0; var inputLength = input.length; while (!input.isEOS) { final bytes = input.readBytes(50); if (offset + 50 > inputLength) { final remaining = inputLength - offset; expect(bytes.length, equals(remaining)); } offset += bytes.length; output.writeInputStream(bytes); } input.closeSync(); output.closeSync(); final aBytes = File(p.join(testDirPath, 'res/cat.jpg')).readAsBytesSync(); final bBytes = File(p.join(testDirPath, 'out/cat2.jpg')).readAsBytesSync(); expect(aBytes.length, equals(bBytes.length)); var same = true; for (var i = 0; same && i < aBytes.length; ++i) { same = aBytes[i] == bBytes[i]; } expect(same, equals(true)); }); test('InputFileStream/OutputFileStream (ram)', () { var input = InputFileStream(p.join(testDirPath, 'res/cat.jpg')); final RamFileHandle rfh = RamFileHandle.asWritableRamBuffer(); var output = OutputFileStream.toRamFile(rfh); var offset = 0; var inputLength = input.length; while (!input.isEOS) { final bytes = input.readBytes(50); if (offset + 50 > inputLength) { final remaining = inputLength - offset; expect(bytes.length, equals(remaining)); } offset += bytes.length; output.writeInputStream(bytes); } input.closeSync(); output.closeSync(); final aBytes = File(p.join(testDirPath, 'res/cat.jpg')).readAsBytesSync(); final bBytes = Uint8List(rfh.length); rfh.readInto(bBytes); compareBytes(bBytes, aBytes); output.closeSync(); }); test('Zip in RAM and then unzip from RAM', () { final testFiles = [ 'a.txt.gz', 'cat.jpg', 'cat.jpg.gz', 'emptyfile.txt', 'example.tar', 'tarurls.txt', 'test_100k_files.zip', 'test2.tar', 'test2.tar.bz2', 'test2.tar.gz', 'test2.zip', 'test.tar', 'test.zip', ]; final fileNameToFileContent = {}; for (final fileName in testFiles) { fileNameToFileContent[fileName] = File(p.join(testDirPath, 'res/cat.jpg')).readAsBytesSync(); } final RamFileData ramFileData = RamFileData.outputBuffer(); final zipEncoder = ZipFileEncoder() ..createWithBuffer( OutputFileStream.toRamFile( RamFileHandle.fromRamFileData(ramFileData), ), ); for (final fileEntry in fileNameToFileContent.entries) { final name = fileEntry.key; final content = fileEntry.value; zipEncoder.addArchiveFile(ArchiveFile(name, content.length, content)); } zipEncoder.closeSync(); final Uint8List zippedBytes = Uint8List(ramFileData.length); ramFileData.readIntoSync(zippedBytes, 0, zippedBytes.length); final RamFileData readRamFileData = RamFileData.fromBytes(zippedBytes); final Archive archive = ZipDecoder().decodeBuffer( InputFileStream.withFileBuffer( FileBuffer( RamFileHandle.fromRamFileData(readRamFileData), ), ), ); expect(archive.files.length, fileNameToFileContent.length); for (int i = 0; i < archive.files.length; i++) { final file = archive.files[i]; final Uint8List? fileContent = fileNameToFileContent[file.name]; expect(fileContent != null, true, reason: 'File content was null for "${file.name}"'); compareBytes(file.content as List, fileContent as List); } }); test('empty file', () async { final encoder = ZipFileEncoder(); encoder.create('$testDirPath/out/testEmpty.zip'); await encoder.addFile(File('$testDirPath/res/emptyfile.txt')); encoder.closeSync(); final zipDecoder = ZipDecoder(); final f = File('$testDirPath/out/testEmpty.zip'); final archive = zipDecoder.decodeBytes(f.readAsBytesSync(), verify: true); expect(archive.length, equals(1)); }); _testInputFileStream('stream tar decode', (ifsConstructor) async { // Decode a tar from disk to memory final stream = await ifsConstructor(p.join(testDirPath, 'res/test2.tar')); final tarArchive = TarDecoder(); tarArchive.decodeBuffer(stream); for (final file in tarArchive.files) { if (!file.isFile) { continue; } final filename = file.filename; try { final f = File('$testDirPath/out/$filename'); f.parent.createSync(recursive: true); f.writeAsBytesSync(file.content as List); } catch (e) { print(e); } } expect(tarArchive.files.length, equals(4)); }); _testInputFileStream('stream zip decode', (ifsConstructor) async { // Decode a tar from disk to memory final stream = await ifsConstructor(p.join(testDirPath, 'res/test.zip')); final zip = ZipDecoder().decodeBuffer(stream); expect(zip.files.length, equals(2)); expect(zip.files[0].name, equals("a.txt")); expect(zip.files[1].name, equals("cat.jpg")); expect(zip.files[1].content.length, equals(51662)); }); test('stream tar encode', () async { // Encode a directory from disk to disk, no memory final encoder = TarFileEncoder(); encoder.open('$testDirPath/out/test3.tar'); await encoder.addDirectory(Directory('$testDirPath/res/test2')); await encoder.close(); final tarDecoder = TarDecoder(); final f = File('$testDirPath/out/test3.tar'); final archive = tarDecoder.decodeBytes(f.readAsBytesSync(), verify: true); expect(archive.length, equals(4)); }); _testInputOutputFileStream('stream gzip encode', ( ifsConstructor, ofsConstructor, ) async { final input = await ifsConstructor(p.join(testDirPath, 'res/cat.jpg')); final output = await ofsConstructor(p.join(testDirPath, 'out/cat.jpg.gz')); final encoder = GZipEncoder(); encoder.encode(input, output: output); await output.close(); }); _testInputOutputFileStream('stream gzip decode', ( ifsConstructor, ofsConstructor, ) async { final input = await ifsConstructor(p.join(testDirPath, 'out/cat.jpg.gz')); final output = await ofsConstructor(p.join(testDirPath, 'out/cat.jpg')); GZipDecoder().decodeStream(input, output); await output.close(); }); _testInputOutputFileStream('TarFileEncoder -> GZipEncoder', ( ifsConstructor, ofsConstructor, ) async { // Encode a directory from disk to disk, no memory final encoder = TarFileEncoder(); encoder.create('$testDirPath/out/example2.tar'); await encoder.addDirectory(Directory('$testDirPath/res/test2')); await encoder.close(); final input = await ifsConstructor(p.join(testDirPath, 'out/example2.tar')); final output = await ofsConstructor(p.join(testDirPath, 'out/example2.tgz')); GZipEncoder().encode(input, output: output); await input.close(); await output.close(); }); test('TarFileEncoder tgz', () async { // Encode a directory from disk to disk, no memory final encoder = TarFileEncoder(); await encoder.tarDirectory(Directory('$testDirPath/res/test2'), filename: '$testDirPath/out/example2.tgz', compression: 1); await encoder.close(); }); test('stream zip encode', () async { final encoder = ZipFileEncoder(); encoder.create('$testDirPath/out/example2.zip'); await encoder.addDirectory(Directory('$testDirPath/res/test2')); await encoder.addFile(File('$testDirPath/res/cat.jpg')); await encoder.addFile(File('$testDirPath/res/tarurls.txt')); encoder.closeSync(); final zipDecoder = ZipDecoder(); final f = File('$testDirPath/out/example2.zip'); final archive = zipDecoder.decodeBytes(f.readAsBytesSync(), verify: true); expect(archive.length, equals(6)); }); test('decode_empty_directory', () { final zip = ZipDecoder(); final archive = zip.decodeBytes(File('$testDirPath/res/test2.zip').readAsBytesSync()); expect(archive.length, 4); }); test('create_archive_from_directory', () { final dir = Directory('$testDirPath/res/test2'); final archive = createArchiveFromDirectory(dir); expect(archive.length, equals(4)); final encoder = ZipEncoder(); final bytes = encoder.encode(archive)!; File('$testDirPath/out/test2_.zip') ..openSync(mode: FileMode.write) ..writeAsBytesSync(bytes); final zipDecoder = ZipDecoder(); final archive2 = zipDecoder.decodeBytes(bytes, verify: true); expect(archive2.length, equals(4)); }); _testInputFileStream('file close', (ifsConstructor) async { final testPath = p.join(testDirPath, 'out/test2.bin'); final testData = Uint8List(120); for (var i = 0; i < testData.length; ++i) { testData[i] = i; } final testFile = File(testPath); testFile.createSync(recursive: true); final fp = testFile.openSync(mode: FileMode.write); fp.writeFromSync(testData); fp.closeSync(); final input = await ifsConstructor(testPath); final bs = input.readBytes(50); expect(bs.length, 50); await input.close(); await testFile.delete(); }); test('extractFileToDisk tar', () async { final inPath = '$testDirPath/res/test2.tar'; final outPath = '$testDirPath/out/extractFileToDisk_tar'; final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final files = dir.listSync(recursive: true); expect(files.length, 4); }); test('extractFileToDisk tar.gz', () async { final inPath = '$testDirPath/res/test2.tar.gz'; final outPath = '$testDirPath/out/extractFileToDisk_tgz'; final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final files = dir.listSync(recursive: true); expect(files.length, 4); }); test('extractFileToDisk tar.tbz', () async { final inPath = '$testDirPath/res/test2.tar.bz2'; final outPath = '$testDirPath/out/extractFileToDisk_tbz'; final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final files = dir.listSync(recursive: true); expect(files.length, 4); }); test('extractFileToDisk zip', () async { final inPath = '$testDirPath/res/test.zip'; final outPath = '$testDirPath/out/extractFileToDisk_zip'; final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final files = dir.listSync(recursive: true); expect(files.length, 2); }); test('extractFileToDisk zip bzip2', () async { final inPath = '$testDirPath/res/zip/zip_bzip2.zip'; final outPath = '$testDirPath/out/extractFileToDisk_zip_bzip2'; final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final files = dir.listSync(recursive: true); expect(files.length, 2); }); test('extractArchiveToDisk symlink', () async { final f1 = ArchiveFile('test', 3, 'foo'.codeUnits); final f2 = ArchiveFile('link', 0, null); f2.isSymbolicLink = true; f2.nameOfLinkedFile = './../test.tar'; final a = Archive(); a.addFile(f1); a.addFile(f2); await extractArchiveToDisk( a, '$testDirPath/out/extractArchiveToDisk_symlink'); }); test('extractArchiveToDiskSync symlink', () { final f1 = ArchiveFile('test', 3, 'foo'.codeUnits); final f2 = ArchiveFile('link', 0, null); f2.isSymbolicLink = true; f2.nameOfLinkedFile = './../test.tar'; final a = Archive(); a.addFile(f1); a.addFile(f2); extractArchiveToDiskSync( a, '$testDirPath/out/extractArchiveToDisk_symlink'); }); test('FileHandle', () async { final fp = File('$testDirPath/res/zip/zip_bzip2.zip'); final fh = FileHandle.fromFile(fp); final fs = InputFileStream.withFileHandle(fh); expect(fs.readByte(), equals(80)); }); test('zip directory', () async { final tmpPath = '$testDirPath/out/test_zip_dir'; generateDataDirectory(tmpPath, fileSize: 1024, numFiles: 5); final inPath = '$testDirPath/out/test_zip_dir_2.zip'; final outPath = '$testDirPath/out/test_zip_dir_2'; var count = 0; final encoder = ZipFileEncoder(); await encoder.zipDirectoryAsync(Directory(tmpPath), level: 0, filename: inPath, onProgress: (double x) { count++; }); expect(count, equals(5)); final dir = Directory(outPath); if (dir.existsSync()) { dir.deleteSync(recursive: true); } await extractFileToDisk(inPath, outPath); final srcFiles = Directory(tmpPath).listSync(recursive: true); final dstFiles = Directory('$testDirPath/out/test_zip_dir_2').listSync(recursive: true); expect(dstFiles.length, equals(srcFiles.length)); //encoder.close(); }); group('$ZipFileEncoder', () { test( 'zipDirectory throws a FormatException when filename is within dir', () { final encoder = ZipFileEncoder(); final invalidFilename = p.join(testDirPath, 'res/test2.zip'); expect( () => encoder.zipDirectory( Directory(testDirPath), filename: invalidFilename, ), throwsA( isA() .having( (exception) => exception.message, 'message', equals( 'filename must not be within the directory being zipped'), ) .having( (exception) => exception.source, 'source', equals(invalidFilename), ), ), ); }, ); test( 'zipDirectoryAsync throws a FormatException when filename is within dir', () async { final encoder = ZipFileEncoder(); final invalidFilename = p.join(testDirPath, 'res/test2.zip'); await expectLater( () => encoder.zipDirectoryAsync( Directory(testDirPath), filename: invalidFilename, ), throwsA( isA() .having( (exception) => exception.message, 'message', equals( 'filename must not be within the directory being zipped'), ) .having( (exception) => exception.source, 'source', equals(invalidFilename), ), ), ); }, ); }); }