import 'dart:convert'; 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'; final zipTests = [ { 'Name': 'res/zip/test.zip', 'Comment': 'This is a zipfile comment.', 'File': [ { 'Name': 'test.txt', 'Content': 'This is a test text file.\n'.codeUnits, 'Mtime': '09-05-10 12:12:02', 'Mode': 0644, }, { 'Name': 'gophercolor16x16.png', 'File': 'gophercolor16x16.png', 'Mtime': '09-05-10 15:52:58', 'Mode': 0644, }, ], }, { 'Name': 'res/zip/test-trailing-junk.zip', 'Comment': 'This is a zipfile comment.', 'File': [ { 'Name': 'test.txt', 'Content': 'This is a test text file.\n'.codeUnits, 'Mtime': '09-05-10 12:12:02', 'Mode': 0644, }, { 'Name': 'gophercolor16x16.png', 'File': 'gophercolor16x16.png', 'Mtime': '09-05-10 15:52:58', 'Mode': 0644, }, ], }, /*{ 'Name': 'res/zip/r.zip', 'Source': returnRecursiveZip, 'File': [ { 'Name': 'r/r.zip', 'Content': rZipBytes(), 'Mtime': '03-04-10 00:24:16', 'Mode': 0666, }, ], },*/ { 'Name': 'res/zip/symlink.zip', 'File': [ { 'Name': 'symlink', 'Content': '../target'.codeUnits, 'Mode': 0777 | 0120000, 'isSymbolicLink': true, }, ], }, { 'Name': 'res/zip/readme.zip', }, { 'Name': 'res/zip/readme.notzip', //'Error': ErrFormat, }, { 'Name': 'res/zip/dd.zip', 'File': [ { 'Name': 'filename', 'Content': 'This is a test textfile.\n'.codeUnits, 'Mtime': '02-02-11 13:06:20', 'Mode': 0666, }, ], }, { // created in windows XP file manager. 'Name': 'res/zip/winxp.zip', 'File': [ {'Name': 'hello', 'isFile': true}, {'Name': 'dir/bar', 'isFile': true}, { 'Name': 'dir/empty/', 'Content': [], // empty list of codeUnits - no content 'isFile': false }, {'Name': 'readonly', 'isFile': true}, ] }, /* { // created by Zip 3.0 under Linux 'Name': 'res/zip/unix.zip', 'File': crossPlatform, },*/ { 'Name': 'res/zip/go-no-datadesc-sig.zip', 'File': [ { 'Name': 'foo.txt', 'Content': 'foo\n'.codeUnits, 'Mtime': '03-08-12 16:59:10', 'Mode': 0644, }, { 'Name': 'bar.txt', 'Content': 'bar\n'.codeUnits, 'Mtime': '03-08-12 16:59:12', 'Mode': 0644, }, ], }, { 'Name': 'res/zip/go-with-datadesc-sig.zip', 'File': [ { 'Name': 'foo.txt', 'Content': 'foo\n'.codeUnits, 'Mode': 0666, }, { 'Name': 'bar.txt', 'Content': 'bar\n'.codeUnits, 'Mode': 0666, }, ], }, /*{ 'Name': 'Bad-CRC32-in-data-descriptor', 'Source': returnCorruptCRC32Zip, 'File': [ { 'Name': 'foo.txt', 'Content': 'foo\n'.codeUnits, 'Mode': 0666, 'ContentErr': ErrChecksum, }, { 'Name': 'bar.txt', 'Content': 'bar\n'.codeUnits, 'Mode': 0666, }, ], },*/ // Tests that we verify (and accept valid) crc32s on files // with crc32s in their file header (not in data descriptors) { 'Name': 'res/zip/crc32-not-streamed.zip', 'File': [ { 'Name': 'foo.txt', 'Content': 'foo\n'.codeUnits, 'Mtime': '03-08-12 16:59:10', 'Mode': 0644, }, { 'Name': 'bar.txt', 'Content': 'bar\n'.codeUnits, 'Mtime': '03-08-12 16:59:12', 'Mode': 0644, }, ], }, // Tests that we verify (and reject invalid) crc32s on files // with crc32s in their file header (not in data descriptors) { 'Name': 'res/zip/crc32-not-streamed.zip', //'Source': returnCorruptNotStreamedZip, 'File': [ { 'Name': 'foo.txt', 'Content': 'foo\n'.codeUnits, 'Mtime': '03-08-12 16:59:10', 'Mode': 0644, 'VerifyChecksum': true //'ContentErr': ErrChecksum, }, { 'Name': 'bar.txt', 'Content': 'bar\n'.codeUnits, 'Mtime': '03-08-12 16:59:12', 'Mode': 0644, 'VerifyChecksum': true }, ], }, { 'Name': 'res/zip/zip64.zip', 'File': [ { 'Name': 'README', 'Content': 'This small file is in ZIP64 format.\n'.codeUnits, 'Mtime': '08-10-12 14:33:32', 'Mode': 0644, }, ], }, ]; void main() { test('zip empty', () async { ZipDecoder().decodeBytes(ZipEncoder().encode(Archive())!); }); test('apk', () async { final archive = Archive() ..addFile( ArchiveFile('AndroidManifest.xml', 100, List.filled(100, 0))); final apk = ZipEncoder().encode(archive)!; final decodedArchive = ZipDecoder().decodeBytes(apk); for (final archiveFile in decodedArchive.files) { expect(archiveFile.rawContent, isNotNull); expect(archiveFile.rawContent!.length, 6); } }); test('zip isFile', () async { var file = File(p.join(testDirPath, 'res/zip/android-javadoc.zip')); var bytes = file.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: true); expect(archive.numberOfFiles(), equals(102)); for (var file in archive.files) { //print('@ ${file.name} ${file.isFile} ${!file.name.endsWith('/')}'); expect(file.isFile, equals(!file.name.endsWith('/'))); } }); test('file decode utf file', () { var bytes = File(p.join(testDirPath, 'res/zip/utf.zip')).readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: true); expect(archive.numberOfFiles(), equals(5)); }); test('file encoding zip file', () { final originalFileName = 'fileöäüÖÄÜß.txt'; final bytes = Utf8Codec().encode('test'); final archiveFile = ArchiveFile(originalFileName, bytes.length, bytes); final archive = Archive(); archive.addFile(archiveFile); final encoder = ZipEncoder(); final decoder = ZipDecoder(); var encodedBytes = encoder.encode(archive)!; final archiveDecoded = decoder.decodeBytes(encodedBytes); final decodedFile = archiveDecoded.files.first; expect(decodedFile.name, originalFileName); }); test('zip64', () { var bytes = File(p.join(testDirPath, 'res/zip/zip64_archive.zip')) .readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: false); expect(archive.numberOfFiles(), equals(3)); expect(archive.fileSize(0), equals(3136)); }); test('zip data types', () { final archive = Archive(); archive.addFile(ArchiveFile('uint8list', 2, Uint8List(2))); archive.addFile(ArchiveFile('list_int', 2, [1, 2])); archive.addFile( ArchiveFile('float32list', 8, Float32List.fromList([3.0, 4.0]))); archive.addFile(ArchiveFile.string('string', 'hello')); var zipData = ZipEncoder().encode(archive); var archive2 = ZipDecoder().decodeBytes(zipData!); expect(archive2.length, equals(archive.length)); }); // Disabled test until it can be verified /*test('zip executable', () async { // Only tested on linux so far if (Platform.isLinux || Platform.isMacOS) { var path = Directory.systemTemp.createTempSync('zip_executable').path; var srcPath = p.join(path, 'src'); try { Directory(path).deleteSync(recursive: true); } catch (_) {} final dir = Directory(srcPath); await dir.create(recursive: true); // Create an executable file and zip it final file = File(p.join(srcPath, 'test.bin')); await file.writeAsString('bin', flush: true); await Process.run('chmod', ['+x', file.path]); final subdir = Directory(p.join(dir.path, 'subdir')); subdir.createSync(recursive: true); var file2 = File(p.join(subdir.path, 'test2.bin')); await file2.writeAsString('bin2', flush: true); await Process.run('chmod', ['+x', file2.path]); var dstFilePath = p.join(path, 'test.zip'); ZipFileEncoder().zipDirectory(Directory(srcPath), filename: dstFilePath); // Read final bytes = await File(dstFilePath).readAsBytes(); // Decode the Zip file final archive = ZipDecoder().decodeBytes(bytes); final archiveFile = archive[1]; expect(archiveFile.isFile, true); } });*/ test('encode', () { final archive = Archive(); var bdata = 'hello world'; var bytes = Uint8List.fromList(bdata.codeUnits); final name = 'abc.txt'; final afile = ArchiveFile.noCompress(name, bytes.lengthInBytes, bytes); archive.addFile(afile); var zipData = ZipEncoder().encode(archive)!; File(p.join(testDirPath, 'out/uncompressed.zip')) ..createSync(recursive: true) ..writeAsBytesSync(zipData); var arc = ZipDecoder().decodeBytes(zipData, verify: true); expect(arc.numberOfFiles(), equals(1)); var arcData = arc.fileData(0); expect(arcData.length, equals(bdata.length)); for (var i = 0; i < arcData.length; ++i) { expect(arcData[i], equals(bdata.codeUnits[i])); } }); test('encode with timestamp', () { final archive = Archive(); var bdata = 'some file data'; var bytes = Uint8List.fromList(bdata.codeUnits); final name = 'somefile.txt'; final afile = ArchiveFile.noCompress(name, bytes.lengthInBytes, bytes); archive.addFile(afile); var zipData = ZipEncoder() .encode(archive, modified: DateTime.utc(2010, DateTime.january, 1))!; File(p.join(testDirPath, 'out/uncompressed.zip')) ..createSync(recursive: true) ..writeAsBytesSync(zipData); var arc = ZipDecoder().decodeBytes(zipData, verify: true); expect(arc.numberOfFiles(), equals(1)); var arcData = arc.fileData(0); expect(arcData.length, equals(bdata.length)); for (var i = 0; i < arcData.length; ++i) { expect(arcData[i], equals(bdata.codeUnits[i])); } expect(arc[0].lastModTime, equals(1008795648)); }); test('zipCrypto', () { var file = File(p.join(testDirPath, 'res/zip/zipCrypto.zip')); var bytes = file.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: false, password: '12345'); expect(archive.numberOfFiles(), equals(2)); for (var i = 0; i < archive.numberOfFiles(); ++i) { var file = File(p.join(testDirPath, 'res/zip/${archive.files[i].name}')); var bytes = file.readAsBytesSync(); var content = archive.files[i].content as Uint8List; expect(bytes.length, equals(content.length)); bool diff = false; for (int i = 0; i < bytes.length; ++i) { if (bytes[i] != content[i]) { diff = true; break; } } expect(diff, equals(false)); } }); test('aes256', () { final stream = InputFileStream(p.join(testDirPath, 'res/zip/aes256.zip')); final archive = ZipDecoder().decodeBuffer(stream, password: '12345'); expect(archive.numberOfFiles(), equals(2)); for (var i = 0; i < archive.numberOfFiles(); ++i) { var file = File(p.join(testDirPath, 'res/zip/${archive.files[i].name}')); var bytes = file.readAsBytesSync(); var content = archive.files[i].content as Uint8List; expect(content.length, equals(bytes.length)); bool diff = false; for (int i = 0; i < bytes.length; ++i) { if (bytes[i] != content[i]) { diff = true; break; } } expect(diff, equals(false)); } }); test('password', () { var file = File(p.join(testDirPath, 'res/zip/password_zipcrypto.zip')); var bytes = file.readAsBytesSync(); var b = File(p.join(testDirPath, 'res/zip/hello.txt')); final bBytes = b.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: true, password: 'test1234'); expect(archive.numberOfFiles(), equals(1)); for (var i = 0; i < archive.numberOfFiles(); ++i) { final zBytes = archive.fileData(i); if (archive.fileName(i) == 'hello.txt') { compareBytes(zBytes, bBytes); } else { throw TestFailure('Invalid file found'); } } }); test('decode zip bzip2', () { var file = File(p.join(testDirPath, 'res/zip/zip_bzip2.zip')); var bytes = file.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: true); expect(archive.numberOfFiles(), equals(2)); for (final f in archive) { final c = f.content; expect(c, isNotNull); } }); test('encode password', () { final archive = Archive(); final bdata = 'hello world'; final bytes = Uint8List.fromList(bdata.codeUnits); final name = 'abc.txt'; final afile = ArchiveFile.noCompress(name, bytes.lengthInBytes, bytes); archive.addFile(afile); final zipData = ZipEncoder(password: 'abc123').encode(archive)!; File(p.join(testDirPath, 'out/zip_password.zip')) ..createSync(recursive: true) ..writeAsBytesSync(zipData); final arc = ZipDecoder().decodeBytes(zipData, password: 'abc123'); expect(arc.numberOfFiles(), equals(1)); var arcData = arc.fileData(0); expect(arcData.length, equals(bdata.length)); for (var i = 0; i < arcData.length; ++i) { expect(arcData[i], equals(bdata.codeUnits[i])); } }); test('decode/encode', () { var file = File(p.join(testDirPath, 'res/test.zip')); var bytes = file.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(bytes, verify: true); expect(archive.numberOfFiles(), equals(2)); var b = File(p.join(testDirPath, 'res/cat.jpg')); final bBytes = b.readAsBytesSync(); final aBytes = aTxt.codeUnits; for (var i = 0; i < archive.numberOfFiles(); ++i) { final zBytes = archive.fileData(i); if (archive.fileName(i) == 'a.txt') { compareBytes(zBytes, aBytes); } else if (archive.fileName(i) == 'cat.jpg') { compareBytes(zBytes, bBytes); } else { throw TestFailure('Invalid file found'); } } // Encode the archive we just decoded final zipped = ZipEncoder().encode(archive)!; final f = File(p.join(testDirPath, 'out/test.zip')); f.createSync(recursive: true); f.writeAsBytesSync(zipped); // Decode the archive we just encoded final archive2 = ZipDecoder().decodeBytes(zipped, verify: true); expect(archive2.numberOfFiles(), equals(archive.numberOfFiles())); for (var i = 0; i < archive2.numberOfFiles(); ++i) { expect(archive2.fileName(i), equals(archive.fileName(i))); expect(archive2.fileSize(i), equals(archive.fileSize(i))); } }); test('encode already compressed file', () { final testArchive = Archive(); testArchive.addFile(ArchiveFile('test', 3, [1, 2, 3])..compress = true); final testArchiveBytes = ZipEncoder().encode(testArchive, level: Deflate.BEST_COMPRESSION)!; final decodedTestArchive = ZipDecoder().decodeBytes(testArchiveBytes); // Verify that the archive file is already compressed and will be // compressed when re-encoded. expect(decodedTestArchive.files.single.isCompressed, true); expect(decodedTestArchive.files.single.compressionType, 8); expect(decodedTestArchive.files.single.compress, true); final decodedTestArchiveBytes = ZipEncoder() .encode(decodedTestArchive, level: Deflate.BEST_COMPRESSION)!; final verifyArchive = ZipDecoder().decodeBytes(decodedTestArchiveBytes); expect(verifyArchive.single.content, [1, 2, 3]); }); test('decode many files (100k)', () { final fp = InputFileStream( p.join(testDirPath, 'res/test_100k_files.zip'), bufferSize: 1024 * 1024, ); final Archive archive = ZipDecoder().decodeBuffer(fp); final int totalArchiveEntriesCount = archive.files.length; expect(archive.numberOfFiles(), equals(100000)); int nextEntryIndex = 0; ArchiveFile? file; while (nextEntryIndex < totalArchiveEntriesCount) { file = archive.files[nextEntryIndex]; if (!file.isFile) { nextEntryIndex++; continue; } final ArchiveFile f = file; final String filename = f.name; final dynamic data = f.content; f.clear(); expect( filename.trim().isEmpty, false, reason: 'Archive file check error: file name empty', ); expect( data == null, false, reason: 'Archive file check error: content for $filename is null', ); nextEntryIndex++; } }); for (final Z in zipTests) { final z = Z as Map; test('unzip ${z['Name']}', () { var file = File(p.join(testDirPath, z['Name'] as String)); var bytes = file.readAsBytesSync(); final zipDecoder = ZipDecoder(); final archive = zipDecoder.decodeBytes(bytes, verify: true); final zipFiles = zipDecoder.directory.fileHeaders; if (z.containsKey('Comment')) { expect(zipDecoder.directory.zipFileComment, z['Comment']); } if (!z.containsKey('File')) { return; } expect(zipFiles.length, equals(z['File'].length)); for (var i = 0; i < zipFiles.length; ++i) { final zipFileHeader = zipFiles[i]; final zipFile = zipFileHeader.file; var hdr = z['File'][i] as Map; if (hdr.containsKey('Name')) { expect(zipFile!.filename, equals(hdr['Name'])); } if (hdr.containsKey('Content')) { expect(zipFile!.content, equals(hdr['Content'])); } if (hdr.containsKey('VerifyChecksum')) { expect(zipFile!.verifyCrc32(), equals(hdr['VerifyChecksum'])); } if (hdr.containsKey('isFile')) { expect(archive.findFile(zipFile!.filename)!.isFile, hdr['isFile']); } if (hdr.containsKey('isSymbolicLink')) { expect(archive.findFile(zipFile!.filename)!.isSymbolicLink, hdr['isSymbolicLink']); expect(archive.findFile(zipFile.filename)!.nameOfLinkedFile, utf8.decode(hdr['Content'] as List)); } } }); } }