├── test ├── _data │ ├── emptyfile.txt │ ├── folder │ │ ├── abc.txt │ │ └── with_contents │ │ │ └── def.txt │ ├── test2 │ │ ├── test.txt │ │ └── test │ │ │ └── test.txt │ ├── tar │ │ ├── folder │ │ │ ├── abc.txt │ │ │ └── with_contents │ │ │ │ └── def.txt │ │ ├── small.txt │ │ ├── small2.txt │ │ ├── v7.tar │ │ ├── folder.zip │ │ ├── writer-big.tar │ │ ├── nil-uid.tar │ │ ├── ustar.tar │ │ ├── gnu.tar │ │ ├── star.tar │ │ └── writer.tar │ ├── zip │ │ ├── hello.txt │ │ ├── dd.zip │ │ ├── utf.zip │ │ ├── aes256.zip │ │ ├── readme.zip │ │ ├── test.zip │ │ ├── unix.zip │ │ ├── winxp.zip │ │ ├── zip64.zip │ │ ├── symlink.zip │ │ ├── readme.notzip │ │ ├── zipCrypto.zip │ │ ├── zip_bzip2.zip │ │ ├── zip64_archive.zip │ │ ├── android-javadoc.zip │ │ ├── gophercolor16x16.png │ │ ├── crc32-not-streamed.zip │ │ ├── go-no-datadesc-sig.zip │ │ ├── password_zipcrypto.zip │ │ ├── test-trailing-junk.zip │ │ └── go-with-datadesc-sig.zip │ ├── a.txt.gz │ ├── cat.jpg │ ├── test.tar │ ├── test.zip │ ├── test2.zip │ ├── cat.jpg.gz │ ├── example.tar │ ├── folder.zip │ ├── xz │ │ ├── crc32.xz │ │ ├── crc64.xz │ │ ├── empty.xz │ │ ├── hello.xz │ │ ├── cat.jpg.xz │ │ ├── nocheck.xz │ │ ├── sha256.xz │ │ ├── good-1-lzma2-1.xz │ │ ├── hello-hello-hello.xz │ │ └── expected │ │ │ └── good-1-lzma2-1 │ ├── bzip2 │ │ └── test.bz2 │ ├── test2.tar.bz2 │ ├── test2.tar.gz │ ├── inflate │ │ └── data.bin │ ├── test_100k_files.zip │ └── test2.tar ├── crc32_test.dart ├── bzip2_test.dart ├── adler32_test.dart ├── _test_util.dart ├── deflate_test.dart ├── file_handle_test.dart ├── test_all.dart ├── web_test.dart ├── output_memory_stream_test.dart ├── zlib_test.dart ├── commands_test.dart ├── output_file_stream_test.dart ├── archive_test.dart ├── input_file_stream_test.dart ├── gzip_test.dart ├── input_memory_stream_test.dart └── xz_test.dart ├── .pubignore ├── lib ├── src │ ├── util │ │ ├── byte_order.dart │ │ ├── file_access.dart │ │ ├── file_handle.dart │ │ ├── _cast.dart │ │ ├── _crc64_html.dart │ │ ├── archive_exception.dart │ │ ├── crc64.dart │ │ ├── adler32.dart │ │ ├── abstract_file_handle.dart │ │ ├── _file_handle_html.dart │ │ ├── aes.dart │ │ ├── file_content.dart │ │ ├── _file_handle_io.dart │ │ ├── output_stream.dart │ │ ├── output_memory_stream.dart │ │ ├── input_memory_stream.dart │ │ ├── input_stream.dart │ │ ├── crc32.dart │ │ └── input_file_stream.dart │ ├── io │ │ ├── posix.dart │ │ ├── posix_html.dart │ │ ├── posix_stub.dart │ │ ├── zip_file_progress.dart │ │ ├── posix_io.dart │ │ ├── create_archive_from_directory.dart │ │ ├── tar_command.dart │ │ └── tar_file_encoder.dart │ ├── codecs │ │ ├── zlib │ │ │ ├── _gzip_decoder.dart │ │ │ ├── _gzip_encoder.dart │ │ │ ├── _zlib_decoder.dart │ │ │ ├── _zlib_encoder.dart │ │ │ ├── _inflate_buffer_web.dart │ │ │ ├── _inflate_buffer_io.dart │ │ │ ├── inflate_buffer.dart │ │ │ ├── _zlib_decoder_base.dart │ │ │ ├── _zlib_encoder_base.dart │ │ │ ├── gzip_decoder_web.dart │ │ │ ├── zlib_decoder_web.dart │ │ │ ├── gzip_flag.dart │ │ │ ├── zlib_encoder_web.dart │ │ │ ├── gzip_encoder_web.dart │ │ │ ├── _zlib_decoder_io.dart │ │ │ ├── _huffman_table.dart │ │ │ ├── _gzip_decoder_io.dart │ │ │ ├── _zlib_encoder_io.dart │ │ │ ├── _gzip_encoder_io.dart │ │ │ ├── _zlib_encoder_web.dart │ │ │ ├── _zlib_decoder_web.dart │ │ │ ├── _gzip_encoder_web.dart │ │ │ └── _gzip_decoder_web.dart │ │ ├── bzip2 │ │ │ ├── bz2_bit_reader.dart │ │ │ ├── bz2_bit_writer.dart │ │ │ └── bzip2.dart │ │ ├── gzip_encoder.dart │ │ ├── gzip_decoder.dart │ │ ├── zlib_encoder.dart │ │ ├── zlib_decoder.dart │ │ ├── tar_encoder.dart │ │ ├── zip_decoder.dart │ │ ├── lzma │ │ │ └── range_decoder.dart │ │ ├── tar_decoder.dart │ │ └── zip │ │ │ └── zip_file_header.dart │ └── archive │ │ ├── encryption_type.dart │ │ ├── compression_type.dart │ │ └── archive.dart ├── archive_io.dart └── archive.dart ├── web ├── res │ ├── cat.jpg │ └── readme.zip ├── test_zip.html └── test_zip.dart ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── build.yaml ├── pubspec.yaml ├── analysis_options.yaml ├── bin └── tar.dart ├── LICENSE ├── example └── example.dart ├── doc └── migrating_3_to_4.md ├── LICENSE-other.md └── README.md /test/_data/emptyfile.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/_data/folder/abc.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /test/_data/test2/test.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /test/_data/tar/folder/abc.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /test/_data/tar/small.txt: -------------------------------------------------------------------------------- 1 | Kilts -------------------------------------------------------------------------------- /test/_data/test2/test/test.txt: -------------------------------------------------------------------------------- 1 | def -------------------------------------------------------------------------------- /test/_data/zip/hello.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /test/_data/folder/with_contents/def.txt: -------------------------------------------------------------------------------- 1 | def -------------------------------------------------------------------------------- /test/_data/tar/small2.txt: -------------------------------------------------------------------------------- 1 | Google.com 2 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | test/ 2 | web/ 3 | example/ 4 | -------------------------------------------------------------------------------- /test/_data/tar/folder/with_contents/def.txt: -------------------------------------------------------------------------------- 1 | def -------------------------------------------------------------------------------- /lib/src/util/byte_order.dart: -------------------------------------------------------------------------------- 1 | enum ByteOrder { littleEndian, bigEndian } 2 | -------------------------------------------------------------------------------- /lib/src/util/file_access.dart: -------------------------------------------------------------------------------- 1 | enum FileAccess { closed, read, write } 2 | -------------------------------------------------------------------------------- /web/res/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/web/res/cat.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .dart_tool/ 3 | .idea/ 4 | _out/ 5 | pubspec.lock 6 | 7 | /build 8 | -------------------------------------------------------------------------------- /lib/src/io/posix.dart: -------------------------------------------------------------------------------- 1 | export 'posix_html.dart' if (dart.library.io) 'posix_io.dart'; 2 | -------------------------------------------------------------------------------- /test/_data/a.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/a.txt.gz -------------------------------------------------------------------------------- /test/_data/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/cat.jpg -------------------------------------------------------------------------------- /test/_data/test.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test.tar -------------------------------------------------------------------------------- /test/_data/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test.zip -------------------------------------------------------------------------------- /test/_data/test2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test2.zip -------------------------------------------------------------------------------- /web/res/readme.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/web/res/readme.zip -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: brendan-duncan 4 | -------------------------------------------------------------------------------- /test/_data/cat.jpg.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/cat.jpg.gz -------------------------------------------------------------------------------- /test/_data/example.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/example.tar -------------------------------------------------------------------------------- /test/_data/folder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/folder.zip -------------------------------------------------------------------------------- /test/_data/tar/v7.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/tar/v7.tar -------------------------------------------------------------------------------- /test/_data/xz/crc32.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/crc32.xz -------------------------------------------------------------------------------- /test/_data/xz/crc64.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/crc64.xz -------------------------------------------------------------------------------- /test/_data/xz/empty.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/empty.xz -------------------------------------------------------------------------------- /test/_data/xz/hello.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/hello.xz -------------------------------------------------------------------------------- /test/_data/zip/dd.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/dd.zip -------------------------------------------------------------------------------- /test/_data/zip/utf.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/utf.zip -------------------------------------------------------------------------------- /test/_data/bzip2/test.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/bzip2/test.bz2 -------------------------------------------------------------------------------- /test/_data/tar/folder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/tar/folder.zip -------------------------------------------------------------------------------- /test/_data/test2.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test2.tar.bz2 -------------------------------------------------------------------------------- /test/_data/test2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test2.tar.gz -------------------------------------------------------------------------------- /test/_data/xz/cat.jpg.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/cat.jpg.xz -------------------------------------------------------------------------------- /test/_data/xz/nocheck.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/nocheck.xz -------------------------------------------------------------------------------- /test/_data/xz/sha256.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/sha256.xz -------------------------------------------------------------------------------- /test/_data/zip/aes256.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/aes256.zip -------------------------------------------------------------------------------- /test/_data/zip/readme.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/readme.zip -------------------------------------------------------------------------------- /test/_data/zip/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/test.zip -------------------------------------------------------------------------------- /test/_data/zip/unix.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/unix.zip -------------------------------------------------------------------------------- /test/_data/zip/winxp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/winxp.zip -------------------------------------------------------------------------------- /test/_data/zip/zip64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/zip64.zip -------------------------------------------------------------------------------- /lib/src/util/file_handle.dart: -------------------------------------------------------------------------------- 1 | export '_file_handle_html.dart' if (dart.library.io) '_file_handle_io.dart'; 2 | -------------------------------------------------------------------------------- /test/_data/inflate/data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/inflate/data.bin -------------------------------------------------------------------------------- /test/_data/zip/symlink.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/symlink.zip -------------------------------------------------------------------------------- /test/_data/tar/writer-big.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/tar/writer-big.tar -------------------------------------------------------------------------------- /test/_data/test_100k_files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/test_100k_files.zip -------------------------------------------------------------------------------- /test/_data/zip/readme.notzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/readme.notzip -------------------------------------------------------------------------------- /test/_data/zip/zipCrypto.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/zipCrypto.zip -------------------------------------------------------------------------------- /test/_data/zip/zip_bzip2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/zip_bzip2.zip -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_decoder.dart: -------------------------------------------------------------------------------- 1 | export '_gzip_decoder_web.dart' if (dart.library.io) '_gzip_decoder_io.dart'; 2 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_encoder.dart: -------------------------------------------------------------------------------- 1 | export '_gzip_encoder_web.dart' if (dart.library.io) '_gzip_encoder_io.dart'; 2 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_decoder.dart: -------------------------------------------------------------------------------- 1 | export '_zlib_decoder_web.dart' if (dart.library.io) '_zlib_decoder_io.dart'; 2 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_encoder.dart: -------------------------------------------------------------------------------- 1 | export '_zlib_encoder_web.dart' if (dart.library.io) '_zlib_encoder_io.dart'; 2 | -------------------------------------------------------------------------------- /test/_data/xz/good-1-lzma2-1.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/good-1-lzma2-1.xz -------------------------------------------------------------------------------- /test/_data/zip/zip64_archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/zip64_archive.zip -------------------------------------------------------------------------------- /test/_data/xz/hello-hello-hello.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/xz/hello-hello-hello.xz -------------------------------------------------------------------------------- /test/_data/zip/android-javadoc.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/android-javadoc.zip -------------------------------------------------------------------------------- /test/_data/zip/gophercolor16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/gophercolor16x16.png -------------------------------------------------------------------------------- /test/_data/zip/crc32-not-streamed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/crc32-not-streamed.zip -------------------------------------------------------------------------------- /test/_data/zip/go-no-datadesc-sig.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/go-no-datadesc-sig.zip -------------------------------------------------------------------------------- /test/_data/zip/password_zipcrypto.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/password_zipcrypto.zip -------------------------------------------------------------------------------- /test/_data/zip/test-trailing-junk.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/test-trailing-junk.zip -------------------------------------------------------------------------------- /test/_data/zip/go-with-datadesc-sig.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendan-duncan/archive/HEAD/test/_data/zip/go-with-datadesc-sig.zip -------------------------------------------------------------------------------- /lib/src/io/posix_html.dart: -------------------------------------------------------------------------------- 1 | bool isPosixSupported() { 2 | return false; 3 | } 4 | 5 | void chmod(String path, String permissions) {} 6 | -------------------------------------------------------------------------------- /lib/src/io/posix_stub.dart: -------------------------------------------------------------------------------- 1 | bool isPosixSupported() { 2 | return false; 3 | } 4 | 5 | void chmod(String path, String permissions) {} 6 | -------------------------------------------------------------------------------- /lib/src/archive/encryption_type.dart: -------------------------------------------------------------------------------- 1 | /// The type of encryption used to encrypt archive file contents. 2 | enum EncryptionType { none, aes, zipCrypto } 3 | -------------------------------------------------------------------------------- /lib/src/archive/compression_type.dart: -------------------------------------------------------------------------------- 1 | /// The format of the compression an [ArchiveFile] is stored with. 2 | enum CompressionType { none, deflate, bzip2 } 3 | -------------------------------------------------------------------------------- /lib/src/util/_cast.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | Uint8List castToUint8List(T data) => 4 | data is Uint8List ? data : data as Uint8List; 5 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_inflate_buffer_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'inflate.dart'; 4 | 5 | Uint8List? inflateBuffer_(List data) => Inflate(data).getBytes(); 6 | -------------------------------------------------------------------------------- /lib/src/util/_crc64_html.dart: -------------------------------------------------------------------------------- 1 | bool isCrc64Supported_() => false; 2 | 3 | int getCrc64_(List array, [int crc = 0]) { 4 | throw UnsupportedError('Crc64 is not support on html'); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_inflate_buffer_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | Uint8List? inflateBuffer_(List data) => 5 | ZLibDecoder(raw: true).convert(data) as Uint8List; 6 | -------------------------------------------------------------------------------- /lib/src/util/archive_exception.dart: -------------------------------------------------------------------------------- 1 | /// An exception thrown when there was a problem in the archive library. 2 | class ArchiveException extends FormatException { 3 | ArchiveException(super.message); 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/io/zip_file_progress.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | enum ZipFileOperation { include, skip, cancel } 4 | 5 | typedef ZipFileProgress = ZipFileOperation Function( 6 | FileSystemEntity entity, double progress); 7 | -------------------------------------------------------------------------------- /lib/src/util/crc64.dart: -------------------------------------------------------------------------------- 1 | import '_crc64_html.dart' if (dart.library.io) '_crc64_io.dart'; 2 | 3 | int getCrc64(List array, [int crc = 0]) => getCrc64_(array, crc); 4 | 5 | bool isCrc64Supported() => isCrc64Supported_(); 6 | -------------------------------------------------------------------------------- /web/test_zip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test_zip 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/inflate_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import '_inflate_buffer_web.dart' 5 | if (dart.library.io) '_inflate_buffer_io.dart'; 6 | 7 | FutureOr? inflateBuffer(List data) => inflateBuffer_(data); 8 | -------------------------------------------------------------------------------- /lib/archive_io.dart: -------------------------------------------------------------------------------- 1 | library archive_io; 2 | 3 | export 'archive.dart'; 4 | 5 | export 'src/io/create_archive_from_directory.dart'; 6 | export 'src/io/extract_archive_to_disk.dart'; 7 | export 'src/io/tar_command.dart'; 8 | export 'src/io/tar_file_encoder.dart'; 9 | export 'src/io/zip_file_encoder.dart'; 10 | export 'src/io/zip_file_progress.dart'; 11 | -------------------------------------------------------------------------------- /lib/src/io/posix_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:posix/posix.dart' as posix; 4 | 5 | bool isPosixSupported() { 6 | try { 7 | return (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) && 8 | posix.isPosixSupported; 9 | } catch (_) { 10 | return false; 11 | } 12 | } 13 | 14 | void chmod(String path, String permissions) { 15 | posix.chmod(path, permissions); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_decoder_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import '../../util/input_stream.dart'; 3 | import '../../util/output_stream.dart'; 4 | 5 | abstract class ZLibDecoderBase { 6 | const ZLibDecoderBase(); 7 | 8 | Uint8List decodeBytes(List bytes, 9 | {bool verify = false, bool raw = false}); 10 | 11 | bool decodeStream(InputStream input, OutputStream output, 12 | {bool verify = false, bool raw = false}); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_encoder_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import '../../util/input_stream.dart'; 3 | import '../../util/output_stream.dart'; 4 | 5 | abstract class ZLibEncoderBase { 6 | const ZLibEncoderBase(); 7 | 8 | Uint8List encodeBytes(List bytes, 9 | {int? level, int? windowBits, bool raw = false}); 10 | 11 | void encodeStream(InputStream input, OutputStream output, 12 | {int? level, int? windowBits, bool raw = false}); 13 | } 14 | -------------------------------------------------------------------------------- /test/_data/xz/expected/good-1-lzma2-1: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipisicing 2 | elit, sed do eiusmod tempor incididunt ut 3 | labore et dolore magna aliqua. Ut enim 4 | ad minim veniam, quis nostrud exercitation ullamco 5 | laboris nisi ut aliquip ex ea commodo 6 | consequat. Duis aute irure dolor in reprehenderit 7 | in voluptate velit esse cillum dolore eu 8 | fugiat nulla pariatur. Excepteur sint occaecat cupidatat 9 | non proident, sunt in culpa qui officia 10 | deserunt mollit anim id est laborum. 11 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: archive 2 | version: 4.0.7 3 | description: >- 4 | Provides encoders and decoders for various archive and compression formats 5 | such as zip, tar, bzip2, gzip, and zlib. 6 | repository: https://github.com/brendan-duncan/archive 7 | topics: 8 | - archive 9 | - zip 10 | - tar 11 | - gzip 12 | - xz 13 | environment: 14 | sdk: '>=3.0.0 <4.0.0' 15 | 16 | dependencies: 17 | path: ^1.8.0 18 | posix: ^6.0.2 19 | 20 | dev_dependencies: 21 | build_runner: ^2.4.14 22 | build_web_compilers: ^4.1.0 23 | http: ^1.1.0 24 | lints: ^4.0.0 25 | test: ^1.24.6 26 | web: ^1.1.0 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | schedule: 5 | # “At 00:00 (UTC) on Sunday.” 6 | - cron: '0 0 * * 0' 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | # https://github.com/dart-lang/setup-dart/blob/main/README.md 20 | - uses: dart-lang/setup-dart@v1 21 | 22 | - name: Install dependencies 23 | run: dart pub get 24 | 25 | - name: Analyze project source 26 | run: dart analyze --fatal-infos 27 | 28 | - name: Run tests 29 | run: dart test 30 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/core.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | strict-raw-types: true 8 | errors: 9 | constant_identifier_names: error 10 | non_constant_identifier_names: error 11 | unused_element: error 12 | unused_field: error 13 | unused_local_variable: error 14 | exclude: 15 | - test/_data/** 16 | - _out/** 17 | 18 | linter: 19 | rules: 20 | - avoid_print 21 | - always_declare_return_types 22 | - camel_case_types 23 | - directives_ordering 24 | - discarded_futures 25 | - sort_pub_dependencies 26 | - unawaited_futures 27 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/gzip_decoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_stream.dart'; 4 | import '../../util/output_stream.dart'; 5 | import '_gzip_decoder_web.dart'; 6 | import '_zlib_decoder_base.dart'; 7 | 8 | class GZipDecoderWeb extends ZLibDecoderBase { 9 | const GZipDecoderWeb(); 10 | 11 | @override 12 | Uint8List decodeBytes(List data, 13 | {bool verify = false, bool raw = false}) => 14 | platformGZipDecoder.decodeBytes(data, verify: verify, raw: raw); 15 | 16 | @override 17 | bool decodeStream(InputStream input, OutputStream output, 18 | {bool verify = false, bool raw = false}) => 19 | platformGZipDecoder.decodeStream(input, output, verify: verify, raw: raw); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/zlib_decoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_stream.dart'; 4 | import '../../util/output_stream.dart'; 5 | import '_zlib_decoder_base.dart'; 6 | import '_zlib_decoder_web.dart'; 7 | 8 | class ZLibDecoderWeb extends ZLibDecoderBase { 9 | const ZLibDecoderWeb(); 10 | 11 | @override 12 | Uint8List decodeBytes(List data, 13 | {bool verify = false, bool raw = false}) => 14 | platformZLibDecoder.decodeBytes(data, verify: verify, raw: raw); 15 | 16 | @override 17 | bool decodeStream(InputStream input, OutputStream output, 18 | {bool verify = false, bool raw = false}) => 19 | platformZLibDecoder.decodeStream(input, output, verify: verify, raw: raw); 20 | } 21 | -------------------------------------------------------------------------------- /test/crc32_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:archive/archive.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('crc32', () { 6 | test('empty', () { 7 | final crcVal = getCrc32([]); 8 | expect(crcVal, 0); 9 | }); 10 | test('1 byte', () { 11 | final crcVal = getCrc32([1]); 12 | expect(crcVal, 0xA505DF1B); 13 | }); 14 | test('10 bytes', () { 15 | final crcVal = getCrc32([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); 16 | expect(crcVal, 0xC5F5BE65); 17 | }); 18 | test('100000 bytes', () { 19 | var crcVal = getCrc32([]); 20 | for (var i = 0; i < 10000; i++) { 21 | crcVal = getCrc32([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], crcVal); 22 | } 23 | expect(crcVal, 0x3AC67C2B); 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /test/bzip2_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' as io; 2 | 3 | import 'package:archive/archive.dart'; 4 | import 'package:path/path.dart' as p; 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | test('decode', () { 9 | final orig = io.File(p.join('test/_data/bzip2/test.bz2')).readAsBytesSync(); 10 | 11 | BZip2Decoder().decodeBytes(orig, verify: true); 12 | }); 13 | 14 | test('encode', () { 15 | final file = io.File(p.join('test/_data/cat.jpg')).readAsBytesSync(); 16 | 17 | final compressed = BZip2Encoder().encodeBytes(file); 18 | 19 | final d2 = BZip2Decoder().decodeBytes(compressed, verify: true); 20 | 21 | expect(d2.length, equals(file.length)); 22 | for (var i = 0, len = d2.length; i < len; ++i) { 23 | expect(d2[i], equals(file[i])); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /test/adler32_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:archive/archive.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('adler32', () { 6 | test('empty', () { 7 | final adlerVal = getAdler32([]); 8 | expect(adlerVal, 1); 9 | }); 10 | test('1 byte', () { 11 | final adlerVal = getAdler32([1]); 12 | expect(adlerVal, 0x20002); 13 | }); 14 | test('10 bytes', () { 15 | final adlerVal = getAdler32([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); 16 | expect(adlerVal, 0xDC002E); 17 | }); 18 | test('100000 bytes', () { 19 | var adlerVal = getAdler32([]); 20 | for (var i = 0; i < 10000; i++) { 21 | adlerVal = getAdler32([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], adlerVal); 22 | } 23 | expect(adlerVal, 0x96C8DE2B); 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/gzip_flag.dart: -------------------------------------------------------------------------------- 1 | class GZipFlag { 2 | static const signature = 0x8b1f; 3 | static const deflate = 8; 4 | static const text = 0x01; 5 | static const hcrc = 0x02; 6 | static const extra = 0x04; 7 | static const name = 0x08; 8 | static const comment = 0x10; 9 | 10 | // enum OperatingSystem 11 | static const osFat = 0; 12 | static const osAmiga = 1; 13 | static const osVMS = 2; 14 | static const osUnix = 3; 15 | static const osVmCms = 4; 16 | static const osAtariTos = 5; 17 | static const osHpfs = 6; 18 | static const osMacintosh = 7; 19 | static const osZSystem = 8; 20 | static const osCpM = 9; 21 | static const osTops20 = 10; 22 | static const osNtfs = 11; 23 | static const osQDos = 12; 24 | static const osAcornRiscOS = 13; 25 | static const osUnknown = 255; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/zlib_encoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_stream.dart'; 4 | import '../../util/output_stream.dart'; 5 | import '_zlib_encoder_base.dart'; 6 | import '_zlib_encoder_web.dart'; 7 | 8 | class ZLibEncoderWeb extends ZLibEncoderBase { 9 | const ZLibEncoderWeb(); 10 | 11 | @override 12 | Uint8List encodeBytes(List data, 13 | {int? level, int? windowBits, bool raw = false}) => 14 | platformZLibEncoder.encodeBytes(data, 15 | level: level, windowBits: windowBits, raw: raw); 16 | 17 | @override 18 | void encodeStream(InputStream input, OutputStream output, 19 | {int? level, int? windowBits, bool raw = false}) => 20 | platformZLibEncoder.encodeStream(input, output, 21 | level: level, windowBits: windowBits, raw: raw); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/gzip_encoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_stream.dart'; 4 | import '../../util/output_stream.dart'; 5 | import '_gzip_encoder_web.dart'; 6 | import '_zlib_encoder_base.dart'; 7 | 8 | class GZipEncoderWeb extends ZLibEncoderBase { 9 | const GZipEncoderWeb(); 10 | 11 | @override 12 | Uint8List encodeBytes(List data, 13 | {int? level, int? windowBits, bool raw = false}) => 14 | platformGZipEncoder.encodeBytes(data, 15 | level: level ?? 6, windowBits: windowBits, raw: raw); 16 | 17 | @override 18 | void encodeStream(InputStream input, OutputStream output, 19 | {int? level, int? windowBits, bool raw = false}) => 20 | platformGZipEncoder.encodeStream(input, output, 21 | level: level ?? 6, windowBits: windowBits, raw: raw); 22 | } 23 | -------------------------------------------------------------------------------- /test/_data/tar/nil-uid.tar: -------------------------------------------------------------------------------- 1 | P1050238.JPG.log00006640000000001612130627766012777 0ustar eyefieyefi121304042001213062776644,44,POWERON -------------------------------------------------------------------------------- /bin/tar.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | import 'dart:io'; 3 | import 'package:archive/archive_io.dart'; 4 | 5 | // tar --list 6 | // tar --extract 7 | // tar --create 8 | const usage = 'usage: tar [--list|--extract|--create] [|]'; 9 | 10 | void _fail(String message) { 11 | print(message); 12 | exit(1); 13 | } 14 | 15 | void main(List arguments) async { 16 | if (arguments.isEmpty) { 17 | _fail(usage); 18 | } 19 | 20 | final command = arguments[0]; 21 | if (command == '--list') { 22 | if (arguments.length < 2) { 23 | _fail(usage); 24 | } 25 | listTarFiles(arguments[1]); 26 | } else if (command == '--extract') { 27 | if (arguments.length < 3) { 28 | _fail(usage); 29 | } 30 | extractTarFiles(arguments[1], arguments[2]); 31 | } else if (command == '--create') { 32 | if (arguments.length < 2) { 33 | _fail(usage); 34 | } 35 | await createTarFile(arguments[1]); 36 | } else { 37 | _fail(usage); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/test_zip.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | import 'package:archive/archive.dart'; 3 | import 'package:http/http.dart' as http; 4 | import 'package:web/web.dart'; 5 | 6 | void main() async { 7 | // An img on the html page is used to establish the path to the images 8 | // directory. It's removed after we get the path since we'll be populating 9 | // the page with our own decoded images. 10 | final img = document.querySelectorAll('img').item(0) as HTMLImageElement; 11 | final path = img.src.substring(0, img.src.lastIndexOf('/')); 12 | img.remove(); 13 | 14 | // Use an http request to get the image file from disk. 15 | var url = Uri.parse('$path/readme.zip'); 16 | var response = await http.get(url); 17 | if (response.statusCode == 200) { 18 | final archive = ZipDecoder().decodeBytes(response.bodyBytes); 19 | print('NUMBER OF FILES ${archive.length}'); 20 | print(archive[0].name); 21 | print(archive[0].size); 22 | final decoded = archive[0].readBytes(); 23 | print(String.fromCharCodes(decoded!)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2021 Brendan Duncan. 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /test/_test_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | final testOutputPath = '${Directory.systemTemp.createTempSync().path}'; 6 | //const testOutputPath = './_out'; 7 | 8 | const aTxt = '''this is a test 9 | of the 10 | zip archive 11 | format. 12 | this is a test 13 | of the 14 | zip archive 15 | format. 16 | this is a test 17 | of the 18 | zip archive 19 | format. 20 | '''; 21 | 22 | void compareBytes(List actual, List expected) { 23 | expect(actual.length, equals(expected.length)); 24 | final len = actual.length; 25 | for (var i = 0; i < len; ++i) { 26 | expect(actual[i], equals(expected[i]), 27 | reason: 'Wrong value for Byte at index $i'); 28 | } 29 | } 30 | 31 | void listDir(List files, Directory dir) { 32 | var fileOrDirs = dir.listSync(recursive: true); 33 | for (final f in fileOrDirs) { 34 | if (f is File) { 35 | // Ignore paxHeader files, which 7zip write out since it doesn't properly 36 | // handle POSIX tar files. 37 | if (f.path.contains('PaxHeader')) { 38 | continue; 39 | } 40 | files.add(f); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/codecs/bzip2/bz2_bit_reader.dart: -------------------------------------------------------------------------------- 1 | import '../../util/input_stream.dart'; 2 | 3 | /// Internal class used by [BZip2Decoder] 4 | class Bz2BitReader { 5 | InputStream input; 6 | 7 | Bz2BitReader(this.input); 8 | 9 | int readByte() => readBits(8); 10 | 11 | /// Read a number of bits from the input stream. 12 | int readBits(int numBits) { 13 | if (numBits == 0) { 14 | return 0; 15 | } 16 | 17 | if (_bitPos == 0) { 18 | _bitPos = 8; 19 | _bitBuffer = input.readByte(); 20 | } 21 | 22 | var value = 0; 23 | 24 | while (numBits > _bitPos) { 25 | value = (value << _bitPos) + (_bitBuffer & _bitMask[_bitPos]); 26 | numBits -= _bitPos; 27 | _bitPos = 8; 28 | _bitBuffer = input.readByte(); 29 | } 30 | 31 | if (numBits > 0) { 32 | if (_bitPos == 0) { 33 | _bitPos = 8; 34 | _bitBuffer = input.readByte(); 35 | } 36 | 37 | value = (value << numBits) + 38 | (_bitBuffer >> (_bitPos - numBits) & _bitMask[numBits]); 39 | 40 | _bitPos -= numBits; 41 | } 42 | 43 | return value; 44 | } 45 | 46 | int _bitBuffer = 0; 47 | int _bitPos = 0; 48 | 49 | static const List _bitMask = [0, 1, 3, 7, 15, 31, 63, 127, 255]; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/util/adler32.dart: -------------------------------------------------------------------------------- 1 | import 'input_stream.dart'; 2 | 3 | int getAdler32Stream(InputStream stream, [int adler = 1]) { 4 | // largest prime smaller than 65536 5 | const base = 65521; 6 | 7 | var s1 = adler & 0xffff; 8 | var s2 = adler >> 16; 9 | var len = stream.length; 10 | while (len > 0) { 11 | var n = 3800; 12 | if (n > len) { 13 | n = len; 14 | } 15 | len -= n; 16 | while (--n >= 0) { 17 | s1 = s1 + stream.readByte(); 18 | s2 = s2 + s1; 19 | } 20 | s1 %= base; 21 | s2 %= base; 22 | } 23 | 24 | return (s2 << 16) | s1; 25 | } 26 | 27 | /// Get the Adler-32 checksum for the given array. You can append bytes to an 28 | /// already computed adler checksum by specifying the previous [adler] value. 29 | int getAdler32(List array, [int adler = 1]) { 30 | // largest prime smaller than 65536 31 | const base = 65521; 32 | 33 | var s1 = adler & 0xffff; 34 | var s2 = adler >> 16; 35 | var len = array.length; 36 | var i = 0; 37 | while (len > 0) { 38 | var n = 3800; 39 | if (n > len) { 40 | n = len; 41 | } 42 | len -= n; 43 | while (--n >= 0) { 44 | s1 = s1 + (array[i++] & 0xff); 45 | s2 = s2 + s1; 46 | } 47 | s1 %= base; 48 | s2 %= base; 49 | } 50 | 51 | return (s2 << 16) | s1; 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_decoder_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | 6 | import '../../util/input_stream.dart'; 7 | import '../../util/output_stream.dart'; 8 | import '_zlib_decoder_base.dart'; 9 | 10 | const platformZLibDecoder = _ZLibDecoder(); 11 | 12 | /// Decompress data with the zlib format decoder. 13 | class _ZLibDecoder extends ZLibDecoderBase { 14 | const _ZLibDecoder(); 15 | 16 | @override 17 | Uint8List decodeBytes(List data, 18 | {bool verify = false, bool raw = false}) => 19 | ZLibCodec(raw: raw).decode(data) as Uint8List; 20 | 21 | @override 22 | bool decodeStream(InputStream input, OutputStream output, 23 | {bool verify = false, bool raw = false}) { 24 | final outSink = ChunkedConversionSink>.withCallback((chunks) { 25 | for (final chunk in chunks) { 26 | output.writeBytes(chunk); 27 | } 28 | output.flush(); 29 | }); 30 | 31 | final inSink = ZLibCodec(raw: raw).decoder.startChunkedConversion(outSink); 32 | 33 | while (!input.isEOS) { 34 | final chunkSize = min(1024, input.length); 35 | final chunk = input.readBytes(chunkSize).toUint8List(); 36 | inSink.add(chunk); 37 | } 38 | inSink.close(); 39 | 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_huffman_table.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// Build huffman table from length list. 4 | class HuffmanTable { 5 | late Uint32List table; 6 | int maxCodeLength = 0; 7 | int minCodeLength = 0x7fffffff; 8 | 9 | HuffmanTable(List lengths) { 10 | final listSize = lengths.length; 11 | 12 | for (var i = 0; i < listSize; ++i) { 13 | if (lengths[i] > maxCodeLength) { 14 | maxCodeLength = lengths[i]; 15 | } 16 | if (lengths[i] < minCodeLength) { 17 | minCodeLength = lengths[i]; 18 | } 19 | } 20 | 21 | final size = 1 << maxCodeLength; 22 | table = Uint32List(size); 23 | 24 | for (var bitLength = 1, code = 0, skip = 2; bitLength <= maxCodeLength;) { 25 | for (var i = 0; i < listSize; ++i) { 26 | if (lengths[i] == bitLength) { 27 | var reversed = 0; 28 | var rTemp = code; 29 | for (var j = 0; j < bitLength; ++j) { 30 | reversed = (reversed << 1) | (rTemp & 1); 31 | rTemp >>= 1; 32 | } 33 | 34 | for (var j = reversed; j < size; j += skip) { 35 | table[j] = (bitLength << 16) | i; 36 | } 37 | 38 | ++code; 39 | } 40 | } 41 | 42 | ++bitLength; 43 | code <<= 1; 44 | skip <<= 1; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_decoder_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | 6 | import '../../util/input_stream.dart'; 7 | import '../../util/output_stream.dart'; 8 | import '_zlib_decoder_base.dart'; 9 | 10 | const platformGZipDecoder = _GZipDecoder(); 11 | 12 | /// Decompress data with the zlib format decoder. 13 | class _GZipDecoder extends ZLibDecoderBase { 14 | const _GZipDecoder(); 15 | 16 | @override 17 | Uint8List decodeBytes(List data, 18 | {bool verify = false, bool raw = false}) => 19 | GZipCodec().decode(data) as Uint8List; 20 | 21 | @override 22 | bool decodeStream(InputStream input, OutputStream output, 23 | {bool verify = false, bool raw = false}) { 24 | final outSink = ChunkedConversionSink>.withCallback((chunks) { 25 | for (final chunk in chunks) { 26 | output.writeBytes(chunk); 27 | } 28 | output.flush(); 29 | }); 30 | 31 | final inSink = GZipCodec().decoder.startChunkedConversion(outSink); 32 | 33 | while (!input.isEOS) { 34 | final chunkSize = min(1024, input.length); 35 | final chunk = input.readBytes(chunkSize).toUint8List(); 36 | inSink.add(chunk); 37 | } 38 | inSink.close(); 39 | 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/io/create_archive_from_directory.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:path/path.dart' as path; 3 | 4 | import '../archive/archive.dart'; 5 | import '../archive/archive_file.dart'; 6 | import '../util/input_file_stream.dart'; 7 | 8 | Archive createArchiveFromDirectory(Directory dir, 9 | {bool includeDirName = true}) { 10 | final archive = Archive(); 11 | 12 | final dirName = path.basename(dir.path); 13 | final files = dir.listSync(recursive: true); 14 | for (final file in files) { 15 | // If it's a Directory, only add empty directories 16 | if (file is Directory) { 17 | var filename = path.relative(file.path, from: dir.path); 18 | filename = includeDirName ? ('$dirName/$filename') : filename; 19 | final af = ArchiveFile.directory('$filename/'); 20 | af.mode = file.statSync().mode; 21 | archive.add(af); 22 | } else if (file is File) { 23 | // It's a File 24 | var filename = path.relative(file.path, from: dir.path); 25 | filename = includeDirName ? ('$dirName/$filename') : filename; 26 | 27 | final fileStream = InputFileStream(file.path); 28 | final af = ArchiveFile.stream(filename, fileStream); 29 | af.lastModTime = file.lastModifiedSync().millisecondsSinceEpoch ~/ 1000; 30 | af.mode = file.statSync().mode; 31 | 32 | archive.add(af); 33 | } 34 | } 35 | 36 | return archive; 37 | } 38 | -------------------------------------------------------------------------------- /test/deflate_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:archive/archive.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | final buffer = Uint8List(0xfffff); 8 | for (var i = 0; i < buffer.length; ++i) { 9 | buffer[i] = i % 256; 10 | } 11 | 12 | test('NO_COMPRESSION', () { 13 | final deflated = Deflate(buffer, level: DeflateLevel.none).getBytes(); 14 | 15 | final inflated = Inflate(deflated).getBytes(); 16 | 17 | expect(inflated.length, equals(buffer.length)); 18 | for (var i = 0; i < buffer.length; ++i) { 19 | expect(inflated[i], equals(buffer[i])); 20 | } 21 | }); 22 | 23 | test('BEST_SPEED', () { 24 | final deflated = Deflate(buffer, level: DeflateLevel.bestSpeed).getBytes(); 25 | 26 | final inflated = Inflate(deflated).getBytes(); 27 | 28 | expect(inflated.length, equals(buffer.length)); 29 | for (var i = 0; i < buffer.length; ++i) { 30 | expect(inflated[i], equals(buffer[i])); 31 | } 32 | }); 33 | 34 | test('BEST_COMPRESSION', () { 35 | final deflated = 36 | Deflate(buffer, level: DeflateLevel.bestCompression).getBytes(); 37 | 38 | final inflated = Inflate(deflated).getBytes(); 39 | 40 | expect(inflated.length, equals(buffer.length)); 41 | for (var i = 0; i < buffer.length; ++i) { 42 | expect(inflated[i], equals(buffer[i])); 43 | } 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/codecs/gzip_encoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/input_stream.dart'; 4 | import '../util/output_stream.dart'; 5 | import 'zlib/_gzip_encoder.dart'; 6 | 7 | /// Compress data with the GZip format encoder. 8 | /// The actual encoder used will depend on the platform the code is run on. 9 | /// In a 'dart:io' based platform, like Flutter, the native GZipCodec will 10 | /// be used to improve performance. On web platforms, a Dart implementation 11 | /// of ZLib will be used, via the [Deflate] class. 12 | /// If you want to force the use of the Dart implementation, you can use the 13 | /// [GZipEncoderWeb] class. 14 | class GZipEncoder { 15 | const GZipEncoder(); 16 | 17 | /// Compress the given [bytes] with the GZip format. 18 | /// [level] will set the compression level to use, between 0 and 9. 19 | Uint8List encodeBytes(List bytes, {int? level}) => 20 | platformGZipEncoder.encodeBytes(bytes, level: level); 21 | 22 | /// Alias for [encodeBytes], kept for backwards compatibility. 23 | List encode(List bytes, {int? level}) => 24 | encodeBytes(bytes, level: level); 25 | 26 | /// Compress the given [input] stream with the GZip format. 27 | /// [level] will set the compression level to use, between 0 and 9. 28 | void encodeStream(InputStream input, OutputStream output, {int? level}) => 29 | platformGZipEncoder.encodeStream(input, output, level: level); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_encoder_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | 6 | import '../../util/input_stream.dart'; 7 | import '../../util/output_stream.dart'; 8 | import '_zlib_encoder_base.dart'; 9 | 10 | const platformZLibEncoder = _ZLibEncoder(); 11 | 12 | class _ZLibEncoder extends ZLibEncoderBase { 13 | const _ZLibEncoder(); 14 | 15 | Uint8List encodeBytes(List bytes, 16 | {int? level, int? windowBits, bool raw = false}) => 17 | ZLibCodec(level: level ?? 6, windowBits: windowBits ?? 15, raw: raw) 18 | .encode(bytes) as Uint8List; 19 | 20 | void encodeStream(InputStream input, OutputStream output, 21 | {int? level, int? windowBits, bool raw = false}) { 22 | final outSink = ChunkedConversionSink>.withCallback((chunks) { 23 | for (final chunk in chunks) { 24 | output.writeBytes(chunk); 25 | } 26 | output.flush(); 27 | }); 28 | 29 | final inSink = 30 | ZLibCodec(level: level ?? 6, windowBits: windowBits ?? 15, raw: raw) 31 | .encoder 32 | .startChunkedConversion(outSink); 33 | 34 | while (!input.isEOS) { 35 | final chunkSize = min(1024, input.length); 36 | final chunk = input.readBytes(chunkSize).toUint8List(); 37 | inSink.add(chunk); 38 | } 39 | inSink.close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/file_handle_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:archive/archive_io.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import '_test_util.dart'; 8 | 9 | void main() { 10 | final testData = Uint8List(120); 11 | for (var i = 0; i < testData.length; ++i) { 12 | testData[i] = i; 13 | } 14 | final testPath = '$testOutputPath/test_123.bin'; 15 | File(testPath) 16 | ..createSync(recursive: true) 17 | ..writeAsBytesSync(testData); 18 | 19 | group('FileHandle', () { 20 | test('open', () async { 21 | final fh = FileHandle(testPath); 22 | expect(fh.isOpen, true); 23 | await fh.close(); 24 | expect(fh.isOpen, false); 25 | }); 26 | 27 | test('length', () async { 28 | final fh = FileHandle(testPath); 29 | expect(fh.length, testData.length); 30 | }); 31 | 32 | test('position', () async { 33 | final fh = FileHandle(testPath); 34 | fh.position = 10; 35 | expect(fh.position, equals(10)); 36 | fh.position = fh.length - 10; 37 | expect(fh.position, equals(110)); 38 | }); 39 | 40 | test('readInto', () async { 41 | final fh = FileHandle(testPath); 42 | final bytes = Uint8List(10); 43 | fh.readInto(bytes); 44 | for (var i = 0; i < 10; ++i) { 45 | expect(bytes[i], equals(i)); 46 | } 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_encoder_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | import '../../util/input_stream.dart'; 6 | import '../../util/output_stream.dart'; 7 | import '_zlib_encoder_base.dart'; 8 | 9 | const platformGZipEncoder = _GZipEncoder(); 10 | 11 | class _GZipEncoder extends ZLibEncoderBase { 12 | const _GZipEncoder(); 13 | 14 | @override 15 | Uint8List encodeBytes(List bytes, 16 | {int? level, int? windowBits, bool raw = false}) => 17 | GZipCodec(level: level ?? 6, windowBits: windowBits ?? 15, raw: raw) 18 | .encode(bytes) as Uint8List; 19 | 20 | @override 21 | void encodeStream(InputStream input, OutputStream output, 22 | {int? level, int? windowBits, bool raw = false}) { 23 | final outSink = ChunkedConversionSink>.withCallback((chunks) { 24 | for (final chunk in chunks) { 25 | output.writeBytes(chunk); 26 | } 27 | output.flush(); 28 | }); 29 | 30 | final inSink = 31 | GZipCodec(level: level ?? 6, windowBits: windowBits ?? 15, raw: raw) 32 | .encoder 33 | .startChunkedConversion(outSink); 34 | 35 | while (!input.isEOS) { 36 | final chunkSize = min(1024, input.length); 37 | final chunk = input.readBytes(chunkSize).toUint8List(); 38 | inSink.add(chunk); 39 | } 40 | inSink.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:archive/archive_io.dart'; 3 | 4 | Future main() async { 5 | // Read the Zip file from disk. 6 | final input = InputFileStream('test.zip'); 7 | 8 | // Decode the Zip file 9 | final archive = ZipDecoder().decodeStream(input); 10 | 11 | // Extract the contents of the Zip archive to disk. 12 | for (final file in archive) { 13 | final filename = file.name; 14 | if (file.isFile) { 15 | final output = OutputFileStream('out/$filename'); 16 | output.writeStream(file.getContent()!); 17 | await file.close(); 18 | } else { 19 | await Directory('out/$filename').create(recursive: true); 20 | } 21 | } 22 | 23 | // Encode the archive as a BZip2 compressed Tar file. 24 | final tarData = TarEncoder().encodeBytes(archive); 25 | final tarBz2 = BZip2Encoder().encodeBytes(tarData); 26 | 27 | // Write the compressed tar file to disk. 28 | final fp = File('test.tbz'); 29 | fp.writeAsBytesSync(tarBz2); 30 | 31 | // Zip a directory to out.zip using the zipDirectory convenience method 32 | var encoder = ZipFileEncoder(); 33 | await encoder.zipDirectory(Directory('out'), filename: 'out.zip'); 34 | 35 | // Manually create a zip of a directory and individual files. 36 | encoder.create('out2.zip'); 37 | await encoder.addDirectory(Directory('out')); 38 | await encoder.addFile(File('test.zip')); 39 | encoder.closeSync(); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/codecs/gzip_decoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/input_stream.dart'; 4 | import '../util/output_stream.dart'; 5 | import 'zlib/_gzip_decoder.dart'; 6 | 7 | /// Decompress data with the gzip format decoder. 8 | /// The actual decoder used will depend on the platform the code is run on. 9 | /// In a 'dart:io' based platform, like Flutter, the native GZipCodec will 10 | /// be used to improve performance. On web platforms, a Dart implementation 11 | /// of ZLib will be used, via the [Inflate] class. 12 | /// If you want to force the use of the Dart implementation, you can use the 13 | /// [GZipDecoderWeb] class. 14 | class GZipDecoder { 15 | const GZipDecoder(); 16 | 17 | /// Decompress the given [bytes] with the GZip format. 18 | /// [verify] can be used to validate the checksum of the decompressed data, 19 | /// though it is not guaranteed this will be used. 20 | Uint8List decodeBytes(List bytes, {bool verify = false}) => 21 | platformGZipDecoder.decodeBytes(bytes, verify: verify); 22 | 23 | /// Decompress the given [input] with the GZip format, writing the 24 | /// decompressed data to the [output] stream. 25 | /// [verify] can be used to validate the checksum of the decompressed data, 26 | /// though it is not guaranteed this will be used. 27 | bool decodeStream(InputStream input, OutputStream output, 28 | {bool verify = false}) => 29 | platformGZipDecoder.decodeStream(input, output, verify: verify); 30 | } 31 | -------------------------------------------------------------------------------- /test/test_all.dart: -------------------------------------------------------------------------------- 1 | library test.archive; 2 | 3 | import 'adler32_test.dart' as adler32_test; 4 | import 'archive_test.dart' as archive_test; 5 | import 'bzip2_test.dart' as bzip2_test; 6 | import 'commands_test.dart' as commands_test; 7 | import 'crc32_test.dart' as crc32_test; 8 | import 'deflate_test.dart' as deflate_test; 9 | import 'gzip_test.dart' as gzip_test; 10 | import 'inflate_test.dart' as inflate_test; 11 | import 'input_file_stream_test.dart' as input_file_stream_test; 12 | import 'input_memory_stream_test.dart' as input_memory_stream_test; 13 | import 'io_test.dart' as io_test; 14 | import 'output_file_stream_test.dart' as output_file_stream_test; 15 | import 'output_memory_stream_test.dart' as output_memory_stream_test; 16 | import 'ram_file_data_test.dart' as ram_file_data_test; 17 | import 'tar_test.dart' as tar_test; 18 | import 'xz_test.dart' as xz_test; 19 | import 'zip_test.dart' as zip_test; 20 | import 'zlib_test.dart' as zlib_test; 21 | 22 | void main() { 23 | adler32_test.main(); 24 | archive_test.main(); 25 | bzip2_test.main(); 26 | commands_test.main(); 27 | crc32_test.main(); 28 | deflate_test.main(); 29 | gzip_test.main(); 30 | inflate_test.main(); 31 | input_file_stream_test.main(); 32 | input_memory_stream_test.main(); 33 | io_test.main(); 34 | ram_file_data_test.main(); 35 | output_file_stream_test.main(); 36 | output_memory_stream_test.main(); 37 | tar_test.main(); 38 | zip_test.main(); 39 | zlib_test.main(); 40 | xz_test.main(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/util/abstract_file_handle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'file_access.dart'; 4 | 5 | /// Base class for [FileHandle] and [RamFileHandle]. 6 | /// Provides an interface for random access to the data of a file. 7 | abstract class AbstractFileHandle { 8 | /// The current read/write position pf the file. 9 | int get position; 10 | 11 | /// Set the current read/write position of the file. 12 | set position(int p); 13 | 14 | /// The size of the file in bytes. 15 | int get length; 16 | 17 | /// True if the file is currently open. 18 | bool get isOpen; 19 | 20 | /// Open the file with the given [mode], for either read or write. 21 | bool open({FileAccess mode = FileAccess.read}) => false; 22 | 23 | /// Close the file asynchronously. 24 | Future close(); 25 | 26 | /// Close the file synchronously. 27 | void closeSync(); 28 | 29 | /// Read from the file into the given [buffer]. 30 | /// If [end] is omitted, it defaults to [buffer].length. 31 | int readInto(Uint8List buffer, [int? length]); 32 | 33 | /// Synchronously writes from a [buffer] to the file. 34 | /// Will read the buffer from index [start] to index [end]. 35 | /// The [start] must be non-negative and no greater than [buffer].length. 36 | /// If [end] is omitted, it defaults to [buffer].length. 37 | /// Otherwise [end] must be no less than [start] 38 | /// and no greater than [buffer].length. 39 | void writeFromSync(List buffer, [int start = 0, int? end]); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/util/_file_handle_html.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'abstract_file_handle.dart'; 3 | import 'file_access.dart'; 4 | 5 | class FileHandle extends AbstractFileHandle { 6 | FileHandle(String path, {FileAccess mode = FileAccess.read}); 7 | 8 | /// The current read/write position pf the file. 9 | @override 10 | int get position => 0; 11 | 12 | /// Set the current read/write position of the file. 13 | @override 14 | set position(int p) {} 15 | 16 | /// The size of the file in bytes. 17 | @override 18 | int get length => 0; 19 | 20 | /// True if the file is currently open. 21 | @override 22 | bool get isOpen => false; 23 | 24 | /// Open the file with the given [mode], for either read or write. 25 | @override 26 | bool open({FileAccess mode = FileAccess.read}) => false; 27 | 28 | /// Close the file asynchronously. 29 | @override 30 | Future close() async {} 31 | 32 | /// Close the file synchronously. 33 | @override 34 | void closeSync() {} 35 | 36 | /// Read from the file into the given [buffer]. 37 | /// If [end] is omitted, it defaults to [buffer].length. 38 | @override 39 | int readInto(Uint8List buffer, [int? length]) => 0; 40 | 41 | /// Synchronously writes from a [buffer] to the file. 42 | /// Will read the buffer from index [start] to index [end]. 43 | /// The [start] must be non-negative and no greater than [buffer].length. 44 | /// If [end] is omitted, it defaults to [buffer].length. 45 | /// Otherwise [end] must be no less than [start] 46 | /// and no greater than [buffer].length. 47 | @override 48 | void writeFromSync(List buffer, [int start = 0, int? end]) {} 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib_encoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/input_stream.dart'; 4 | import '../util/output_stream.dart'; 5 | import 'zlib/_zlib_encoder.dart'; 6 | 7 | /// Compress data with the zlib format encoder. 8 | /// The actual encoder used will depend on the platform the code is run on. 9 | /// In a 'dart:io' based platform, like Flutter, the native ZLibCodec will 10 | /// be used to improve performance. On web platforms, a Dart implementation 11 | /// of ZLib will be used, via the [Deflate] class. 12 | /// If you want to force the use of the Dart implementation, you can use the 13 | /// [ZLibEncoderWeb] class. 14 | class ZLibEncoder { 15 | const ZLibEncoder(); 16 | 17 | static const maxWindowBits = 15; 18 | 19 | /// Compress the given [bytes] with the ZLib format. 20 | /// [level] will set the compression level to use, between 0 and 9, 6 is the 21 | /// default. 22 | Uint8List encodeBytes(List bytes, 23 | {int? level, int windowBits = maxWindowBits}) => 24 | platformZLibEncoder.encodeBytes(bytes, level: level); 25 | 26 | /// Alias for [encodeBytes], kept for backwards compatibility. 27 | List encode(List bytes, 28 | {int? level, int windowBits = maxWindowBits}) => 29 | encodeBytes(bytes, level: level, windowBits: windowBits); 30 | 31 | /// Compress the given [input] stream with the ZLib format. 32 | /// [level] will set the compression level to use, between 0 and 9, 6 is the 33 | /// default. 34 | void encodeStream(InputStream input, OutputStream output, 35 | {int? level, int windowBits = maxWindowBits}) => 36 | platformZLibEncoder.encodeStream(input, output, 37 | level: level, windowBits: windowBits); 38 | } 39 | -------------------------------------------------------------------------------- /test/web_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:archive/archive.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import '_test_util.dart'; 7 | 8 | void main() { 9 | group('zlib web', () { 10 | test('encode/decode', () { 11 | final origData = [1, 2, 3, 4, 5, 6]; 12 | final compressed = ZLibEncoder().encodeBytes(origData); 13 | final uncompressed = ZLibDecoder().decodeBytes(compressed); 14 | compareBytes(uncompressed, origData); 15 | }); 16 | }); 17 | 18 | group('gzip web', () { 19 | final buffer = Uint8List(10000); 20 | for (var i = 0; i < buffer.length; ++i) { 21 | buffer[i] = i % 256; 22 | } 23 | 24 | test('encode/decode', () { 25 | final origData = [1, 2, 3, 4, 5, 6]; 26 | final compressed = GZipEncoder().encodeBytes(origData); 27 | final uncompressed = GZipDecoder().decodeBytes(compressed); 28 | compareBytes(uncompressed, origData); 29 | }); 30 | 31 | test('multiblock', () async { 32 | final compressedData = [ 33 | ...GZipEncoder().encodeBytes([1, 2, 3]), 34 | ...GZipEncoder().encodeBytes([4, 5, 6]) 35 | ]; 36 | final decodedData = 37 | GZipDecoderWeb().decodeBytes(compressedData, verify: true); 38 | compareBytes(decodedData, [1, 2, 3, 4, 5, 6]); 39 | }); 40 | 41 | test('encode/decode', () { 42 | final compressed = GZipEncoder().encodeBytes(buffer); 43 | final decompressed = GZipDecoder().decodeBytes(compressed, verify: true); 44 | expect(decompressed.length, equals(buffer.length)); 45 | for (var i = 0; i < buffer.length; ++i) { 46 | expect(decompressed[i], equals(buffer[i])); 47 | } 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib_decoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/input_stream.dart'; 4 | import '../util/output_stream.dart'; 5 | import 'zlib/_zlib_decoder.dart'; 6 | 7 | /// Decompress data with the zlib format decoder. 8 | /// The actual decoder used will depend on the platform the code is run on. 9 | /// In a 'dart:io' based platform, like Flutter, the native ZLibCodec will 10 | /// be used to improve performance. On web platforms, a Dart implementation 11 | /// of ZLib will be used, via the [Inflate] class. 12 | /// If you want to force the use of the Dart implementation, you can use the 13 | /// [ZLibDecoderWeb] class. 14 | class ZLibDecoder { 15 | const ZLibDecoder(); 16 | 17 | /// Decompress the given [bytes] with the ZLib format. 18 | /// [verify] can be used to validate the checksum of the decompressed data, 19 | /// though it is not guaranteed this will be used. 20 | /// If [raw] is true, the input will be considered deflate compressed data 21 | /// without a zlib header. 22 | Uint8List decodeBytes(List bytes, 23 | {bool verify = false, bool raw = false}) => 24 | platformZLibDecoder.decodeBytes(bytes, verify: verify, raw: raw); 25 | 26 | /// Decompress the given [input] with the ZLib format, writing the 27 | /// decompressed data to the [output] stream. 28 | /// [verify] can be used to validate the checksum of the decompressed data, 29 | /// though it is not guaranteed this will be used. 30 | /// If [raw] is true, the input will be considered deflate compressed data 31 | /// without a zlib header. 32 | bool decodeStream(InputStream input, OutputStream output, 33 | {bool verify = false, bool raw = false}) => 34 | platformZLibDecoder.decodeStream(input, output, verify: verify, raw: raw); 35 | } 36 | -------------------------------------------------------------------------------- /test/output_memory_stream_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:archive/archive.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('OutputStreamMemory', () { 6 | test('empty', () async { 7 | final out = OutputMemoryStream(); 8 | final bytes = out.getBytes(); 9 | expect(bytes.length, equals(0)); 10 | }); 11 | 12 | test('writeByte', () async { 13 | final out = OutputMemoryStream(); 14 | for (var i = 0; i < 10000; ++i) { 15 | out.writeByte(i % 256); 16 | } 17 | final bytes = out.getBytes(); 18 | expect(bytes.length, equals(10000)); 19 | for (var i = 0; i < 10000; ++i) { 20 | expect(bytes[i], equals(i % 256)); 21 | } 22 | }); 23 | 24 | test('writeUint16', () async { 25 | final out = OutputMemoryStream(); 26 | 27 | const len = 0xffff; 28 | for (var i = 0; i < len; ++i) { 29 | out.writeUint16(i); 30 | } 31 | 32 | final bytes = out.getBytes(); 33 | expect(bytes.length, equals(len * 2)); 34 | 35 | final input = InputMemoryStream(bytes); 36 | for (var i = 0; i < len; ++i) { 37 | final x = input.readUint16(); 38 | expect(x, equals(i)); 39 | } 40 | }); 41 | 42 | test('writeUint32', () async { 43 | final out = OutputMemoryStream(); 44 | 45 | const len = 0xffff; 46 | for (var i = 0; i < len; ++i) { 47 | out.writeUint32(0xffff + i); 48 | } 49 | 50 | final bytes = out.getBytes(); 51 | expect(bytes.length, equals(len * 4)); 52 | 53 | final input = InputMemoryStream(bytes); 54 | for (var i = 0; i < len; ++i) { 55 | final x = input.readUint32(); 56 | expect(x, equals(0xffff + i)); 57 | } 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /test/zlib_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:archive/archive_io.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import '_test_util.dart'; 7 | 8 | void main() async { 9 | final buffer = Uint8List(10000); 10 | for (var i = 0; i < buffer.length; ++i) { 11 | buffer[i] = i % 256; 12 | } 13 | 14 | group('ZLib', () { 15 | test('multiblock', () async { 16 | final compressedData = [ 17 | ...ZLibEncoder().encodeBytes([1, 2, 3]), 18 | ...ZLibEncoder().encodeBytes([4, 5, 6]) 19 | ]; 20 | final decodedData = 21 | ZLibDecoderWeb().decodeBytes(compressedData, verify: true); 22 | compareBytes(decodedData, [1, 2, 3, 4, 5, 6]); 23 | }); 24 | 25 | test('encode/decode', () async { 26 | final compressed = const ZLibEncoder().encodeBytes(buffer); 27 | final decompressed = 28 | const ZLibDecoder().decodeBytes(compressed, verify: true); 29 | expect(decompressed.length, equals(buffer.length)); 30 | for (var i = 0; i < buffer.length; ++i) { 31 | expect(decompressed[i], equals(buffer[i])); 32 | } 33 | }); 34 | 35 | test('encodeStream', () async { 36 | { 37 | final outStream = OutputFileStream('$testOutputPath/zlib_stream.zlib') 38 | ..open(); 39 | final inStream = InputMemoryStream(buffer); 40 | const ZLibEncoder().encodeStream(inStream, outStream); 41 | } 42 | 43 | { 44 | final inStream = InputFileStream('$testOutputPath/zlib_stream.zlib') 45 | ..open(); 46 | final outStream = OutputMemoryStream(); 47 | ZLibDecoder().decodeStream(inStream, outStream); 48 | final decoded = outStream.getBytes(); 49 | 50 | expect(decoded.length, equals(buffer.length)); 51 | for (var i = 0; i < buffer.length; ++i) { 52 | expect(decoded[i], equals(buffer[i])); 53 | } 54 | } 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /test/_data/tar/ustar.tar: -------------------------------------------------------------------------------- 1 | file.txt0000644000076500000240000000000612104402656045134 0ustar00shanestaff00000000000000longname/longname/longname/longname/longname/longname/longname/longname/longname/longname/longname/longname/longname/longname/longnamehello 2 | -------------------------------------------------------------------------------- /lib/src/codecs/bzip2/bz2_bit_writer.dart: -------------------------------------------------------------------------------- 1 | import '../../util/output_stream.dart'; 2 | 3 | /// Internal class used by [BZip2Encoder] 4 | class Bz2BitWriter { 5 | OutputStream output; 6 | 7 | Bz2BitWriter(this.output); 8 | 9 | void writeByte(int byte) => writeBits(8, byte); 10 | 11 | void writeBytes(List bytes) { 12 | for (var i = 0; i < bytes.length; ++i) { 13 | writeBits(8, bytes[i]); 14 | } 15 | } 16 | 17 | void writeUint16(int value) { 18 | writeBits(16, value); 19 | } 20 | 21 | void writeUint24(int value) { 22 | writeBits(24, value); 23 | } 24 | 25 | void writeUint32(int value) { 26 | writeBits(32, value); 27 | } 28 | 29 | void writeBits(int numBits, int value) { 30 | // This could be optimized 31 | if (_bitPos == 8 && numBits == 8) { 32 | output.writeByte(value & 0xff); 33 | return; 34 | } 35 | 36 | if (_bitPos == 8 && numBits == 16) { 37 | output.writeByte((value >> 8) & 0xff); 38 | output.writeByte(value & 0xff); 39 | return; 40 | } 41 | 42 | if (_bitPos == 8 && numBits == 24) { 43 | output.writeByte((value >> 16) & 0xff); 44 | output.writeByte((value >> 8) & 0xff); 45 | output.writeByte(value & 0xff); 46 | return; 47 | } 48 | 49 | if (_bitPos == 8 && numBits == 32) { 50 | output.writeByte((value >> 24) & 0xff); 51 | output.writeByte((value >> 16) & 0xff); 52 | output.writeByte((value >> 8) & 0xff); 53 | output.writeByte(value & 0xff); 54 | return; 55 | } 56 | 57 | while (numBits > 0) { 58 | numBits--; 59 | final b = (value >> numBits) & 0x1; 60 | _bitBuffer = (_bitBuffer << 1) | b; 61 | _bitPos--; 62 | if (_bitPos == 0) { 63 | output.writeByte(_bitBuffer); 64 | _bitPos = 8; 65 | _bitBuffer = 0; 66 | } 67 | } 68 | } 69 | 70 | /// Write any remaining bits from the buffer to the output, padding the 71 | /// remainder of the byte with 0's. 72 | void flush() { 73 | if (_bitPos != 8) { 74 | writeBits(_bitPos, 0); 75 | } 76 | } 77 | 78 | int _bitBuffer = 0; 79 | int _bitPos = 8; 80 | } 81 | -------------------------------------------------------------------------------- /test/commands_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:archive/archive_io.dart'; 4 | import 'package:path/path.dart' as p; 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | test('bin/tar.dart list test2.tar.gz', () { 9 | // Test that 'tar --list' does not throw. 10 | listTarFiles('test/_data/test2.tar.gz'); 11 | }); 12 | 13 | test('bin/tar.dart list test2.tar.gz2', () { 14 | // Test that 'tar --list' does not throw. 15 | listTarFiles('test/_data/test2.tar.bz2'); 16 | }); 17 | 18 | test('tar extract', () { 19 | final dir = Directory.systemTemp.createTempSync('foo'); 20 | 21 | try { 22 | //print(dir.path); 23 | 24 | final inputPath = p.join('test/_data/test2.tar.gz'); 25 | 26 | { 27 | final tempDir = Directory.systemTemp.createTempSync('dart_archive'); 28 | final tarPath = '${tempDir.path}${Platform.pathSeparator}temp.tar'; 29 | final input = InputFileStream(inputPath); 30 | final output = OutputFileStream(tarPath); 31 | GZipDecoder().decodeStream(input, output); 32 | 33 | final aBytes = File(tarPath).readAsBytesSync(); 34 | final bBytes = File(p.join('test/_data/test2.tar')).readAsBytesSync(); 35 | 36 | expect(aBytes.length, equals(bBytes.length)); 37 | var same = true; 38 | for (var i = 0; same && i < aBytes.length; ++i) { 39 | same = aBytes[i] == bBytes[i]; 40 | } 41 | expect(same, equals(true)); 42 | 43 | input.closeSync(); 44 | output.closeSync(); 45 | 46 | tempDir.deleteSync(recursive: true); 47 | } 48 | 49 | extractTarFiles('test/_data/test2.tar.gz', dir.path); 50 | expect(dir.listSync(recursive: true).length, 4); 51 | } finally { 52 | dir.deleteSync(recursive: true); 53 | } 54 | }); 55 | 56 | /*test('tar create', () { 57 | final dir = Directory.systemTemp.createTempSync('foo'); 58 | final file = File('${dir.path}${Platform.pathSeparator}foo.txt'); 59 | file.writeAsStringSync('foo bar'); 60 | 61 | try { 62 | // Test that 'tar --create' does not throw. 63 | tar_command.createTarFile(dir.path); 64 | } finally { 65 | dir.delete(recursive: true); 66 | } 67 | });*/ 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/util/aes.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'encryption.dart'; 3 | 4 | class Uint8ListEquality { 5 | static bool equals(Uint8List mac, Uint8List computedMac) { 6 | if (mac.length != computedMac.length) { 7 | return false; 8 | } 9 | var v = 0; 10 | for (var i = 0; i < mac.length; i++) { 11 | v |= mac[i] ^ computedMac[i]; 12 | } 13 | return v == 0; 14 | } 15 | } 16 | 17 | class AesCipherUtil { 18 | static HMac getMacBasedPRF(Uint8List derivedKey) { 19 | var mac = HMac(SHA1Digest(), 64); 20 | mac.init(KeyParameter(derivedKey)); 21 | return mac; 22 | } 23 | 24 | static void prepareBuffAESIVBytes(Uint8List buff, int nonce) { 25 | buff[0] = nonce & 0xFF; 26 | buff[1] = (nonce >> 8) & 0xFF; 27 | buff[2] = (nonce >> 16) & 0xFF; 28 | buff[3] = (nonce >> 24) & 0xFF; 29 | 30 | for (int i = 4; i <= 15; ++i) { 31 | buff[i] = 0; 32 | } 33 | } 34 | } 35 | 36 | // AesDecrypt 37 | class Aes { 38 | int nonce = 1; 39 | Uint8List iv = Uint8List(16); 40 | Uint8List counterBlock = Uint8List(16); 41 | Uint8List derivedKey; 42 | int aesKeyStrength; 43 | bool encrypt; 44 | AESEngine? aesEngine; 45 | late HMac _macGen; 46 | late Uint8List mac; 47 | 48 | int processData(Uint8List buff, int start, int len) { 49 | if (!encrypt) { 50 | _macGen.update(buff, 0, len); 51 | } 52 | 53 | for (int j = start; j < start + len; j += 16) { 54 | int loopCount = j + 16 <= start + len ? 16 : start + len - j; 55 | AesCipherUtil.prepareBuffAESIVBytes(iv, nonce); 56 | aesEngine?.processBlock(iv, 0, counterBlock, 0); 57 | for (int k = 0; k < loopCount; ++k) { 58 | buff[j + k] ^= counterBlock[k]; 59 | } 60 | ++nonce; 61 | } 62 | 63 | if (encrypt) { 64 | _macGen.update(buff, 0, len); 65 | } 66 | 67 | mac = Uint8List(_macGen.macSize); 68 | _macGen.doFinal(mac, 0); 69 | mac = mac.sublist(0, 10); 70 | _macGen.reset(); 71 | 72 | return len; 73 | } 74 | 75 | Aes(this.derivedKey, Uint8List hmacDerivedKey, this.aesKeyStrength, 76 | {this.encrypt = false}) { 77 | aesEngine = AESEngine(); 78 | aesEngine!.init(true, KeyParameter(derivedKey)); 79 | _macGen = AesCipherUtil.getMacBasedPRF(hmacDerivedKey); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/archive.dart: -------------------------------------------------------------------------------- 1 | library archive; 2 | 3 | export 'src/archive/archive.dart'; 4 | export 'src/archive/archive_file.dart'; 5 | export 'src/archive/compression_type.dart'; 6 | export 'src/archive/encryption_type.dart'; 7 | export 'src/codecs/bzip2/bz2_bit_reader.dart'; 8 | export 'src/codecs/bzip2/bz2_bit_writer.dart'; 9 | export 'src/codecs/bzip2/bzip2.dart'; 10 | export 'src/codecs/bzip2_decoder.dart'; 11 | export 'src/codecs/bzip2_encoder.dart'; 12 | export 'src/codecs/gzip_decoder.dart'; 13 | export 'src/codecs/gzip_encoder.dart'; 14 | export 'src/codecs/lzma/lzma_decoder.dart'; 15 | export 'src/codecs/lzma/range_decoder.dart'; 16 | export 'src/codecs/tar/tar_file.dart'; 17 | export 'src/codecs/tar_decoder.dart'; 18 | export 'src/codecs/tar_encoder.dart'; 19 | export 'src/codecs/xz_decoder.dart'; 20 | export 'src/codecs/xz_encoder.dart'; 21 | export 'src/codecs/zip/zip_directory.dart'; 22 | export 'src/codecs/zip/zip_file.dart'; 23 | export 'src/codecs/zip/zip_file_header.dart'; 24 | export 'src/codecs/zip_decoder.dart'; 25 | export 'src/codecs/zip_encoder.dart'; 26 | export 'src/codecs/zlib/_zlib_decoder.dart'; 27 | export 'src/codecs/zlib/deflate.dart'; 28 | export 'src/codecs/zlib/gzip_decoder_web.dart'; 29 | export 'src/codecs/zlib/gzip_encoder_web.dart'; 30 | export 'src/codecs/zlib/inflate.dart'; 31 | export 'src/codecs/zlib/inflate_buffer.dart'; 32 | export 'src/codecs/zlib/zlib_decoder_web.dart'; 33 | export 'src/codecs/zlib/zlib_encoder_web.dart'; 34 | export 'src/codecs/zlib_decoder.dart'; 35 | export 'src/codecs/zlib_encoder.dart'; 36 | export 'src/util/abstract_file_handle.dart'; 37 | export 'src/util/adler32.dart'; 38 | export 'src/util/aes_decrypt.dart'; 39 | export 'src/util/archive_exception.dart'; 40 | export 'src/util/byte_order.dart'; 41 | export 'src/util/crc32.dart'; 42 | export 'src/util/crc64.dart'; 43 | export 'src/util/encryption.dart'; 44 | export 'src/util/file_access.dart'; 45 | export 'src/util/file_buffer.dart'; 46 | export 'src/util/file_content.dart'; 47 | export 'src/util/file_handle.dart'; 48 | export 'src/util/input_file_stream.dart'; 49 | export 'src/util/input_memory_stream.dart'; 50 | export 'src/util/input_stream.dart'; 51 | export 'src/util/output_file_stream.dart'; 52 | export 'src/util/output_memory_stream.dart'; 53 | export 'src/util/output_stream.dart'; 54 | export 'src/util/ram_file_handle.dart'; 55 | -------------------------------------------------------------------------------- /test/output_file_stream_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:archive/archive.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import '_test_util.dart'; 7 | 8 | void main() { 9 | group('OutputFileStream', () { 10 | test('InputFileStream/OutputFileStream', () async { 11 | final input = InputFileStream('test/_data/folder.zip')..open(); 12 | final output = OutputFileStream('$testOutputPath/folder.zip')..open(); 13 | 14 | while (!input.isEOS) { 15 | final bytes = input.readBytes(50); 16 | output.writeStream(bytes); 17 | } 18 | 19 | await input.close(); 20 | await output.close(); 21 | 22 | final aBytes = File('test/_data/folder.zip').readAsBytesSync(); 23 | final bBytes = File('$testOutputPath/folder.zip').readAsBytesSync(); 24 | 25 | expect(aBytes.length, equals(bBytes.length)); 26 | for (var i = 0; i < aBytes.length; ++i) { 27 | expect(aBytes[i], equals(bBytes[i])); 28 | } 29 | }); 30 | 31 | test('InputMemoryStream/OutputFileStream', () async { 32 | final bytes = List.generate(256, (index) => index); 33 | final input = InputMemoryStream.fromList(bytes)..open(); 34 | final output = OutputFileStream('$testOutputPath/test.bin')..open(); 35 | 36 | while (!input.isEOS) { 37 | final bytes = input.readBytes(50); 38 | output.writeStream(bytes); 39 | } 40 | 41 | await input.close(); 42 | await output.close(); 43 | 44 | final aBytes = File('$testOutputPath/test.bin').readAsBytesSync(); 45 | 46 | expect(aBytes.length, equals(bytes.length)); 47 | for (var i = 0; i < aBytes.length; ++i) { 48 | expect(aBytes[i], equals(bytes[i])); 49 | } 50 | }); 51 | 52 | test('InputFileStream/OutputMemoryStream', () async { 53 | final input = InputFileStream('test/_data/folder.zip')..open(); 54 | final output = OutputMemoryStream()..open(); 55 | 56 | while (!input.isEOS) { 57 | final bytes = input.readBytes(50); 58 | output.writeStream(bytes); 59 | } 60 | 61 | await input.close(); 62 | 63 | final aBytes = File('test/_data/folder.zip').readAsBytesSync(); 64 | final bBytes = output.getBytes(); 65 | 66 | expect(aBytes.length, equals(bBytes.length)); 67 | for (var i = 0; i < aBytes.length; ++i) { 68 | expect(aBytes[i], equals(bBytes[i])); 69 | } 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_encoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/adler32.dart'; 4 | import '../../util/byte_order.dart'; 5 | import '../../util/input_memory_stream.dart'; 6 | import '../../util/input_stream.dart'; 7 | import '../../util/output_memory_stream.dart'; 8 | import '../../util/output_stream.dart'; 9 | import '_zlib_encoder_base.dart'; 10 | import 'deflate.dart'; 11 | 12 | const platformZLibEncoder = _ZLibEncoder(); 13 | 14 | class _ZLibEncoder extends ZLibEncoderBase { 15 | static const _deflate = 8; 16 | 17 | const _ZLibEncoder(); 18 | 19 | Uint8List encodeBytes(List bytes, 20 | {int? level, int? windowBits, bool raw = false}) { 21 | final output = OutputMemoryStream(byteOrder: ByteOrder.bigEndian); 22 | encodeStream(InputMemoryStream(bytes), output, 23 | level: level, windowBits: windowBits, raw: raw); 24 | return output.getBytes(); 25 | } 26 | 27 | void encodeStream(InputStream input, OutputStream output, 28 | {int? level, int? windowBits, bool raw = false}) { 29 | output.byteOrder = ByteOrder.bigEndian; 30 | 31 | if (raw) { 32 | Deflate.stream(input, 33 | level: level ?? 6, windowBits: windowBits ?? 15, output: output); 34 | return; 35 | } 36 | 37 | final wb = (windowBits ?? 15).clamp(0, 15); 38 | 39 | // Compression Method and Flags 40 | const cm = _deflate; 41 | final cinfo = wb - 8; //2^(7+8) = 32768 window size 42 | 43 | final cmf = (cinfo << 4) | cm; 44 | output.writeByte(cmf); 45 | 46 | // 0x01, (00 0 00001) (FLG) 47 | // bits 0 to 4 FCHECK (check bits for CMF and FLG) 48 | // bit 5 FDICT (preset dictionary) 49 | // bits 6 to 7 FLEVEL (compression level) 50 | // FCHECK is set such that (cmf * 256 + flag) must be a multiple of 31. 51 | const fdict = 0; 52 | const flevel = 0; 53 | var flag = ((flevel & 0x3) << 7) | ((fdict & 0x1) << 5); 54 | var fcheck = 0; 55 | final cmf256 = cmf * 256; 56 | while ((cmf256 + (flag | fcheck)) % 31 != 0) { 57 | fcheck++; 58 | } 59 | flag |= fcheck; 60 | output.writeByte(flag); 61 | 62 | final startPos = input.position; 63 | final adler32 = getAdler32Stream(input); 64 | 65 | input.setPosition(startPos); 66 | 67 | Deflate.stream(input, 68 | level: level ?? 6, windowBits: windowBits ?? 15, output: output); 69 | 70 | output 71 | ..writeUint32(adler32) 72 | ..flush(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/archive_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:archive/archive.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('archive', () { 8 | test('replace existing file', () { 9 | final archive = Archive(); 10 | archive.addFile(ArchiveFile.bytes("a", Uint8List.fromList([0]))); 11 | archive.addFile(ArchiveFile.bytes("b", Uint8List.fromList([1]))); 12 | archive.addFile(ArchiveFile.bytes("c", Uint8List.fromList([2]))); 13 | 14 | archive.addFile(ArchiveFile.bytes("b", Uint8List.fromList([3]))); 15 | 16 | archive.addFile( 17 | ArchiveFile.bytes("陳大文_1_test.png", Uint8List.fromList([4]))); 18 | 19 | expect(archive.length, 4); 20 | expect(archive[0].name, "a"); 21 | expect(archive[1].name, "b"); 22 | expect(archive[2].name, "c"); 23 | expect(archive[3].name, "陳大文_1_test.png"); 24 | 25 | expect(archive[0].getContent()!.readByte(), 0); 26 | expect(archive[1].getContent()!.readByte(), 3); 27 | expect(archive[2].getContent()!.readByte(), 2); 28 | expect(archive[3].getContent()!.readByte(), 4); 29 | }); 30 | 31 | test('clear', () { 32 | final archive = Archive(); 33 | archive.addFile(ArchiveFile.bytes("a", Uint8List.fromList([0]))); 34 | archive.addFile(ArchiveFile.bytes("b", Uint8List.fromList([1]))); 35 | archive.addFile(ArchiveFile.bytes("c", Uint8List.fromList([2]))); 36 | archive.clearSync(); 37 | expect(archive.length, 0); 38 | }); 39 | 40 | test('remove file', () { 41 | final archive = Archive(); 42 | final file1 = ArchiveFile.bytes("a", Uint8List.fromList([0])); 43 | final file2 = ArchiveFile.bytes("b", Uint8List.fromList([1])); 44 | archive.addFile(file1); 45 | archive.addFile(file2); 46 | expect(archive.length, 2); 47 | archive.removeFile(file1); 48 | expect(archive.length, 1); 49 | expect(archive[0].name, "b"); 50 | expect(archive[0].getContent()!.readByte(), 1); 51 | archive.removeFile(file2); 52 | expect(archive.length, 0); 53 | }); 54 | 55 | test('remove file at index', () { 56 | final archive = Archive(); 57 | archive.addFile(ArchiveFile.bytes("a", Uint8List.fromList([0]))); 58 | archive.addFile(ArchiveFile.bytes("b", Uint8List.fromList([1]))); 59 | archive.removeAt(0); 60 | expect(archive.length, 1); 61 | expect(archive[0].name, "b"); 62 | expect(archive[0].getContent()!.readByte(), 1); 63 | archive.removeAt(0); 64 | expect(archive.length, 0); 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/util/file_content.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'input_memory_stream.dart'; 4 | import 'input_stream.dart'; 5 | import 'output_stream.dart'; 6 | 7 | /// Used by [ArchiveFile] to abstract the content of a file within an archive 8 | /// file, either in memory, or a position within a file on disk. 9 | abstract class FileContent { 10 | /// The size of the file content in bytes. 11 | int get length; 12 | 13 | /// Get the InputStream for reading the file content. 14 | InputStream getStream({bool decompress = true}); 15 | 16 | /// Write the contents of the file to the given [output]. 17 | void write(OutputStream output); 18 | 19 | /// Close the file content asynchronously. 20 | Future close(); 21 | 22 | /// Close the file content synchronously. 23 | void closeSync(); 24 | 25 | /// Read the file content into memory and return the read bytes. 26 | Uint8List readBytes() { 27 | final stream = getStream(); 28 | return stream.toUint8List(); 29 | } 30 | 31 | /// Decompress the file content and write out the decompressed bytes to 32 | /// the [output] stream. 33 | void decompress(OutputStream output) { 34 | output.writeStream(getStream()); 35 | } 36 | 37 | /// True if the file content is compressed. 38 | bool get isCompressed => false; 39 | } 40 | 41 | /// A [FileContent] that is resident in memory. 42 | class FileContentMemory extends FileContent { 43 | Uint8List? bytes; 44 | 45 | FileContentMemory(List data) 46 | : bytes = data is Uint8List ? data : Uint8List.fromList(data); 47 | 48 | @override 49 | int get length => bytes?.length ?? 0; 50 | 51 | @override 52 | InputStream getStream({bool decompress = true}) => 53 | InputMemoryStream(bytes ?? Uint8List(0)); 54 | 55 | @override 56 | void write(OutputStream output) { 57 | if (bytes != null) { 58 | output.writeBytes(bytes!); 59 | } 60 | } 61 | 62 | @override 63 | Future close() async { 64 | bytes = null; 65 | } 66 | 67 | @override 68 | void closeSync() { 69 | bytes = null; 70 | } 71 | } 72 | 73 | /// A [FileContent] that is stored in a disk file. 74 | class FileContentStream extends FileContent { 75 | final InputStream stream; 76 | 77 | FileContentStream(this.stream); 78 | 79 | @override 80 | int get length => stream.length; 81 | 82 | @override 83 | InputStream getStream({bool decompress = true}) => stream; 84 | 85 | @override 86 | void write(OutputStream output) => output.writeStream(stream); 87 | 88 | @override 89 | Future close() async => stream.close(); 90 | 91 | @override 92 | void closeSync() => stream.closeSync(); 93 | } 94 | -------------------------------------------------------------------------------- /lib/src/util/_file_handle_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'abstract_file_handle.dart'; 5 | import 'file_access.dart'; 6 | 7 | class FileHandle extends AbstractFileHandle { 8 | final String _path; 9 | RandomAccessFile? _file; 10 | int _position; 11 | int _length; 12 | 13 | FileHandle(this._path, {FileAccess mode = FileAccess.read}) 14 | : _position = 0, 15 | _length = 0, 16 | super() { 17 | if (mode != FileAccess.closed) { 18 | open(mode: mode); 19 | } 20 | } 21 | 22 | @override 23 | bool open({FileAccess mode = FileAccess.read}) { 24 | if (_file != null) { 25 | return true; 26 | } 27 | if (mode == FileAccess.closed) { 28 | return false; 29 | } 30 | final fileMode = mode == FileAccess.read ? FileMode.read : FileMode.write; 31 | final fp = File(path); 32 | if (mode == FileAccess.write) { 33 | fp.createSync(recursive: true); 34 | } 35 | _file = fp.openSync(mode: fileMode); 36 | _length = _file?.lengthSync() ?? 0; 37 | _position = 0; 38 | return _file != null; 39 | } 40 | 41 | String get path => _path; 42 | 43 | @override 44 | int get position => _position; 45 | 46 | @override 47 | set position(int p) { 48 | if (_file == null || p == _position) { 49 | return; 50 | } 51 | _position = p; 52 | _file!.setPositionSync(p); 53 | } 54 | 55 | @override 56 | int get length => _length; 57 | 58 | @override 59 | bool get isOpen => _file != null; 60 | 61 | @override 62 | Future close() async { 63 | if (_file == null) { 64 | return; 65 | } 66 | final fp = _file; 67 | _file = null; 68 | _position = 0; 69 | await fp!.close(); 70 | } 71 | 72 | @override 73 | void closeSync() { 74 | if (_file == null) { 75 | return; 76 | } 77 | final fp = _file; 78 | _file = null; 79 | _position = 0; 80 | fp!.closeSync(); 81 | } 82 | 83 | @override 84 | int readInto(Uint8List buffer, [int? length]) { 85 | if (_file == null) { 86 | open(); 87 | } 88 | final size = _file!.readIntoSync(buffer, 0, length); 89 | _position += size; 90 | return size; 91 | } 92 | 93 | @override 94 | void writeFromSync(List buffer, [int start = 0, int? end]) { 95 | if (_file == null) { 96 | open(); 97 | } 98 | final int size; 99 | if (end == null) { 100 | size = buffer.length; 101 | } else { 102 | size = end - start; 103 | } 104 | _file!.writeFromSync(buffer, start, end); 105 | _position += size; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/codecs/tar_encoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import '../archive/archive.dart'; 5 | import '../archive/archive_file.dart'; 6 | import '../util/_cast.dart'; 7 | import '../util/output_memory_stream.dart'; 8 | import '../util/output_stream.dart'; 9 | import 'tar/tar_file.dart'; 10 | 11 | /// Encode an [Archive] object into a tar formatted buffer. 12 | class TarEncoder { 13 | final Encoding filenameEncoding; 14 | 15 | TarEncoder({this.filenameEncoding = const Utf8Codec()}); 16 | 17 | void encodeStream(Archive archive, OutputStream output) { 18 | start(output); 19 | for (final file in archive) { 20 | add(file); 21 | } 22 | finish(); 23 | } 24 | 25 | Uint8List encodeBytes(Archive archive, {OutputStream? output}) { 26 | output ??= OutputMemoryStream(); 27 | encodeStream(archive, output); 28 | return output.getBytes(); 29 | } 30 | 31 | /// Alias for [encodeBytes], kept for backwards compatibility. 32 | List encode(Archive archive, {OutputStream? output}) => 33 | encodeBytes(archive, output: output); 34 | 35 | void start([OutputStream? outputStream]) { 36 | _outputStream = outputStream ?? OutputMemoryStream(); 37 | } 38 | 39 | void add(ArchiveFile entry) { 40 | if (_outputStream == null) { 41 | return; 42 | } 43 | 44 | // GNU tar files store extra long file names in a separate file 45 | if (entry.name.length > 100) { 46 | final ts = TarFile(); 47 | ts.filename = '././@LongLink'; 48 | ts.fileSize = entry.name.length; 49 | ts.mode = 0; 50 | ts.ownerId = 0; 51 | ts.groupId = 0; 52 | ts.lastModTime = 0; 53 | ts.contentBytes = castToUint8List(utf8.encode(entry.name)); 54 | ts.write(_outputStream!); 55 | } 56 | 57 | final ts = TarFile(); 58 | ts.filename = entry.name; 59 | ts.mode = entry.mode; 60 | ts.ownerId = entry.ownerId; 61 | ts.groupId = entry.groupId; 62 | ts.lastModTime = entry.lastModTime; 63 | if (!entry.isFile) { 64 | ts.typeFlag = TarFile.directory; 65 | } else { 66 | final file = entry; 67 | if (file.symbolicLink != null) { 68 | ts.typeFlag = TarFile.symbolicLink; 69 | ts.nameOfLinkedFile = file.symbolicLink; 70 | } else { 71 | ts.fileSize = file.size; 72 | ts.contentBytes = file.getContent()?.toUint8List(); 73 | } 74 | } 75 | ts.write(_outputStream!); 76 | } 77 | 78 | void finish() { 79 | if (_outputStream == null) { 80 | return; 81 | } 82 | // At the end of the archive file there are two 512-byte blocks filled 83 | // with binary zeros as an end-of-file marker. 84 | final eof = Uint8List(1024); 85 | _outputStream!.writeBytes(eof); 86 | _outputStream!.flush(); 87 | _outputStream = null; 88 | } 89 | 90 | OutputStream? _outputStream; 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/util/output_stream.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'byte_order.dart'; 5 | import 'input_stream.dart'; 6 | 7 | abstract class OutputStream { 8 | ByteOrder byteOrder; 9 | 10 | int get length; 11 | 12 | OutputStream({required this.byteOrder}); 13 | 14 | void open() {} 15 | 16 | Future close() async {} 17 | 18 | void closeSync() {} 19 | 20 | bool get isOpen => true; 21 | 22 | void clear(); 23 | 24 | /// Write any pending data writes to the output. 25 | void flush(); 26 | 27 | /// Write a byte to the output stream. 28 | void writeByte(int value); 29 | 30 | /// Write a set of bytes to the output stream. 31 | void writeBytes(List bytes, {int? length}); 32 | 33 | /// Write an InputStream to the output stream. 34 | void writeStream(InputStream stream); 35 | 36 | /// Write a 16-bit word to the output stream. 37 | void writeUint16(int value) { 38 | if (byteOrder == ByteOrder.bigEndian) { 39 | writeByte((value >> 8) & 0xff); 40 | writeByte(value & 0xff); 41 | } else { 42 | writeByte(value & 0xff); 43 | writeByte((value >> 8) & 0xff); 44 | } 45 | } 46 | 47 | /// Write a 32-bit word to the end of the buffer. 48 | void writeUint32(int value) { 49 | if (byteOrder == ByteOrder.bigEndian) { 50 | writeByte((value >> 24) & 0xff); 51 | writeByte((value >> 16) & 0xff); 52 | writeByte((value >> 8) & 0xff); 53 | writeByte(value & 0xff); 54 | } else { 55 | writeByte(value & 0xff); 56 | writeByte((value >> 8) & 0xff); 57 | writeByte((value >> 16) & 0xff); 58 | writeByte((value >> 24) & 0xff); 59 | } 60 | } 61 | 62 | /// Write a 64-bit word to the end of the buffer. 63 | void writeUint64(int value) { 64 | // Works around Dart treating 64 bit integers as signed when shifting. 65 | var topBit = 0x00; 66 | if (value & 0x8000000000000000 != 0) { 67 | topBit = 0x80; 68 | value ^= 0x8000000000000000; 69 | } 70 | if (byteOrder == ByteOrder.bigEndian) { 71 | writeByte(topBit | ((value >> 56) & 0xff)); 72 | writeByte((value >> 48) & 0xff); 73 | writeByte((value >> 40) & 0xff); 74 | writeByte((value >> 32) & 0xff); 75 | writeByte((value >> 24) & 0xff); 76 | writeByte((value >> 16) & 0xff); 77 | writeByte((value >> 8) & 0xff); 78 | writeByte((value) & 0xff); 79 | return; 80 | } 81 | writeByte((value) & 0xff); 82 | writeByte((value >> 8) & 0xff); 83 | writeByte((value >> 16) & 0xff); 84 | writeByte((value >> 24) & 0xff); 85 | writeByte((value >> 32) & 0xff); 86 | writeByte((value >> 40) & 0xff); 87 | writeByte((value >> 48) & 0xff); 88 | writeByte(topBit | ((value >> 56) & 0xff)); 89 | } 90 | 91 | Uint8List subset(int start, [int? end]); 92 | 93 | Uint8List getBytes() => subset(0, length); 94 | } 95 | -------------------------------------------------------------------------------- /test/_data/tar/gnu.tar: -------------------------------------------------------------------------------- 1 | small.txt0000640021650100116100000000000511213074064012105 0ustar dsymondsengKiltssmall2.txt0000640021650100116100000000001311213113114012154 0ustar dsymondsengGoogle.com 2 | -------------------------------------------------------------------------------- /test/_data/tar/star.tar: -------------------------------------------------------------------------------- 1 | small.txt0000640 0216501 0011610 00000000005 11213575217 0016730 0ustar00dsymondseng0000000 0000000 11213575217 11213575217 tarKiltssmall2.txt0000640 0216501 0011610 00000000013 11213575217 0017011 0ustar00dsymondseng0000000 0000000 11213575217 11213575217 tarGoogle.com 2 | -------------------------------------------------------------------------------- /lib/src/io/tar_command.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | import 'dart:io'; 3 | 4 | import '../../archive_io.dart'; 5 | 6 | /// Print the entries in the given tar file. 7 | void listTarFiles(String path) { 8 | final file = File(path); 9 | if (!file.existsSync()) { 10 | _fail('$path does not exist'); 11 | } 12 | 13 | final input = InputFileStream(path); 14 | final dir = Directory.systemTemp.createTempSync('foo'); 15 | final tempTarPath = '${dir.path}${Platform.pathSeparator}temp.tar'; 16 | final output = OutputFileStream(tempTarPath); 17 | 18 | //List data = file.readAsBytesSync(); 19 | if (path.endsWith('tar.gz') || path.endsWith('tgz')) { 20 | GZipDecoder().decodeStream(input, output); 21 | } else if (path.endsWith('tar.bz2') || path.endsWith('tbz')) { 22 | BZip2Decoder().decodeStream(input, output); 23 | } 24 | 25 | final tarInput = InputFileStream(tempTarPath); 26 | 27 | final tarArchive = TarDecoder(); 28 | // Tell the decoder not to store the actual file data since we don't need 29 | // it. 30 | tarArchive.decodeStream(tarInput, storeData: false); 31 | 32 | print('${tarArchive.files.length} file(s)'); 33 | for (final f in tarArchive.files) { 34 | print(' $f'); 35 | } 36 | } 37 | 38 | /// Extract the entries in the given tar file to a directory. 39 | Directory extractTarFiles(String inputPath, String outputPath) { 40 | Directory? tempDir; 41 | var tarPath = inputPath; 42 | 43 | if (inputPath.endsWith('tar.gz') || inputPath.endsWith('tgz')) { 44 | tempDir = Directory.systemTemp.createTempSync('dart_archive'); 45 | tarPath = '${tempDir.path}${Platform.pathSeparator}temp.tar'; 46 | final input = InputFileStream(inputPath); 47 | final tarOutput = OutputFileStream(tarPath); 48 | GZipDecoder().decodeStream(input, tarOutput); 49 | input.closeSync(); 50 | tarOutput.closeSync(); 51 | } 52 | 53 | final outDir = Directory(outputPath); 54 | if (!outDir.existsSync()) { 55 | outDir.createSync(recursive: true); 56 | } 57 | 58 | final input = InputFileStream(tarPath); 59 | final tarArchive = TarDecoder().decodeStream(input); 60 | 61 | for (final entry in tarArchive) { 62 | final path = '$outputPath${Platform.pathSeparator}${entry.name}'; 63 | if (entry.isDirectory) { 64 | Directory(path).createSync(recursive: true); 65 | } else { 66 | final output = OutputFileStream(path); 67 | entry.writeContent(output); 68 | print(' extracted ${path}'); 69 | output.closeSync(); 70 | } 71 | } 72 | 73 | input.closeSync(); 74 | tarArchive.clearSync(); 75 | 76 | /*if (tempDir != null) { 77 | tempDir.delete(recursive: true); 78 | }*/ 79 | 80 | return outDir; 81 | } 82 | 83 | Future createTarFile(String dirPath) async { 84 | final dir = Directory(dirPath); 85 | if (!dir.existsSync()) { 86 | _fail('$dirPath does not exist'); 87 | } 88 | 89 | // Encode a directory from disk to disk, no memory 90 | final encoder = TarFileEncoder(); 91 | await encoder.tarDirectory(dir, compression: TarFileEncoder.gzip); 92 | } 93 | 94 | void _fail(String message) { 95 | print(message); 96 | exit(1); 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/codecs/zip_decoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../archive/archive.dart'; 4 | import '../archive/archive_file.dart'; 5 | import '../util/input_memory_stream.dart'; 6 | import '../util/input_stream.dart'; 7 | import 'zip/zip_directory.dart'; 8 | 9 | /// Decode a zip formatted buffer into an [Archive] object. 10 | class ZipDecoder { 11 | late ZipDirectory directory; 12 | 13 | Archive decodeBytes(List bytes, 14 | {bool verify = false, String? password, ArchiveCallback? callback}) => 15 | decodeStream(InputMemoryStream(bytes), 16 | verify: verify, password: password, callback: callback); 17 | 18 | Archive decodeStream(InputStream input, 19 | {bool verify = false, String? password, ArchiveCallback? callback}) { 20 | directory = ZipDirectory(); 21 | directory.read(input, password: password); 22 | 23 | final archive = Archive(); 24 | for (final zfh in directory.fileHeaders) { 25 | final zf = zfh.file!; 26 | 27 | // The attributes are stored in base 8 28 | final mode = zfh.externalFileAttributes; 29 | 30 | /*if (verify) { 31 | final stream = zf.getStream(); 32 | final computedCrc = getCrc32(stream.toUint8List()); 33 | if (computedCrc != zf.crc32) { 34 | throw ArchiveException('Invalid CRC for file in archive.'); 35 | } 36 | }*/ 37 | 38 | final entryMode = mode >> 16; 39 | 40 | var isDirectory = false; 41 | if (zfh.versionMadeBy >> 8 == 3) { 42 | final fileType = entryMode & 0xf000; 43 | // No determination can be made so we assume it's a file.) 44 | if (fileType == 0x8000 || fileType == 0x0000) { 45 | isDirectory = false; 46 | } else { 47 | isDirectory = true; 48 | } 49 | } else { 50 | isDirectory = zf.filename.endsWith('/') || zf.filename.endsWith('\\'); 51 | } 52 | 53 | final filename = zf.filename; 54 | 55 | var entry = archive.find(filename); 56 | 57 | if (entry == null) { 58 | entry = isDirectory 59 | ? ArchiveFile.directory(filename) 60 | : ArchiveFile.file(filename, zf.uncompressedSize, zf); 61 | entry.compression = zf.compressionMethod; 62 | 63 | archive.add(entry); 64 | } 65 | 66 | entry.mode = entryMode; 67 | 68 | // see https://github.com/brendan-duncan/archive/issues/21 69 | // UNIX systems has a creator version of 3 decimal at 1 byte offset 70 | if (zfh.versionMadeBy >> 8 == 3) { 71 | final fileType = entry.mode & 0xf000; 72 | if (fileType == 0xa000) { 73 | final f = ArchiveFile.file(filename, zf.uncompressedSize, zf); 74 | f.compression = zf.compressionMethod; 75 | final bytes = f.readBytes(); 76 | if (bytes != null) { 77 | entry.symbolicLink = utf8.decode(bytes); 78 | } 79 | } 80 | } 81 | 82 | entry 83 | ..crc32 = zf.crc32 84 | ..lastModTime = zf.lastModFileDate << 16 | zf.lastModFileTime; 85 | 86 | if (callback != null) { 87 | callback(entry); 88 | } 89 | } 90 | 91 | return archive; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/input_file_stream_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:archive/archive_io.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import '_test_util.dart'; 8 | 9 | void main() { 10 | final testData = Uint8List(120); 11 | for (var i = 0; i < testData.length; ++i) { 12 | testData[i] = i; 13 | } 14 | final testPath = '$testOutputPath/test_123.bin'; 15 | File(testPath) 16 | ..createSync(recursive: true) 17 | ..writeAsBytesSync(testData); 18 | 19 | group('InputStreamFile', () { 20 | test('length', () async { 21 | final fs = InputFileStream(testPath)..open(); 22 | expect(fs.length, testData.length); 23 | }); 24 | 25 | test('readBytes', () async { 26 | final input = InputFileStream(testPath)..open(); 27 | expect(input.length, equals(120)); 28 | var same = true; 29 | var ai = 0; 30 | while (!input.isEOS) { 31 | final bs = input.readBytes(50); 32 | final bytes = bs.toUint8List(); 33 | for (var i = 0; i < bytes.length; ++i) { 34 | same = bytes[i] == ai + i; 35 | if (!same) { 36 | expect(same, equals(true)); 37 | return; 38 | } 39 | } 40 | ai += bytes.length; 41 | } 42 | }); 43 | 44 | test('position', () async { 45 | final fs = InputFileStream(testPath, bufferSize: 2) 46 | ..open() 47 | ..setPosition(50); 48 | final bs = fs.readBytes(50); 49 | final b = bs.toUint8List(); 50 | expect(b.length, 50); 51 | for (var i = 0; i < b.length; ++i) { 52 | expect(b[i], testData[50 + i]); 53 | } 54 | }); 55 | 56 | test('skip', () async { 57 | final fs = InputFileStream(testPath, bufferSize: 2) 58 | ..open() 59 | ..skip(50); 60 | final bs = fs.readBytes(50); 61 | final b = bs.toUint8List(); 62 | expect(b.length, 50); 63 | for (var i = 0; i < b.length; ++i) { 64 | expect(b[i], testData[50 + i]); 65 | } 66 | }); 67 | 68 | test('rewind', () async { 69 | final fs = InputFileStream(testPath, bufferSize: 2) 70 | ..open() 71 | ..skip(50) 72 | ..rewind(10); 73 | final bs = fs.readBytes(50); 74 | final b = bs.toUint8List(); 75 | expect(b.length, 50); 76 | for (var i = 0; i < b.length; ++i) { 77 | expect(b[i], testData[40 + i]); 78 | } 79 | }); 80 | 81 | test('peakBytes', () async { 82 | final fs = InputFileStream(testPath, bufferSize: 2)..open(); 83 | final bs = fs.peekBytes(10); 84 | final b = bs.toUint8List(); 85 | expect(fs.position, 0); 86 | expect(b.length, 10); 87 | for (var i = 0; i < b.length; ++i) { 88 | expect(b[i], testData[i]); 89 | } 90 | }); 91 | 92 | test("clone", () async { 93 | final input = InputFileStream(testPath)..open(); 94 | final input2 = 95 | InputFileStream.fromFileStream(input, position: 6, length: 5); 96 | final bs = input2.readBytes(5); 97 | final b = bs.toUint8List(); 98 | expect(b.length, 5); 99 | for (var i = 0; i < b.length; ++i) { 100 | expect(b[i], testData[6 + i]); 101 | } 102 | }); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /test/_data/tar/writer.tar: -------------------------------------------------------------------------------- 1 | small.txt0000640021650100116100000000000511223032352013400 0ustar00dsymondseng00000000000000Kiltssmall2.txt0000640021650100116100000000001311216101324013457 0ustar00dsymondseng00000000000000Google.com 2 | link.txt0000777000175000017500000000000011626640112015665 2small.txtustar00stringsstrings00000000000000 -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_zlib_decoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/adler32.dart'; 4 | import '../../util/byte_order.dart'; 5 | import '../../util/input_memory_stream.dart'; 6 | import '../../util/input_stream.dart'; 7 | import '../../util/output_memory_stream.dart'; 8 | import '../../util/output_stream.dart'; 9 | import '_zlib_decoder_base.dart'; 10 | import 'inflate.dart'; 11 | 12 | const platformZLibDecoder = _ZLibDecoder(); 13 | 14 | /// Decompress data with the zlib format decoder. 15 | class _ZLibDecoder extends ZLibDecoderBase { 16 | static const deflate = 8; 17 | 18 | const _ZLibDecoder(); 19 | 20 | @override 21 | Uint8List decodeBytes(List data, 22 | {bool verify = false, bool raw = false}) { 23 | final output = OutputMemoryStream(); 24 | decodeStream( 25 | InputMemoryStream(data, byteOrder: ByteOrder.bigEndian), output, 26 | verify: verify, raw: raw); 27 | return output.getBytes(); 28 | } 29 | 30 | @override 31 | bool decodeStream(InputStream input, OutputStream output, 32 | {bool verify = false, bool raw = false}) { 33 | Uint8List? buffer; 34 | 35 | while (!input.isEOS) { 36 | /* 37 | * The zlib format has the following structure: 38 | * CMF 1 byte 39 | * FLG 1 byte 40 | * [DICT_ID 4 bytes]? (if FLAG has FDICT (bit 5) set) 41 | * 42 | * ADLER32 4 bytes 43 | * ---- 44 | * CMF: 45 | * bits [0, 3] Compression Method, DEFLATE = 8 46 | * bits [4, 7] Compression Info, base-2 logarithm of the LZ77 window 47 | * size, minus eight (CINFO=7 indicates a 32K window size). 48 | * FLG: 49 | * bits [0, 4] FCHECK (check bits for CMF and FLG) 50 | * bits [5] FDICT (preset dictionary) 51 | * bits [6, 7] FLEVEL (compression level) 52 | */ 53 | if (!raw) { 54 | final cmf = input.readByte(); 55 | final flg = input.readByte(); 56 | 57 | final method = cmf & 8; 58 | final cinfo = (cmf >> 3) & 8; // ignore: unused_local_variable 59 | 60 | if (method != deflate) { 61 | //throw ArchiveException('Only DEFLATE compression supported: $method'); 62 | return false; 63 | } 64 | 65 | final fcheck = flg & 16; // ignore: unused_local_variable 66 | final fdict = (flg & 32) >> 5; 67 | final flevel = (flg & 64) >> 6; // ignore: unused_local_variable 68 | 69 | // FCHECK is set such that (cmf * 256 + flag) must be a multiple of 31. 70 | if (((cmf * 256) + flg) % 31 != 0) { 71 | //throw ArchiveException('Invalid FCHECK'); 72 | return false; 73 | } 74 | 75 | if (fdict != 0) { 76 | /*dictid =*/ input.readUint32(); 77 | //throw ArchiveException('FDICT Encoding not currently supported'); 78 | return false; 79 | } 80 | } 81 | 82 | if (buffer != null) { 83 | output.writeBytes(buffer); 84 | } 85 | 86 | // Inflate 87 | buffer = Inflate.stream(input).getBytes(); 88 | 89 | // verify adler-32 90 | if (!raw) { 91 | final adler32 = input.readUint32(); 92 | if (verify) { 93 | final a = getAdler32(buffer); 94 | if (adler32 != a) { 95 | buffer = null; 96 | return false; 97 | } 98 | } 99 | } 100 | } 101 | 102 | if (buffer != null) { 103 | output.writeBytes(buffer); 104 | } 105 | 106 | return true; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/util/output_memory_stream.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'byte_order.dart'; 4 | import 'input_memory_stream.dart'; 5 | import 'input_stream.dart'; 6 | import 'output_stream.dart'; 7 | 8 | class OutputMemoryStream extends OutputStream { 9 | @override 10 | int length; 11 | static const defaultBufferSize = 0x8000; // 32k block-size 12 | Uint8List _buffer; 13 | 14 | /// Create a byte buffer for writing. 15 | OutputMemoryStream( 16 | {int? size = defaultBufferSize, super.byteOrder = ByteOrder.littleEndian}) 17 | : _buffer = Uint8List(size ?? defaultBufferSize), 18 | length = 0; 19 | 20 | @override 21 | void flush() {} 22 | 23 | /// Get the resulting bytes from the buffer. 24 | @override 25 | Uint8List getBytes() => 26 | Uint8List.view(_buffer.buffer, _buffer.offsetInBytes, length); 27 | 28 | /// Clear the buffer. 29 | @override 30 | void clear() { 31 | _buffer = Uint8List(defaultBufferSize); 32 | length = 0; 33 | } 34 | 35 | /// Reset the buffer. 36 | void reset() { 37 | length = 0; 38 | } 39 | 40 | /// Write a byte to the end of the buffer. 41 | @override 42 | void writeByte(int value) { 43 | if (length == _buffer.length) { 44 | _expandBuffer(); 45 | } 46 | _buffer[length++] = value; 47 | } 48 | 49 | /// Write a set of bytes to the end of the buffer. 50 | @override 51 | void writeBytes(List bytes, {int? length}) { 52 | length ??= bytes.length; 53 | 54 | while (this.length + length > _buffer.length) { 55 | _expandBuffer((this.length + length) - _buffer.length); 56 | } 57 | _buffer.setRange(this.length, this.length + length, bytes); 58 | this.length += length; 59 | } 60 | 61 | @override 62 | void writeStream(InputStream stream) { 63 | while (length + stream.length > _buffer.length) { 64 | _expandBuffer((length + stream.length) - _buffer.length); 65 | } 66 | 67 | if (stream is InputMemoryStream) { 68 | if (stream.buffer != null) { 69 | _buffer.setRange( 70 | length, length + stream.length, stream.buffer!, stream.position); 71 | } 72 | } else { 73 | final bytes = stream.toUint8List(); 74 | _buffer.setRange(length, length + stream.length, bytes, 0); 75 | } 76 | length += stream.length; 77 | } 78 | 79 | /// Return the subset of the buffer in the range [start:end]. 80 | /// 81 | /// If [start] or [end] are < 0 then it is relative to the end of the buffer. 82 | /// If [end] is not specified (or null), then it is the end of the buffer. 83 | /// This is equivalent to the python list range operator. 84 | @override 85 | Uint8List subset(int start, [int? end]) { 86 | if (start < 0) { 87 | start = length + start; 88 | } 89 | 90 | if (end == null) { 91 | end = length; 92 | } else if (end < 0) { 93 | end = length + end; 94 | } 95 | 96 | return Uint8List.view( 97 | _buffer.buffer, _buffer.offsetInBytes + start, end - start); 98 | } 99 | 100 | /// Grow the buffer to accommodate additional data. 101 | void _expandBuffer([int? required]) { 102 | var blockSize = defaultBufferSize; 103 | if (required != null) { 104 | if (required > defaultBufferSize) { 105 | blockSize = required; 106 | } 107 | } 108 | final newLength = (_buffer.length + blockSize) * 2; 109 | final newBuffer = Uint8List(newLength) 110 | ..setRange(0, _buffer.length, _buffer); 111 | _buffer = newBuffer; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /doc/migrating_3_to_4.md: -------------------------------------------------------------------------------- 1 | # Migrating Archive 3.x to 4.x 2 | 3 | The Archive library was originally written when the web was the primary use of Dart. File IO was less of a concern 4 | and the design was around having everything in memory. As other uses of Dart came about, such as Flutter, a lot 5 | of File IO operations were added to the library, but not in a very clean way. 6 | 7 | The design goal for the 4.0 revision of the library is to ensure File IO is a primary focus, while minimizing memory 8 | usage. Memory-only interfaces are still available for web platforms. 9 | 10 | ### InputStream and OutputStream 11 | 12 | **InputStream** was renamed to **InputMemoryStream**. 13 | 14 | **InputStreamBase** was was renamed to **InputStream**. 15 | 16 | **InputFileStream** was moved to the core archive library from the archive_io library. Conditional imports are used 17 | to ensure their use of dart:io doesn't interfere with web builds. 18 | 19 | **OutputStream** was renamed to **OutputMemoryStream**. 20 | 21 | **OutputStreamBase** was renamed to **OutputStream**. 22 | 23 | **OutputFileStream** was moved to the core archive library from the archive_io library. On non-web builds, it will 24 | throw an exception if used, as there is no file system on the web. 25 | 26 | ### File Data 27 | 28 | #### 3.x 29 | 30 | In 3.x, ArchiveFile had a `dynamic get content` getter, which would return the data of the file, decompressing it as 31 | necessary. 32 | 33 | #### 4.x 34 | 35 | In 4.x, memory management is a priority so the design around file data has changed somewhat. The content object 36 | of ArchiveFile was changed from __dynamic__ to a **FileContent** object, which points to the file data, either 37 | in memory or to a position in a FileHandle. 38 | 39 | `InputStream? ArchiveFile.getContent()` will return an InputStream object, decompressing the data in memory as 40 | necessary. 41 | 42 | `Uint8List? ArchiveFile.readBytes()` will do the same, but get the Uint8List of the data from the 43 | InputStream. 44 | 45 | `void ArchiveFile.writeContent(OutputStream output, {bool freeMemory = true})` will write the contents of the file 46 | to an OutputStream, decompressing the data as necessary to that OutputStream without locally storing the data in 47 | memory. If the OutputStream is a OutputFileStream, then decompression will stream directly to file output without 48 | storing the file content in memory. 49 | 50 | ### Compression Decoders: ZLibDecoder, GZipDecoder, BZip2Decoder, XzDecoder 51 | 52 | **decodeBytes** now return an explicit Uint8List, rather than List (it was always a Uint8List). 53 | 54 | **decodeBuffer** has been renamed to **decodeStream**. decodeStream does not return the bytes, and instead takes 55 | an OutputStream that the data is written to. This can be an OutputMemoryStream or an OutputFileStream. 56 | 57 | If the input of decodeStream is an InputFileStream, and the output is an OutputFileStream, decoding will be read 58 | directly from disk, and written directly to disk, with only a file IO buffer amount of memory used. 59 | 60 | ### Compression Encoders: ZLibEncoder, GZipEncoder, BZip2Encoder, XzEncoder 61 | 62 | **encode** method was renamed to **encodeBytes** to be consistent with decoders. 63 | 64 | **encodeStream** was added, which take an InputStream input and an OutputStream output. This allows compression 65 | encoders to stream data in from memory or a file, and out to memory or to a file. 66 | 67 | ### Archive Decoders: ZipDecoder, TarDecoder 68 | 69 | **decodeBuffer** was renamed to **decodeStream*. 70 | 71 | ### Archive Encoders: ZipEncoder, TarEncoder 72 | 73 | **encode** was renamed to **encodeBytes**. 74 | 75 | **encodeStream** was added to write the output to an OutputStream. 76 | -------------------------------------------------------------------------------- /lib/src/io/tar_file_encoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart' as path; 4 | 5 | import '../archive/archive_file.dart'; 6 | import '../codecs/gzip_encoder.dart'; 7 | import '../codecs/tar_encoder.dart'; 8 | import '../util/input_file_stream.dart'; 9 | import '../util/output_file_stream.dart'; 10 | import 'zip_file_progress.dart'; 11 | 12 | class TarFileEncoder { 13 | late String tarPath; 14 | late OutputFileStream _output; 15 | late TarEncoder _encoder; 16 | 17 | static const store = 0; 18 | static const gzip = 1; 19 | 20 | Future tarDirectory(Directory dir, 21 | {int compression = store, 22 | String? filename, 23 | bool followLinks = true, 24 | int? level, 25 | ZipFileProgress? filter}) async { 26 | final dirPath = dir.path; 27 | var tarPath = filename ?? '$dirPath.tar'; 28 | final tgzPath = filename ?? '$dirPath.tar.gz'; 29 | 30 | Directory tempDir; 31 | if (compression == gzip) { 32 | tempDir = await Directory.systemTemp.createTemp('dart_archive'); 33 | tarPath = '${tempDir.path}/temp.tar'; 34 | } 35 | 36 | // Encode a directory from disk to disk, no memory 37 | open(tarPath); 38 | await addDirectory(Directory(dirPath), 39 | followLinks: followLinks, filter: filter); 40 | await close(); 41 | 42 | if (compression == gzip) { 43 | final input = InputFileStream(tarPath); 44 | final output = OutputFileStream(tgzPath); 45 | GZipEncoder().encodeStream(input, output, level: level ?? 6); 46 | await input.close(); 47 | await File(tarPath).delete(); 48 | } 49 | } 50 | 51 | void open(String tarPath) => create(tarPath); 52 | 53 | void create(String tarPath) { 54 | this.tarPath = tarPath; 55 | _output = OutputFileStream(tarPath); 56 | _encoder = TarEncoder(); 57 | _encoder.start(_output); 58 | } 59 | 60 | Future addDirectory(Directory dir, 61 | {bool followLinks = true, 62 | bool includeDirName = true, 63 | ZipFileProgress? filter}) async { 64 | final files = dir.listSync(recursive: true, followLinks: followLinks); 65 | 66 | final dirName = path.basename(dir.path); 67 | final numFiles = files.length; 68 | var fileCount = 0; 69 | for (final file in files) { 70 | final progress = ++fileCount / numFiles; 71 | if (filter != null) { 72 | final operation = filter(file, progress); 73 | if (operation == ZipFileOperation.cancel) { 74 | break; 75 | } 76 | if (operation == ZipFileOperation.skip) { 77 | continue; 78 | } 79 | } 80 | if (file is Directory) { 81 | var filename = path.relative(file.path, from: dir.path); 82 | filename = includeDirName ? '$dirName/$filename' : filename; 83 | final af = ArchiveFile.directory('$filename/'); 84 | af.mode = (await file.stat()).mode; 85 | _encoder.add(af); 86 | } else if (file is File) { 87 | final dirName = path.basename(dir.path); 88 | final relPath = path.relative(file.path, from: dir.path); 89 | await addFile(file, includeDirName ? '$dirName/$relPath' : relPath); 90 | } 91 | } 92 | } 93 | 94 | Future addFile(File file, [String? filename]) async { 95 | final fileStream = InputFileStream(file.path); 96 | final f = 97 | ArchiveFile.stream(filename ?? path.basename(file.path), fileStream); 98 | f.lastModTime = (await file.lastModified()).millisecondsSinceEpoch ~/ 1000; 99 | f.mode = (await file.stat()).mode; 100 | _encoder.add(f); 101 | await fileStream.close(); 102 | } 103 | 104 | Future close() async { 105 | _encoder.finish(); 106 | await _output.close(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/gzip_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:archive/archive.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import '_test_util.dart'; 8 | 9 | void main() { 10 | group('gzip', () { 11 | final buffer = Uint8List(10000); 12 | for (var i = 0; i < buffer.length; ++i) { 13 | buffer[i] = i % 256; 14 | } 15 | 16 | test('zlib encode_web/decode', () { 17 | final origData = [1, 2, 3, 4, 5, 6]; 18 | final compressed = ZLibEncoderWeb().encodeBytes(origData); 19 | final uncompressed = ZLibDecoder().decodeBytes(compressed); 20 | compareBytes(uncompressed, origData); 21 | }); 22 | 23 | test('zlib encode/decode_web', () { 24 | final origData = [1, 2, 3, 4, 5, 6]; 25 | final compressed = ZLibEncoder().encodeBytes(origData); 26 | final uncompressed = ZLibDecoderWeb().decodeBytes(compressed); 27 | compareBytes(uncompressed, origData); 28 | }); 29 | 30 | test('gzip encode_web/decode', () { 31 | final origData = [1, 2, 3, 4, 5, 6]; 32 | final compressed = GZipEncoderWeb().encodeBytes(origData); 33 | final uncompressed = GZipDecoder().decodeBytes(compressed); 34 | compareBytes(uncompressed, origData); 35 | }); 36 | 37 | test('gzip encode/decode_web', () { 38 | final origData = [1, 2, 3, 4, 5, 6]; 39 | final compressed = GZipEncoder().encodeBytes(origData); 40 | final uncompressed = GZipDecoderWeb().decodeBytes(compressed); 41 | compareBytes(uncompressed, origData); 42 | }); 43 | 44 | test('multiblock', () async { 45 | final compressedData = [ 46 | ...GZipEncoder().encodeBytes([1, 2, 3]), 47 | ...GZipEncoder().encodeBytes([4, 5, 6]) 48 | ]; 49 | final decodedData = 50 | GZipDecoderWeb().decodeBytes(compressedData, verify: true); 51 | compareBytes(decodedData, [1, 2, 3, 4, 5, 6]); 52 | }); 53 | 54 | test('encode/decode', () { 55 | final compressed = GZipEncoder().encodeBytes(buffer); 56 | final decompressed = GZipDecoder().decodeBytes(compressed, verify: true); 57 | expect(decompressed.length, equals(buffer.length)); 58 | for (var i = 0; i < buffer.length; ++i) { 59 | expect(decompressed[i], equals(buffer[i])); 60 | } 61 | }); 62 | 63 | test('decode res/cat.jpg.gz', () { 64 | final b = File('test/_data/cat.jpg'); 65 | final bBytes = b.readAsBytesSync(); 66 | 67 | final file = File('test/_data/cat.jpg.gz'); 68 | final bytes = file.readAsBytesSync(); 69 | 70 | final zBytes = GZipDecoder().decodeBytes(bytes, verify: true); 71 | compareBytes(zBytes, bBytes); 72 | }); 73 | 74 | test('decode res/test2.tar.gz', () { 75 | final b = File('test/_data/test2.tar'); 76 | final bBytes = b.readAsBytesSync(); 77 | 78 | final file = File('test/_data/test2.tar.gz'); 79 | final bytes = file.readAsBytesSync(); 80 | 81 | final zBytes = GZipDecoder().decodeBytes(bytes, verify: true); 82 | compareBytes(zBytes, bBytes); 83 | }); 84 | 85 | test('decode res/a.txt.gz', () { 86 | final aBytes = aTxt.codeUnits; 87 | 88 | final file = File('test/_data/a.txt.gz'); 89 | final bytes = file.readAsBytesSync(); 90 | 91 | final zBytes = GZipDecoder().decodeBytes(bytes, verify: true); 92 | compareBytes(zBytes, aBytes); 93 | }); 94 | 95 | test('encode res/cat.jpg', () { 96 | final b = File('test/_data/cat.jpg'); 97 | final bBytes = b.readAsBytesSync(); 98 | 99 | final compressed = GZipEncoder().encodeBytes(bBytes); 100 | final f = File('$testOutputPath/cat.jpg.gz'); 101 | f.createSync(recursive: true); 102 | f.writeAsBytesSync(compressed); 103 | }); 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_encoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/byte_order.dart'; 4 | import '../../util/input_memory_stream.dart'; 5 | import '../../util/input_stream.dart'; 6 | import '../../util/output_memory_stream.dart'; 7 | import '../../util/output_stream.dart'; 8 | import '_zlib_encoder_base.dart'; 9 | import 'deflate.dart'; 10 | import 'gzip_flag.dart'; 11 | 12 | const platformGZipEncoder = _GZipEncoder(); 13 | 14 | class _GZipEncoder extends ZLibEncoderBase { 15 | const _GZipEncoder(); 16 | 17 | @override 18 | Uint8List encodeBytes(List bytes, 19 | {int? level, int? windowBits, bool raw = false}) { 20 | final output = OutputMemoryStream(byteOrder: ByteOrder.littleEndian); 21 | encodeStream(InputMemoryStream(bytes), output, 22 | level: level, windowBits: windowBits, raw: raw); 23 | return output.getBytes(); 24 | } 25 | 26 | @override 27 | void encodeStream(InputStream input, OutputStream output, 28 | {int? level, int? windowBits, bool raw = false}) { 29 | // The GZip format has the following structure: 30 | // Offset Length Contents 31 | // 0 2 bytes magic header 0x1f, 0x8b (\037 \213) 32 | // 2 1 byte compression method 33 | // 0: store (copied) 34 | // 1: compress 35 | // 2: pack 36 | // 3: lzh 37 | // 4..7: reserved 38 | // 8: deflate 39 | // 3 1 byte flags 40 | // bit 0 set: file probably ascii text 41 | // bit 1 set: continuation of multi-part gzip file, part number present 42 | // bit 2 set: extra field present 43 | // bit 3 set: original file name present 44 | // bit 4 set: file comment present 45 | // bit 5 set: file is encrypted, encryption header present 46 | // bit 6,7: reserved 47 | // 4 4 bytes file modification time in Unix format 48 | // 8 1 byte extra flags (depend on compression method) 49 | // 9 1 byte OS type 50 | // [ 51 | // 2 bytes optional part number (second part=1) 52 | // ]? 53 | // [ 54 | // 2 bytes optional extra field length (e) 55 | // (e)bytes optional extra field 56 | // ]? 57 | // [ 58 | // bytes optional original file name, zero terminated 59 | // ]? 60 | // [ 61 | // bytes optional file comment, zero terminated 62 | // ]? 63 | // [ 64 | // 12 bytes optional encryption header 65 | // ]? 66 | // bytes compressed data 67 | // 4 bytes crc32 68 | // 4 bytes uncompressed input size modulo 2^32 69 | 70 | if (raw) { 71 | Deflate.stream(input, 72 | level: level ?? 6, windowBits: windowBits ?? 15, output: output); 73 | output.flush(); 74 | return; 75 | } 76 | 77 | final dataLength = input.length; 78 | 79 | output.writeUint16(GZipFlag.signature); 80 | output.writeByte(GZipFlag.deflate); 81 | 82 | final flags = 0; 83 | final fileModTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; 84 | final extraFlags = 0; 85 | final osType = GZipFlag.osUnknown; 86 | 87 | output.writeByte(flags); 88 | output.writeUint32(fileModTime); 89 | output.writeByte(extraFlags); 90 | output.writeByte(osType); 91 | 92 | final deflate = Deflate.stream(input, 93 | level: level ?? 6, windowBits: windowBits ?? 15, output: output); 94 | 95 | output.writeUint32(deflate.crc32); 96 | 97 | output.writeUint32(dataLength); 98 | 99 | output.flush(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/_data/test2.tar: -------------------------------------------------------------------------------- 1 | test2/ 40777 0 0 0 13331747445 5151 5test2/test/ 40777 0 0 0 13331747455 6131 5test2/test.txt100777 0 0 3 13331747474 6704 0abctest2/test/test.txt100777 0 0 3 13331747465 7663 0def -------------------------------------------------------------------------------- /lib/src/codecs/lzma/range_decoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_stream.dart'; 4 | 5 | // Number of bits used for probabilities. 6 | const _probabilityBitCount = 11; 7 | 8 | // Value used for a probability of 1.0. 9 | const _probabilityOne = (1 << _probabilityBitCount); 10 | 11 | // Value used for a probability of 0.5. 12 | const _probabilityHalf = _probabilityOne ~/ 2; 13 | 14 | /// Probability table used with [RangeDecoder]. 15 | class RangeDecoderTable { 16 | // Table of probabilities for each symbol. 17 | final Uint16List table; 18 | 19 | // Creates a new probability table for [length] elements. 20 | RangeDecoderTable(int length) : table = Uint16List(length) { 21 | reset(); 22 | } 23 | 24 | // Reset the table to probabilities of 0.5. 25 | void reset() { 26 | table.fillRange(0, table.length, _probabilityHalf); 27 | } 28 | } 29 | 30 | /// Implements the LZMA range decoder for [LZMADecoder]. 31 | class RangeDecoder { 32 | // Data being read from. 33 | late InputStream _input; 34 | 35 | // Mask showing the current bits in [code]. 36 | var range = 0xffffffff; 37 | 38 | // Current code being stored. 39 | var code = 0; 40 | 41 | // Set the input being read from. Must be set before initializing or reading 42 | // bits. 43 | set input(InputStream value) { 44 | _input = value; 45 | } 46 | 47 | void reset() { 48 | range = 0xffffffff; 49 | code = 0; 50 | } 51 | 52 | void initialize() { 53 | code = 0; 54 | range = 0xffffffff; 55 | // Skip the first byte, then load four for the initial state. 56 | _input.skip(1); 57 | for (var i = 0; i < 4; i++) { 58 | code = (code << 8 | _input.readByte()); 59 | } 60 | } 61 | 62 | // Read a single bit from the decoder, using the supplied [index] into a 63 | // probabilities [table]. 64 | int readBit(RangeDecoderTable table, int index) { 65 | _load(); 66 | 67 | final p = table.table[index]; 68 | final bound = (range >> _probabilityBitCount) * p; 69 | const moveBits = 5; 70 | if (code < bound) { 71 | range = bound; 72 | final oneMinusP = _probabilityOne - p; 73 | final shifted = oneMinusP >> moveBits; 74 | table.table[index] += shifted; 75 | return 0; 76 | } else { 77 | range -= bound; 78 | code -= bound; 79 | table.table[index] -= p >> moveBits; 80 | return 1; 81 | } 82 | } 83 | 84 | // Read a bittree (big endian) of [count] bits from the decoder. 85 | int readBittree(RangeDecoderTable table, int count) { 86 | var value = 0; 87 | var symbolPrefix = 1; 88 | for (var i = 0; i < count; i++) { 89 | final b = readBit(table, symbolPrefix | value); 90 | value = ((value << 1) | b) & 0xffffffff; 91 | symbolPrefix = (symbolPrefix << 1) & 0xffffffff; 92 | } 93 | 94 | return value; 95 | } 96 | 97 | // Read a reverse bittree (little endian) of [count] bits from the decoder. 98 | int readBittreeReverse(RangeDecoderTable table, int count) { 99 | var value = 0; 100 | var symbolPrefix = 1; 101 | for (var i = 0; i < count; i++) { 102 | final b = readBit(table, symbolPrefix | value); 103 | value = (value | b << i) & 0xffffffff; 104 | symbolPrefix = (symbolPrefix << 1) & 0xffffffff; 105 | } 106 | 107 | return value; 108 | } 109 | 110 | // Read [count] bits directly from the decoder. 111 | int readDirect(int count) { 112 | var value = 0; 113 | for (var i = 0; i < count; i++) { 114 | _load(); 115 | range >>= 1; 116 | code -= range; 117 | value <<= 1; 118 | if (code & 0x80000000 != 0) { 119 | code += range; 120 | } else { 121 | value++; 122 | } 123 | } 124 | 125 | return value; 126 | } 127 | 128 | // Load a byte if we can fit it. 129 | void _load() { 130 | const topValue = 1 << 24; 131 | if (range < topValue) { 132 | range <<= 8; 133 | code = (code << 8) | _input.readByte(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/codecs/tar_decoder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../archive/archive.dart'; 4 | import '../archive/archive_file.dart'; 5 | import '../util/input_memory_stream.dart'; 6 | import '../util/input_stream.dart'; 7 | import 'tar/tar_file.dart'; 8 | 9 | final paxRecordRegexp = RegExp(r"(\d+) (\w+)=(.*)"); 10 | 11 | /// Decode a tar formatted buffer into an [Archive] object. 12 | class TarDecoder { 13 | final Encoding filenameEncoding; 14 | List files = []; 15 | 16 | TarDecoder({this.filenameEncoding = const Utf8Codec()}); 17 | 18 | Archive decodeBytes(List data, 19 | {bool verify = false, bool storeData = true, ArchiveCallback? callback}) { 20 | return decodeStream(InputMemoryStream(data), 21 | verify: verify, storeData: storeData, callback: callback); 22 | } 23 | 24 | Archive decodeStream(InputStream input, 25 | {bool verify = false, bool storeData = true, ArchiveCallback? callback}) { 26 | final archive = Archive(); 27 | files.clear(); 28 | 29 | String? nextName; 30 | String? nextLinkName; 31 | 32 | // TarFile paxHeader = null; 33 | while (!input.isEOS) { 34 | // End of archive when two consecutive 0's are found. 35 | final endCheck = input.peekBytes(2).toUint8List(); 36 | if (endCheck.length < 2 || (endCheck[0] == 0 && endCheck[1] == 0)) { 37 | break; 38 | } 39 | 40 | final tf = 41 | TarFile.read(input, storeData: storeData, encoding: filenameEncoding); 42 | // GNU tar puts filenames in files when they exceed tar's native length. 43 | if (tf.filename == '././@LongLink') { 44 | nextName = tf.rawContent!.readString(); 45 | continue; 46 | } 47 | 48 | // In POSIX formatted tar files, a separate 'PAX' file contains extended 49 | // metadata for files. These are identified by having a type flag 'X'. 50 | // TODO: parse these metadata values. 51 | if (tf.typeFlag == TarFile.gExHeader || 52 | tf.typeFlag == TarFile.gExHeader2) { 53 | // TODO handle PAX global header. 54 | continue; 55 | } 56 | if (tf.typeFlag == TarFile.exHeader || tf.typeFlag == TarFile.exHeader2) { 57 | utf8 58 | .decode(tf.rawContent!.toUint8List()) 59 | .split('\n') 60 | .where((s) => paxRecordRegexp.hasMatch(s)) 61 | .forEach((record) { 62 | final match = paxRecordRegexp.firstMatch(record)!; 63 | final keyword = match.group(2); 64 | final value = match.group(3)!; 65 | switch (keyword) { 66 | case 'path': 67 | nextName = value; 68 | break; 69 | case 'linkpath': 70 | nextLinkName = value; 71 | break; 72 | default: 73 | // TODO: support other pax headers. 74 | } 75 | }); 76 | continue; 77 | } 78 | 79 | // Fix file attributes. 80 | if (nextName != null) { 81 | tf.filename = nextName!; 82 | nextName = null; 83 | } 84 | if (nextLinkName != null) { 85 | tf.nameOfLinkedFile = nextLinkName!; 86 | nextLinkName = null; 87 | } 88 | files.add(tf); 89 | 90 | final filename = tf.filename; 91 | 92 | if (tf.isFile) { 93 | final file = storeData 94 | ? ArchiveFile.stream(filename, tf.rawContent!) 95 | : ArchiveFile.noData(filename); 96 | 97 | file.mode = tf.mode; 98 | file.ownerId = tf.ownerId; 99 | file.groupId = tf.groupId; 100 | file.lastModTime = tf.lastModTime; 101 | if (tf.nameOfLinkedFile != null) { 102 | file.symbolicLink = tf.nameOfLinkedFile!; 103 | } 104 | 105 | archive.add(file); 106 | 107 | if (callback != null) { 108 | callback(file); 109 | } 110 | } else { 111 | final file = ArchiveFile.directory(filename); 112 | file.mode = tf.mode; 113 | file.ownerId = tf.ownerId; 114 | file.groupId = tf.groupId; 115 | file.lastModTime = tf.lastModTime; 116 | if (tf.nameOfLinkedFile != null) { 117 | file.symbolicLink = tf.nameOfLinkedFile!; 118 | } 119 | 120 | archive.add(file); 121 | 122 | if (callback != null) { 123 | callback(file); 124 | } 125 | } 126 | } 127 | 128 | return archive; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/src/archive/archive.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import 'archive_file.dart'; 5 | 6 | //// A collection of files 7 | class Archive extends IterableBase { 8 | /// The list of files in the archive. 9 | final List _files = []; 10 | final Map _fileMap = {}; 11 | 12 | /// A global comment for the archive. 13 | String? comment; 14 | 15 | /// Unmodifiable view of the files in the archive. 16 | List get files => UnmodifiableListView(_files); 17 | 18 | /// Add a file or directory to the archive. 19 | void add(ArchiveFile file) { 20 | // Adding a file with the same path as one that's already in the archive 21 | // will replace the previous file. 22 | final index = _fileMap[file.name]; 23 | if (index != null) { 24 | _files[index] = file; 25 | return; 26 | } 27 | // No existing file was in the archive with the same path, add it to the 28 | // archive. 29 | _files.add(file); 30 | _fileMap[file.name] = _files.length - 1; 31 | } 32 | 33 | void modifyAtIndex(int index, ArchiveFile file) { 34 | _files[index] = file; // Modify the underlying list 35 | } 36 | 37 | /// Alias for [add] for backwards compatibility. 38 | void addFile(ArchiveFile file) => add(file); 39 | 40 | void removeFile(ArchiveFile file) { 41 | final index = _fileMap[file.name]; 42 | if (index != null) { 43 | _files.removeAt(index); 44 | _fileMap.remove(file.name); 45 | // Indexes have changed, update the file map. 46 | _updateFileMap(); 47 | } 48 | } 49 | 50 | void removeAt(int index) { 51 | if (index < 0 || index >= _files.length) { 52 | return; 53 | } 54 | _fileMap.remove(_files[index].name); 55 | _files.removeAt(index); 56 | // Indexes have changed, update the file map. 57 | _updateFileMap(); 58 | } 59 | 60 | Future clear() async { 61 | var futures = >[for (var fp in _files) fp.close()]; 62 | _files.clear(); 63 | _fileMap.clear(); 64 | comment = null; 65 | await Future.wait(futures); 66 | } 67 | 68 | void clearSync() { 69 | for (var fp in _files) { 70 | fp.closeSync(); 71 | } 72 | _files.clear(); 73 | _fileMap.clear(); 74 | comment = null; 75 | } 76 | 77 | /// The number of files in the archive. 78 | @override 79 | int get length => _files.length; 80 | 81 | /// Get a file from the archive. 82 | ArchiveFile operator [](int index) => _files[index]; 83 | 84 | /// Set a file in the archive. 85 | void operator []=(int index, ArchiveFile file) { 86 | if (index < 0 || index >= _files.length) { 87 | return; 88 | } 89 | _fileMap.remove(_files[index].name); 90 | _files[index] = file; 91 | _fileMap[file.name] = index; 92 | } 93 | 94 | /// Find a file with the given [name] in the archive. If the file isn't found, 95 | /// null will be returned. 96 | ArchiveFile? find(String name) { 97 | var index = _fileMap[name]; 98 | return index != null ? _files[index] : null; 99 | } 100 | 101 | /// Alias for [find], for backwards compatibility. 102 | ArchiveFile? findFile(String name) => find(name); 103 | 104 | /// The number of files in the archive. 105 | int numberOfFiles() => _files.length; 106 | 107 | /// The name of the file at the given [index]. 108 | String fileName(int index) => _files[index].name; 109 | 110 | /// The decompressed size of the file at the given [index]. 111 | int fileSize(int index) => _files[index].size; 112 | 113 | /// The decompressed data of the file at the given [index]. 114 | Uint8List fileData(int index) => _files[index].content; 115 | 116 | @override 117 | ArchiveFile get first => _files.first; 118 | 119 | @override 120 | ArchiveFile get last => _files.last; 121 | 122 | @override 123 | bool get isEmpty => _files.isEmpty; 124 | 125 | // Returns true if there is at least one element in this collection. 126 | @override 127 | bool get isNotEmpty => _files.isNotEmpty; 128 | 129 | @override 130 | Iterator get iterator => _files.iterator; 131 | 132 | void _updateFileMap() { 133 | _fileMap.clear(); 134 | for (var i = 0; i < _files.length; i++) { 135 | _fileMap[_files[i].name] = i; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/src/util/input_memory_stream.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'byte_order.dart'; 5 | import 'input_stream.dart'; 6 | 7 | /// Stream in data from a memory buffer. 8 | class InputMemoryStream extends InputStream { 9 | Uint8List? buffer; 10 | // The read offset into the buffer. 11 | int _position; 12 | late int _length; 13 | 14 | /// Create a [InputStream] for reading from a Uint8List 15 | InputMemoryStream(List bytes, 16 | {super.byteOrder = ByteOrder.littleEndian, int? offset, int? length}) 17 | : _position = 0 { 18 | offset ??= 0; 19 | length ??= bytes.length - offset; 20 | if ((offset + length) > bytes.length) { 21 | length = bytes.length - offset; 22 | } 23 | 24 | final data = bytes is Uint8List ? bytes : Uint8List.fromList(bytes); 25 | 26 | buffer = Uint8List.view(data.buffer, data.offsetInBytes + offset, length); 27 | _length = buffer!.length; 28 | } 29 | 30 | InputMemoryStream.empty() 31 | : buffer = Uint8List(0), 32 | _position = 0, 33 | _length = 0, 34 | super(byteOrder: ByteOrder.littleEndian); 35 | 36 | InputMemoryStream.fromList(List bytes, 37 | {super.byteOrder = ByteOrder.littleEndian}) 38 | : buffer = Uint8List.fromList(bytes), 39 | _position = 0, 40 | _length = bytes.length; 41 | 42 | /// Create a copy of [other]. 43 | InputMemoryStream.from(InputMemoryStream other) 44 | : buffer = other.buffer, 45 | _position = other._position, 46 | _length = other._length, 47 | super(byteOrder: other.byteOrder); 48 | 49 | /// The current read position relative to the start of the buffer. 50 | @override 51 | int get position => _position; 52 | 53 | @override 54 | set position(int v) => setPosition(v); 55 | 56 | /// How many bytes are left in the stream. 57 | @override 58 | int get length => buffer == null ? 0 : buffer!.length - _position; 59 | 60 | /// Is the current position at the end of the stream? 61 | @override 62 | bool get isEOS => _position >= _length; 63 | 64 | @override 65 | void setPosition(int v) { 66 | _position = v; 67 | } 68 | 69 | /// Reset to the beginning of the stream. 70 | @override 71 | void reset() { 72 | _position = 0; 73 | } 74 | 75 | @override 76 | bool open() => true; 77 | 78 | @override 79 | Future close() async { 80 | _position = 0; 81 | } 82 | 83 | @override 84 | void closeSync() { 85 | _position = 0; 86 | } 87 | 88 | /// Rewind the read head of the stream by the given number of bytes. 89 | @override 90 | void rewind([int length = 1]) { 91 | _position -= length; 92 | _position = _position.clamp(0, _length); 93 | } 94 | 95 | /// Move the read position by [count] bytes. 96 | @override 97 | void skip(int count) { 98 | _position += count; 99 | _position = _position.clamp(0, _length); 100 | } 101 | 102 | /// Access the buffer relative from the current position. 103 | int operator [](int index) => buffer![_position + index]; 104 | 105 | /// Return an [InputStream] to read a subset of this stream. It does not 106 | /// move the read position of this stream. [position] is specified relative 107 | /// to the start of the buffer. If [position] is not specified, the current 108 | /// read position is used. If [length] is not specified, the remainder of this 109 | /// stream is used. 110 | @override 111 | InputStream subset({int? position, int? length, int? bufferSize}) { 112 | if (buffer == null) { 113 | return InputMemoryStream([]); 114 | } 115 | position ??= _position; 116 | length ??= _length - position; 117 | return InputMemoryStream(buffer!, 118 | byteOrder: byteOrder, offset: position, length: length); 119 | } 120 | 121 | /// Read a single byte. 122 | @override 123 | int readByte() { 124 | final b = buffer![_position++]; 125 | return b; 126 | } 127 | 128 | @override 129 | Uint8List toUint8List() { 130 | if (buffer == null) { 131 | return Uint8List(0); 132 | } 133 | var len = length; 134 | if ((_position + len) > buffer!.length) { 135 | len = buffer!.length - _position; 136 | } 137 | 138 | final bytes = 139 | Uint8List.view(buffer!.buffer, buffer!.offsetInBytes + _position, len); 140 | 141 | return bytes; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/src/codecs/zip/zip_file_header.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_memory_stream.dart'; 4 | import '../../util/input_stream.dart'; 5 | import 'zip_file.dart'; 6 | 7 | /// Provides information about a file, used by [ZipDecoder]. 8 | class ZipFileHeader { 9 | static const signature = 0x02014b50; 10 | int versionMadeBy = 0; 11 | int versionNeededToExtract = 0; 12 | int generalPurposeBitFlag = 0; 13 | int compressionMethod = 0; 14 | int lastModifiedFileTime = 0; 15 | int lastModifiedFileDate = 0; 16 | int crc32 = 0; 17 | int compressedSize = 0; 18 | int uncompressedSize = 0; 19 | int diskNumberStart = 0; 20 | int internalFileAttributes = 0; 21 | int externalFileAttributes = 0; 22 | int localHeaderOffset = 0; 23 | String filename = ''; 24 | Uint8List? extraField; 25 | String fileComment = ''; 26 | ZipFile? file; 27 | 28 | void read(InputStream input, {InputStream? fileBytes, String? password}) { 29 | versionMadeBy = input.readUint16(); 30 | versionNeededToExtract = input.readUint16(); 31 | generalPurposeBitFlag = input.readUint16(); 32 | compressionMethod = input.readUint16(); 33 | lastModifiedFileTime = input.readUint16(); 34 | lastModifiedFileDate = input.readUint16(); 35 | crc32 = input.readUint32(); 36 | compressedSize = input.readUint32(); 37 | uncompressedSize = input.readUint32(); 38 | final fnameLen = input.readUint16(); 39 | final extraLen = input.readUint16(); 40 | final commentLen = input.readUint16(); 41 | diskNumberStart = input.readUint16(); 42 | internalFileAttributes = input.readUint16(); 43 | externalFileAttributes = input.readUint32(); 44 | localHeaderOffset = input.readUint32(); 45 | 46 | if (fnameLen > 0) { 47 | filename = input.readString(size: fnameLen); 48 | } 49 | 50 | if (extraLen > 0) { 51 | final extraBytes = input.readBytes(extraLen); 52 | extraField = extraBytes.toUint8List(); 53 | 54 | // In some zip or apk files, if the extra field is less than 4 bytes, 55 | // we ignore it for better compatibility. 56 | if (extraLen >= 4) { 57 | final extra = InputMemoryStream(extraField!); 58 | while (!extra.isEOS) { 59 | final id = extra.readUint16(); 60 | var size = extra.readUint16(); 61 | final extraBytes = extra.readBytes(size); 62 | 63 | if (id == 1) { 64 | // Zip64 extended information 65 | // The following is the layout of the zip64 extended 66 | // information "extra" block. If one of the size or 67 | // offset fields in the Local or Central directory 68 | // record is too small to hold the required data, 69 | // a Zip64 extended information record is created. 70 | // The order of the fields in the zip64 extended 71 | // information record is fixed, but the fields MUST 72 | // only appear if the corresponding Local or Central 73 | // directory record field is set to 0xFFFF or 0xFFFFFFFF. 74 | // Original 75 | // Size 8 bytes Original uncompressed file size 76 | // Compressed 77 | // Size 8 bytes Size of compressed data 78 | // Relative Header 79 | // Offset 8 bytes Offset of local header record 80 | // Disk Start 81 | // Number 4 bytes Number of the disk on which 82 | // this file starts 83 | if (size >= 8 && uncompressedSize == 0xffffffff) { 84 | uncompressedSize = extraBytes.readUint64(); 85 | size -= 8; 86 | } 87 | if (size >= 8 && compressedSize == 0xffffffff) { 88 | compressedSize = extraBytes.readUint64(); 89 | size -= 8; 90 | } 91 | if (size >= 8 && localHeaderOffset == 0xffffffff) { 92 | localHeaderOffset = extraBytes.readUint64(); 93 | size -= 8; 94 | } 95 | if (size >= 4 && diskNumberStart == 0xffff) { 96 | diskNumberStart = extraBytes.readUint32(); 97 | size -= 4; 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | if (commentLen > 0) { 105 | fileComment = input.readString(size: commentLen); 106 | } 107 | 108 | if (fileBytes != null) { 109 | fileBytes.setPosition(localHeaderOffset); 110 | file = ZipFile(this); 111 | file!.read(fileBytes, password: password); 112 | } 113 | } 114 | 115 | @override 116 | String toString() => filename; 117 | } 118 | -------------------------------------------------------------------------------- /LICENSE-other.md: -------------------------------------------------------------------------------- 1 | Some code has been derived from the following projects: 2 | 3 | zlib/inflate: 4 | JavaScript Zlib Library, https://github.com/imaya/zlib.js 5 | The MIT License 6 | Copyright (c) 2012 imaya 7 | 8 | zlib/deflate: 9 | Java JZLib Library, http://www.jcraft.com/jzlib/ 10 | Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | 1. Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in 20 | the documentation and/or other materials provided with the distribution. 21 | 22 | 3. The names of the authors may not be used to endorse or promote products 23 | derived from this software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 26 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 27 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, 28 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 31 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 34 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | bzip2: 37 | This program, "bzip2", the associated library "libbzip2", and all 38 | documentation, are copyright (C) 1996-2010 Julian R Seward. All 39 | rights reserved. 40 | 41 | Redistribution and use in source and binary forms, with or without 42 | modification, are permitted provided that the following conditions 43 | are met: 44 | 45 | 1. Redistributions of source code must retain the above copyright 46 | notice, this list of conditions and the following disclaimer. 47 | 48 | 2. The origin of this software must not be misrepresented; you must 49 | not claim that you wrote the original software. If you use this 50 | software in a product, an acknowledgment in the product 51 | documentation would be appreciated but is not required. 52 | 53 | 3. Altered source versions must be plainly marked as such, and must 54 | not be misrepresented as being the original software. 55 | 56 | 4. The name of the author may not be used to endorse or promote 57 | products derived from this software without specific prior written 58 | permission. 59 | 60 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 61 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 62 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 63 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 64 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 65 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 66 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 67 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 68 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 69 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 70 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 71 | 72 | Julian Seward, jseward@bzip.org 73 | bzip2/libbzip2 version 1.0.6 of 6 September 2010 74 | 75 | pointycastle: 76 | Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) 77 | 78 | Permission is hereby granted, free of charge, to any person obtaining a copy of 79 | this software and associated documentation files (the "Software"), to deal in 80 | the Software without restriction, including without limitation the rights to 81 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 82 | of the Software, and to permit persons to whom the Software is furnished to do 83 | so, subject to the following conditions: 84 | 85 | The above copyright notice and this permission notice shall be included in all 86 | copies or substantial portions of the Software. 87 | 88 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 89 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 90 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 91 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 92 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 93 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 94 | -------------------------------------------------------------------------------- /lib/src/codecs/zlib/_gzip_decoder_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../util/input_memory_stream.dart'; 4 | import '../../util/input_stream.dart'; 5 | import '../../util/output_memory_stream.dart'; 6 | import '../../util/output_stream.dart'; 7 | import '_zlib_decoder_base.dart'; 8 | import '_zlib_decoder_web.dart'; 9 | import 'gzip_flag.dart'; 10 | import 'inflate.dart'; 11 | 12 | const platformGZipDecoder = _GZipDecoder(); 13 | 14 | /// Decompress data with the zlib format decoder. 15 | class _GZipDecoder extends ZLibDecoderBase { 16 | const _GZipDecoder(); 17 | 18 | @override 19 | Uint8List decodeBytes(List data, 20 | {bool verify = false, bool raw = false}) { 21 | final output = OutputMemoryStream(); 22 | decodeStream(InputMemoryStream(data), output, verify: verify, raw: raw); 23 | return output.getBytes(); 24 | } 25 | 26 | @override 27 | bool decodeStream(InputStream input, OutputStream output, 28 | {bool verify = false, bool raw = false}) { 29 | while (!input.isEOS) { 30 | final startPos = input.position; 31 | if (!_readHeader(input)) { 32 | // Fall back to ZLib if there is no GZip header. This is to make it 33 | // consistent with dart's native library behavior. 34 | input.position = startPos; 35 | return platformZLibDecoder.decodeStream(input, output, 36 | verify: verify, raw: raw); 37 | } 38 | Inflate.stream(input, output: output); 39 | 40 | /*final crc =*/ input.readUint32(); 41 | /*final size =*/ input.readUint32(); 42 | 43 | output.flush(); 44 | 45 | /*if (verify && output is OutputMemoryStream) { 46 | final bytes = output.getBytes(); 47 | final computedCrc = getCrc32(bytes); 48 | if (crc != computedCrc) { 49 | break; 50 | } 51 | if (size != bytes.length) { 52 | break; 53 | } 54 | }*/ 55 | } 56 | 57 | return true; 58 | } 59 | 60 | bool _readHeader(InputStream input) { 61 | // The GZip format has the following structure: 62 | // Offset Length Contents 63 | // 0 2 bytes magic header 0x1f, 0x8b (\037 \213) 64 | // 2 1 byte compression method 65 | // 0: store (copied) 66 | // 1: compress 67 | // 2: pack 68 | // 3: lzh 69 | // 4..7: reserved 70 | // 8: deflate 71 | // 3 1 byte flags 72 | // bit 0 set: file probably ascii text 73 | // bit 1 set: continuation of multi-part gzip file, part number present 74 | // bit 2 set: extra field present 75 | // bit 3 set: original file name present 76 | // bit 4 set: file comment present 77 | // bit 5 set: file is encrypted, encryption header present 78 | // bit 6,7: reserved 79 | // 4 4 bytes file modification time in Unix format 80 | // 8 1 byte extra flags (depend on compression method) 81 | // 9 1 byte OS type 82 | // [ 83 | // 2 bytes optional part number (second part=1) 84 | // ]? 85 | // [ 86 | // 2 bytes optional extra field length (e) 87 | // (e)bytes optional extra field 88 | // ]? 89 | // [ 90 | // bytes optional original file name, zero terminated 91 | // ]? 92 | // [ 93 | // bytes optional file comment, zero terminated 94 | // ]? 95 | // [ 96 | // 12 bytes optional encryption header 97 | // ]? 98 | // bytes compressed data 99 | // 4 bytes crc32 100 | // 4 bytes uncompressed input size modulo 2^32 101 | 102 | final signature = input.readUint16(); 103 | if (signature != GZipFlag.signature) { 104 | return false; 105 | //throw ArchiveException('Invalid GZip Signature'); 106 | } 107 | 108 | final compressionMethod = input.readByte(); 109 | if (compressionMethod != GZipFlag.deflate) { 110 | return false; 111 | //throw ArchiveException('Invalid GZip Compression Method'); 112 | } 113 | 114 | final flags = input.readByte(); 115 | /*int fileModTime =*/ input.readUint32(); 116 | /*int extraFlags =*/ input.readByte(); 117 | /*int osType =*/ input.readByte(); 118 | 119 | if (flags & GZipFlag.extra != 0) { 120 | final t = input.readUint16(); 121 | input.readBytes(t); 122 | } 123 | 124 | if (flags & GZipFlag.name != 0) { 125 | input.readString(); 126 | } 127 | 128 | if (flags & GZipFlag.comment != 0) { 129 | input.readString(); 130 | } 131 | 132 | // just throw away for now 133 | if (flags & GZipFlag.hcrc != 0) { 134 | input.readUint16(); 135 | } 136 | 137 | return true; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /test/input_memory_stream_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:archive/archive.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('InputStreamMemory', () { 6 | test('empty', () { 7 | final input = InputMemoryStream.empty(); 8 | expect(input.length, equals(0)); 9 | expect(input.isEOS, equals(true)); 10 | }); 11 | 12 | test('readByte', () async { 13 | const data = [0xaa, 0xbb, 0xcc]; 14 | final input = InputMemoryStream.fromList(data); 15 | expect(input.length, equals(3)); 16 | expect(input.readByte(), equals(0xaa)); 17 | expect(input.readByte(), equals(0xbb)); 18 | expect(input.readByte(), equals(0xcc)); 19 | expect(input.isEOS, equals(true)); 20 | }); 21 | 22 | test('peakBytes', () async { 23 | const data = [0xaa, 0xbb, 0xcc]; 24 | final input = InputMemoryStream.fromList(data); 25 | expect(input.readByte(), equals(0xaa)); 26 | 27 | final bytes = input.peekBytes(2).toUint8List(); 28 | expect(bytes[0], equals(0xbb)); 29 | expect(bytes[1], equals(0xcc)); 30 | expect(input.readByte(), equals(0xbb)); 31 | expect(input.readByte(), equals(0xcc)); 32 | expect(input.isEOS, equals(true)); 33 | }); 34 | 35 | test('skip', () async { 36 | const data = [0xaa, 0xbb, 0xcc]; 37 | final input = InputMemoryStream.fromList(data); 38 | expect(input.length, equals(3)); 39 | expect(input.readByte(), equals(0xaa)); 40 | input.skip(1); 41 | expect(input.readByte(), equals(0xcc)); 42 | expect(input.isEOS, equals(true)); 43 | }); 44 | 45 | test('subset', () async { 46 | const data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; 47 | final input = InputMemoryStream.fromList(data); 48 | expect(input.length, equals(5)); 49 | expect(input.readByte(), equals(0xaa)); 50 | 51 | final i2 = input.subset(length: 3); 52 | 53 | final i3 = i2.subset(position: 1, length: 2); 54 | 55 | expect(i2.readByte(), equals(0xbb)); 56 | expect(i2.readByte(), equals(0xcc)); 57 | expect(i2.readByte(), equals(0xdd)); 58 | expect(i2.isEOS, equals(true)); 59 | 60 | expect(i3.readByte(), equals(0xcc)); 61 | expect(i3.readByte(), equals(0xdd)); 62 | }); 63 | 64 | test('readString', () async { 65 | const data = [84, 101, 115, 116, 0]; 66 | final input = InputMemoryStream.fromList(data); 67 | var s = input.readString(); 68 | expect(s, equals('Test')); 69 | expect(input.isEOS, equals(true)); 70 | 71 | input.reset(); 72 | 73 | s = input.readString(size: 4); 74 | expect(s, equals('Test')); 75 | expect(input.readByte(), equals(0)); 76 | expect(input.isEOS, equals(true)); 77 | }); 78 | 79 | test('readBytes', () async { 80 | const data = [84, 101, 115, 116, 0]; 81 | final input = InputMemoryStream.fromList(data); 82 | final b = input.readBytes(3).toUint8List(); 83 | expect(b.length, equals(3)); 84 | expect(b[0], equals(84)); 85 | expect(b[1], equals(101)); 86 | expect(b[2], equals(115)); 87 | expect(input.readByte(), equals(116)); 88 | expect(input.readByte(), equals(0)); 89 | expect(input.isEOS, equals(true)); 90 | }); 91 | 92 | test('readUint16', () async { 93 | const data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; 94 | // Little endian (by default) 95 | final input = InputMemoryStream.fromList(data); 96 | expect(input.readUint16(), equals(0xbbaa)); 97 | 98 | // Big endian 99 | final i2 = 100 | InputMemoryStream.fromList(data, byteOrder: ByteOrder.bigEndian); 101 | expect(i2.readUint16(), equals(0xaabb)); 102 | }); 103 | 104 | test('readUint24', () async { 105 | const data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; 106 | // Little endian (by default) 107 | final input = InputMemoryStream.fromList(data); 108 | expect(input.readUint24(), equals(0xccbbaa)); 109 | 110 | // Big endian 111 | final i2 = 112 | InputMemoryStream.fromList(data, byteOrder: ByteOrder.bigEndian); 113 | expect(i2.readUint24(), equals(0xaabbcc)); 114 | }); 115 | 116 | test('readUint32', () async { 117 | const data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; 118 | // Little endian (by default) 119 | final input = InputMemoryStream.fromList(data); 120 | expect(input.readUint32(), equals(0xddccbbaa)); 121 | 122 | // Big endian 123 | final i2 = 124 | InputMemoryStream.fromList(data, byteOrder: ByteOrder.bigEndian); 125 | expect(i2.readUint32(), equals(0xaabbccdd)); 126 | }); 127 | 128 | test('readUint64', () async { 129 | const data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0xee, 0xdd]; 130 | // Little endian (by default) 131 | final input = InputMemoryStream.fromList(data); 132 | expect(input.readUint64(), equals(0xddeeffeeddccbbaa)); 133 | 134 | // Big endian 135 | final i2 = 136 | InputMemoryStream.fromList(data, byteOrder: ByteOrder.bigEndian); 137 | expect(i2.readUint64(), equals(0xaabbccddeeffeedd)); 138 | }); 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /test/xz_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:archive/archive.dart'; 5 | import 'package:path/path.dart' as p; 6 | import 'package:test/test.dart'; 7 | 8 | import '_test_util.dart'; 9 | 10 | void main() { 11 | group('xz', () { 12 | test('good-1-lzma2-1.xz', () { 13 | final file = File(p.join('test/_data/xz/good-1-lzma2-1.xz')); 14 | final compressed = file.readAsBytesSync(); 15 | final data = XZDecoder().decodeBytes(compressed); 16 | final expected = File(p.join('test/_data/xz/expected/good-1-lzma2-1')) 17 | .readAsBytesSync(); 18 | 19 | expect(data.length, equals(expected.length)); 20 | for (var i = 0; i < data.length; ++i) { 21 | expect(data[i], equals(expected[i])); 22 | } 23 | }); 24 | 25 | test('decode empty', () { 26 | final file = File(p.join('test/_data/xz/empty.xz')); 27 | final compressed = file.readAsBytesSync(); 28 | final data = XZDecoder().decodeBytes(compressed); 29 | expect(data, isEmpty); 30 | }); 31 | 32 | test('decode hello', () { 33 | // hello.xz has no LZMA compression due to its simplicity. 34 | final file = File(p.join('test/_data/xz/hello.xz')); 35 | final compressed = file.readAsBytesSync(); 36 | final data = XZDecoder().decodeBytes(compressed); 37 | expect(data, equals(utf8.encode('hello\n'))); 38 | }); 39 | 40 | test('decode crc32', () { 41 | // Uses a CRC-32 checksum. 42 | final file = File(p.join('test/_data/xz/crc32.xz')); 43 | final compressed = file.readAsBytesSync(); 44 | final data = XZDecoder().decodeBytes(compressed, verify: true); 45 | expect(data, equals(utf8.encode('hello\n'))); 46 | }); 47 | 48 | test('decode crc64', () { 49 | // Uses a CRC-64 checksum. 50 | final file = File(p.join('test/_data/xz/crc64.xz')); 51 | final compressed = file.readAsBytesSync(); 52 | final data = XZDecoder().decodeBytes(compressed, verify: true); 53 | expect(data, equals(utf8.encode('hello\n'))); 54 | }); 55 | 56 | test('decode sha256', () { 57 | // Uses a SHA-256 checksum. 58 | final file = File(p.join('test/_data/xz/sha256.xz')); 59 | final compressed = file.readAsBytesSync(); 60 | final data = XZDecoder().decodeBytes(compressed, verify: true); 61 | expect(data, equals(utf8.encode('hello\n'))); 62 | }); 63 | 64 | test('decode nocheck', () { 65 | // Uses no checksum 66 | final file = File(p.join('test/_data/xz/nocheck.xz')); 67 | final compressed = file.readAsBytesSync(); 68 | final data = XZDecoder().decodeBytes(compressed, verify: true); 69 | expect(data, equals(utf8.encode('hello\n'))); 70 | }); 71 | 72 | test('decode hello repeated', () { 73 | // Simple file with a small amount of compression due to repeated data. 74 | final file = File(p.join('test/_data/xz/hello-hello-hello.xz')); 75 | final compressed = file.readAsBytesSync(); 76 | final data = XZDecoder().decodeBytes(compressed); 77 | expect(data, equals(utf8.encode('hello hello hello'))); 78 | }); 79 | 80 | test('decode cat.jpg', () { 81 | final file = File(p.join('test/_data/xz/cat.jpg.xz')); 82 | final compressed = file.readAsBytesSync(); 83 | final b = File(p.join('test/_data/cat.jpg')); 84 | final bBytes = b.readAsBytesSync(); 85 | final data = XZDecoder().decodeBytes(compressed); 86 | compareBytes(data, bBytes); 87 | }); 88 | 89 | test('encode empty', () { 90 | final file = File(p.join('test/_data/xz/empty.xz')); 91 | final expected = file.readAsBytesSync(); 92 | final data = XZEncoder().encodeBytes([]); 93 | compareBytes(data, expected); 94 | }); 95 | 96 | test('encode hello', () { 97 | // hello.xz has no LZMA compression due to its simplicity. 98 | final file = File(p.join('test/_data/xz/hello.xz')); 99 | final expected = file.readAsBytesSync(); 100 | final data = XZEncoder().encodeBytes(utf8.encode('hello\n')); 101 | compareBytes(data, expected); 102 | }); 103 | 104 | test('encode crc32', () { 105 | // Uses a CRC-32 checksum. 106 | final file = File(p.join('test/_data/xz/crc32.xz')); 107 | final expected = file.readAsBytesSync(); 108 | final data = 109 | XZEncoder().encodeBytes(utf8.encode('hello\n'), check: XZCheck.crc32); 110 | compareBytes(data, expected); 111 | }); 112 | 113 | test('encode crc64', () { 114 | // Uses a CRC-64 checksum. 115 | final file = File(p.join('test/_data/xz/crc64.xz')); 116 | final expected = file.readAsBytesSync(); 117 | final data = 118 | XZEncoder().encodeBytes(utf8.encode('hello\n'), check: XZCheck.crc64); 119 | compareBytes(data, expected); 120 | }); 121 | 122 | test('encode sha256', () { 123 | // Uses a SHA-256 checksum. 124 | final file = File(p.join('test/_data/xz/sha256.xz')); 125 | final expected = file.readAsBytesSync(); 126 | final data = XZEncoder() 127 | .encodeBytes(utf8.encode('hello\n'), check: XZCheck.sha256); 128 | compareBytes(data, expected); 129 | }); 130 | 131 | test('encode nocheck', () { 132 | // Uses no checksum 133 | final file = File(p.join('test/_data/xz/nocheck.xz')); 134 | final expected = file.readAsBytesSync(); 135 | final data = 136 | XZEncoder().encodeBytes(utf8.encode('hello\n'), check: XZCheck.none); 137 | compareBytes(data, expected); 138 | }); 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /lib/src/util/input_stream.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:typed_data'; 4 | 5 | import 'byte_order.dart'; 6 | 7 | abstract class InputStream { 8 | /// The current endian order if the stream. 9 | ByteOrder byteOrder; 10 | 11 | /// The current read position relative to the start of the buffer. 12 | int get position; 13 | 14 | /// Set the current read position relative to the start of the buffer. 15 | set position(int v); 16 | 17 | /// How many bytes are left in the stream. 18 | int get length; 19 | 20 | /// Is the current position at the end of the stream? 21 | bool get isEOS; 22 | 23 | InputStream({required this.byteOrder}); 24 | 25 | bool open(); 26 | 27 | /// Asynchronously closes the input stream. 28 | Future close(); 29 | 30 | /// Synchronously closes the input stream. 31 | void closeSync(); 32 | 33 | /// Reset to the beginning of the stream. 34 | void reset(); 35 | 36 | void setPosition(int v); 37 | 38 | /// Rewind the read head of the stream by the given number of bytes. 39 | void rewind([int length = 1]); 40 | 41 | /// Move the read position by [length] bytes. 42 | void skip(int length); 43 | 44 | /// Read [count] bytes from an [offset] of the current read position, without 45 | /// moving the read position. 46 | InputStream peekBytes(int count, {int offset = 0}) => 47 | subset(position: position + offset, length: count); 48 | 49 | /// Return a [InputStream] to read a subset of this stream. It does not 50 | /// move the read position of this stream. [position] is specified relative 51 | /// to the start of the buffer. If [position] is not specified, the current 52 | /// read position is used. If [length] is not specified, the remainder of this 53 | /// stream is used. 54 | /// If [bufferSize] is provided, and this is an [InputFileStream], the 55 | /// returned [InputStream] will get its own [FileBuffer] with the given 56 | /// [bufferSize], otherwise it will share the [FileBuffer] of this 57 | /// [InputFileStream]. 58 | InputStream subset({int? position, int? length, int? bufferSize}); 59 | 60 | /// Read a single byte. 61 | int readByte(); 62 | 63 | /// Read a single byte. 64 | int readUint8() => readByte(); 65 | 66 | /// Read a 16-bit word from the stream. 67 | int readUint16() { 68 | final b1 = readByte(); 69 | final b2 = readByte(); 70 | if (byteOrder == ByteOrder.bigEndian) { 71 | return (b1 << 8) | b2; 72 | } 73 | return (b2 << 8) | b1; 74 | } 75 | 76 | /// Read a 24-bit word from the stream. 77 | int readUint24() { 78 | final b1 = readByte(); 79 | final b2 = readByte(); 80 | final b3 = readByte(); 81 | if (byteOrder == ByteOrder.bigEndian) { 82 | return b3 | (b2 << 8) | (b1 << 16); 83 | } 84 | return b1 | (b2 << 8) | (b3 << 16); 85 | } 86 | 87 | /// Read a 32-bit word from the stream. 88 | int readUint32() { 89 | final b1 = readByte(); 90 | final b2 = readByte(); 91 | final b3 = readByte(); 92 | final b4 = readByte(); 93 | if (byteOrder == ByteOrder.bigEndian) { 94 | return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; 95 | } 96 | return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; 97 | } 98 | 99 | /// Read a 64-bit word form the stream. 100 | int readUint64() { 101 | final b1 = readByte(); 102 | final b2 = readByte(); 103 | final b3 = readByte(); 104 | final b4 = readByte(); 105 | final b5 = readByte(); 106 | final b6 = readByte(); 107 | final b7 = readByte(); 108 | final b8 = readByte(); 109 | if (byteOrder == ByteOrder.bigEndian) { 110 | return (b1 << 56) | 111 | (b2 << 48) | 112 | (b3 << 40) | 113 | (b4 << 32) | 114 | (b5 << 24) | 115 | (b6 << 16) | 116 | (b7 << 8) | 117 | b8; 118 | } 119 | return (b8 << 56) | 120 | (b7 << 48) | 121 | (b6 << 40) | 122 | (b5 << 32) | 123 | (b4 << 24) | 124 | (b3 << 16) | 125 | (b2 << 8) | 126 | b1; 127 | } 128 | 129 | /// Read [count] bytes from the stream. 130 | InputStream readBytes(int count) { 131 | final bytes = subset(position: position, length: count); 132 | setPosition(position + bytes.length); 133 | return bytes; 134 | } 135 | 136 | /// Read a null-terminated string, or if [size] is provided, that number of 137 | /// bytes returned as a string. 138 | String readString({int? size, bool utf8 = true}) { 139 | String codesToString(List codes) { 140 | try { 141 | final str = utf8 142 | ? const Utf8Decoder().convert(codes) 143 | : String.fromCharCodes(codes); 144 | return str; 145 | } catch (err) { 146 | // If the string is not a valid UTF8 string, decode it as character 147 | // codes. 148 | return String.fromCharCodes(codes); 149 | } 150 | } 151 | 152 | if (size == null) { 153 | final codes = []; 154 | if (isEOS) { 155 | return ''; 156 | } 157 | while (!isEOS) { 158 | final c = readByte(); 159 | if (c == 0) { 160 | return codesToString(codes); 161 | } 162 | codes.add(c); 163 | } 164 | return codesToString(codes); 165 | } 166 | 167 | final s = readBytes(size); 168 | final codes = s.toUint8List(); 169 | return codesToString(codes); 170 | } 171 | 172 | /// Convert the remaining bytes to a Uint8List. 173 | Uint8List toUint8List(); 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # archive 2 | [![Dart CI](https://github.com/brendan-duncan/archive/actions/workflows/build.yaml/badge.svg)](https://github.com/brendan-duncan/archive/actions/workflows/build.yaml) 3 | [![pub package](https://img.shields.io/pub/v/archive.svg)](https://pub.dev/packages/archive) 4 | 5 | ## 4.0 Update 6 | 7 | The Archive library was originally written when the web was the primary use of Dart. File IO was less of a concern 8 | and the design was around having everything in memory. As other uses of Dart came about, such as Flutter, a lot 9 | of File IO operations were added to the library, but not in a very clean way. 10 | 11 | The design goal for the 4.0 revision of the library is to ensure File IO is a primary focus, while minimizing memory 12 | usage. Memory-only interfaces are still available for web platforms. 13 | 14 | #### [Migrating 3.x to 4.x](doc/migrating_3_to_4.md). 15 | 16 | ### Migration quick tips: 17 | * **decodeBuffer** has been renamed to **decodeStream** in the various decoder classes. 18 | * **InputStream** has been renamed to **InputMemoryStream**. 19 | * **OutputStream** has been renamed to **OutputMemoryStream**. 20 | 21 | --- 22 | 23 | ## Overview 24 | 25 | A Dart library to encode and decode various archive and compression formats. 26 | 27 | The archive library currently supports the following codecs: 28 | 29 | - Zip 30 | - Tar 31 | - ZLib 32 | - GZip 33 | - BZip2 34 | - XZ 35 | 36 | --- 37 | 38 | ## Usage 39 | 40 | **package:archive/archive.dart** 41 | * Can be used for both web and native applications. 42 | 43 | **package:archive/archive_io.dart** 44 | * Provides some extra utilities for 'dart:io' based applications. 45 | 46 | 47 | #### Decoding a zip file in memory 48 | 49 | ```dart 50 | import 'package:archive/archive.dart'; 51 | import 'dart:io'; 52 | void main() { 53 | final bytes = File('test.zip').readAsBytesSync(); 54 | final archive = ZipDecoder().decodeBytes(bytes); 55 | for (final entry in archive) { 56 | if (entry.isFile) { 57 | final fileBytes = file.readBytes(); 58 | File('out/${file.fullPathName}') 59 | ..createSync(recursive: true) 60 | ..writeAsBytesSync(fileBytes); 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | #### Using InputFileStream and OutputFileStream to extract a zip: 67 | ```dart 68 | import 'dart:io'; 69 | import 'package:archive/archive.dart'; 70 | void main() { 71 | // Use an InputFileStream to access the zip file without storing it in memory. 72 | // Note that using InputFileStream will result in an error from the web platform 73 | // as there is no file system there. 74 | final inputStream = InputFileStream('test.zip'); 75 | // Decode the zip from the InputFileStream. The archive will have the contents of the 76 | // zip, without having stored the data in memory. 77 | final archive = ZipDecoder().decodeStream(inputStream); 78 | final symbolicLinks = []; // keep a list of the symbolic link entities, if any. 79 | // For all of the entries in the archive 80 | for (final file in archive) { 81 | // You should create symbolic links **after** the rest of the archive has been 82 | // extracted, otherwise the file being linked might not exist yet. 83 | if (file.isSymbolicLink) { 84 | symbolicLinks.add(file); 85 | continue; 86 | } 87 | if (file.isFile) { 88 | // Write the file content to a directory called 'out'. 89 | // In practice, you should make sure file.name doesn't include '..' paths 90 | // that would put it outside of the extraction directory. 91 | // An OutputFileStream will write the data to disk. 92 | final outputStream = OutputFileStream('out/${file.name}'); 93 | // The writeContent method will decompress the file content directly to disk without 94 | // storing the decompressed data in memory. 95 | entity.writeContent(outputStream); 96 | // Make sure to close the output stream so the File is closed. 97 | outputStream.closeSync(); 98 | } else { 99 | // If the entity is a directory, create it. Normally writing a file will create 100 | // the directories necessary, but sometimes an archive will have an empty directory 101 | // with no files. 102 | Directory('out/${file.name}').createSync(recursive: true); 103 | } 104 | } 105 | // Create symbolic links **after** the rest of the archive has been extracted to make sure 106 | // the file being linked exists. 107 | for (final entity in symbolicLinks) { 108 | // Before using this in production code, you should ensure the symbolicLink path 109 | // points to a file within the archive, otherwise it could be a security issue. 110 | final link = Link('out/${entity.fullPathName}'); 111 | link.createSync(entity.symbolicLink!, recursive: true); 112 | } 113 | } 114 | ``` 115 | #### extractFileToDisk 116 | `extractFileToDisk` is a convenience function to extract the contents of 117 | an archive file directory to an output directory. 118 | The type of archive it is will be determined by the file extension. 119 | ```dart 120 | import 'package:archive/archive_io.dart'; 121 | // ... 122 | extractFileToDisk('test.zip', 'out'); 123 | ``` 124 | #### extractArchiveToDisk 125 | `extractArchiveToDisk` is a convenience function to write the contents of an Archive 126 | to an output directory. 127 | ```dart 128 | import 'package:archive/archive_io.dart'; 129 | // ... 130 | // Use an InputFileStream to access the zip file without storing it in memory. 131 | final inputStream = InputFileStream('test.zip'); 132 | // Decode the zip from the InputFileStream. The archive will have the contents of the 133 | // zip, without having stored the data in memory. 134 | final archive = ZipDecoder().decodeStream(inputStream); 135 | extractArchiveToDisk(archive, 'out'); 136 | ``` 137 | -------------------------------------------------------------------------------- /lib/src/codecs/bzip2/bzip2.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// Internal class used by [BZip2Decoder] 4 | class BZip2 { 5 | static final emptyUint8List = Uint8List(0); 6 | static final emptyUint32List = Uint32List(0); 7 | static final emptyInt32List = Int32List(0); 8 | 9 | static int initialCrc = 0xffffffff; 10 | 11 | static int updateCrc(int value, int crc) { 12 | return ((crc << 8) ^ _bz2Crc32Table[(crc >> 24) & 0xff ^ (value & 0xff)]) & 13 | 0xffffffff; 14 | } 15 | 16 | static int finalizeCrc(int crc) { 17 | return crc ^ 0xffffffff; 18 | } 19 | 20 | static const List bzhSignature = [0x42, 0x5a, 0x68]; 21 | 22 | static const hdr0 = 0x30; 23 | 24 | static const List compressedMagic = [0x31, 0x41, 0x59, 0x26, 0x53, 0x59]; 25 | 26 | static const List eosMagic = [0x17, 0x72, 0x45, 0x38, 0x50, 0x90]; 27 | 28 | static const List _bz2Crc32Table = [ 29 | 0x00000000, 30 | 0x04c11db7, 31 | 0x09823b6e, 32 | 0x0d4326d9, 33 | 0x130476dc, 34 | 0x17c56b6b, 35 | 0x1a864db2, 36 | 0x1e475005, 37 | 0x2608edb8, 38 | 0x22c9f00f, 39 | 0x2f8ad6d6, 40 | 0x2b4bcb61, 41 | 0x350c9b64, 42 | 0x31cd86d3, 43 | 0x3c8ea00a, 44 | 0x384fbdbd, 45 | 0x4c11db70, 46 | 0x48d0c6c7, 47 | 0x4593e01e, 48 | 0x4152fda9, 49 | 0x5f15adac, 50 | 0x5bd4b01b, 51 | 0x569796c2, 52 | 0x52568b75, 53 | 0x6a1936c8, 54 | 0x6ed82b7f, 55 | 0x639b0da6, 56 | 0x675a1011, 57 | 0x791d4014, 58 | 0x7ddc5da3, 59 | 0x709f7b7a, 60 | 0x745e66cd, 61 | 0x9823b6e0, 62 | 0x9ce2ab57, 63 | 0x91a18d8e, 64 | 0x95609039, 65 | 0x8b27c03c, 66 | 0x8fe6dd8b, 67 | 0x82a5fb52, 68 | 0x8664e6e5, 69 | 0xbe2b5b58, 70 | 0xbaea46ef, 71 | 0xb7a96036, 72 | 0xb3687d81, 73 | 0xad2f2d84, 74 | 0xa9ee3033, 75 | 0xa4ad16ea, 76 | 0xa06c0b5d, 77 | 0xd4326d90, 78 | 0xd0f37027, 79 | 0xddb056fe, 80 | 0xd9714b49, 81 | 0xc7361b4c, 82 | 0xc3f706fb, 83 | 0xceb42022, 84 | 0xca753d95, 85 | 0xf23a8028, 86 | 0xf6fb9d9f, 87 | 0xfbb8bb46, 88 | 0xff79a6f1, 89 | 0xe13ef6f4, 90 | 0xe5ffeb43, 91 | 0xe8bccd9a, 92 | 0xec7dd02d, 93 | 0x34867077, 94 | 0x30476dc0, 95 | 0x3d044b19, 96 | 0x39c556ae, 97 | 0x278206ab, 98 | 0x23431b1c, 99 | 0x2e003dc5, 100 | 0x2ac12072, 101 | 0x128e9dcf, 102 | 0x164f8078, 103 | 0x1b0ca6a1, 104 | 0x1fcdbb16, 105 | 0x018aeb13, 106 | 0x054bf6a4, 107 | 0x0808d07d, 108 | 0x0cc9cdca, 109 | 0x7897ab07, 110 | 0x7c56b6b0, 111 | 0x71159069, 112 | 0x75d48dde, 113 | 0x6b93dddb, 114 | 0x6f52c06c, 115 | 0x6211e6b5, 116 | 0x66d0fb02, 117 | 0x5e9f46bf, 118 | 0x5a5e5b08, 119 | 0x571d7dd1, 120 | 0x53dc6066, 121 | 0x4d9b3063, 122 | 0x495a2dd4, 123 | 0x44190b0d, 124 | 0x40d816ba, 125 | 0xaca5c697, 126 | 0xa864db20, 127 | 0xa527fdf9, 128 | 0xa1e6e04e, 129 | 0xbfa1b04b, 130 | 0xbb60adfc, 131 | 0xb6238b25, 132 | 0xb2e29692, 133 | 0x8aad2b2f, 134 | 0x8e6c3698, 135 | 0x832f1041, 136 | 0x87ee0df6, 137 | 0x99a95df3, 138 | 0x9d684044, 139 | 0x902b669d, 140 | 0x94ea7b2a, 141 | 0xe0b41de7, 142 | 0xe4750050, 143 | 0xe9362689, 144 | 0xedf73b3e, 145 | 0xf3b06b3b, 146 | 0xf771768c, 147 | 0xfa325055, 148 | 0xfef34de2, 149 | 0xc6bcf05f, 150 | 0xc27dede8, 151 | 0xcf3ecb31, 152 | 0xcbffd686, 153 | 0xd5b88683, 154 | 0xd1799b34, 155 | 0xdc3abded, 156 | 0xd8fba05a, 157 | 0x690ce0ee, 158 | 0x6dcdfd59, 159 | 0x608edb80, 160 | 0x644fc637, 161 | 0x7a089632, 162 | 0x7ec98b85, 163 | 0x738aad5c, 164 | 0x774bb0eb, 165 | 0x4f040d56, 166 | 0x4bc510e1, 167 | 0x46863638, 168 | 0x42472b8f, 169 | 0x5c007b8a, 170 | 0x58c1663d, 171 | 0x558240e4, 172 | 0x51435d53, 173 | 0x251d3b9e, 174 | 0x21dc2629, 175 | 0x2c9f00f0, 176 | 0x285e1d47, 177 | 0x36194d42, 178 | 0x32d850f5, 179 | 0x3f9b762c, 180 | 0x3b5a6b9b, 181 | 0x0315d626, 182 | 0x07d4cb91, 183 | 0x0a97ed48, 184 | 0x0e56f0ff, 185 | 0x1011a0fa, 186 | 0x14d0bd4d, 187 | 0x19939b94, 188 | 0x1d528623, 189 | 0xf12f560e, 190 | 0xf5ee4bb9, 191 | 0xf8ad6d60, 192 | 0xfc6c70d7, 193 | 0xe22b20d2, 194 | 0xe6ea3d65, 195 | 0xeba91bbc, 196 | 0xef68060b, 197 | 0xd727bbb6, 198 | 0xd3e6a601, 199 | 0xdea580d8, 200 | 0xda649d6f, 201 | 0xc423cd6a, 202 | 0xc0e2d0dd, 203 | 0xcda1f604, 204 | 0xc960ebb3, 205 | 0xbd3e8d7e, 206 | 0xb9ff90c9, 207 | 0xb4bcb610, 208 | 0xb07daba7, 209 | 0xae3afba2, 210 | 0xaafbe615, 211 | 0xa7b8c0cc, 212 | 0xa379dd7b, 213 | 0x9b3660c6, 214 | 0x9ff77d71, 215 | 0x92b45ba8, 216 | 0x9675461f, 217 | 0x8832161a, 218 | 0x8cf30bad, 219 | 0x81b02d74, 220 | 0x857130c3, 221 | 0x5d8a9099, 222 | 0x594b8d2e, 223 | 0x5408abf7, 224 | 0x50c9b640, 225 | 0x4e8ee645, 226 | 0x4a4ffbf2, 227 | 0x470cdd2b, 228 | 0x43cdc09c, 229 | 0x7b827d21, 230 | 0x7f436096, 231 | 0x7200464f, 232 | 0x76c15bf8, 233 | 0x68860bfd, 234 | 0x6c47164a, 235 | 0x61043093, 236 | 0x65c52d24, 237 | 0x119b4be9, 238 | 0x155a565e, 239 | 0x18197087, 240 | 0x1cd86d30, 241 | 0x029f3d35, 242 | 0x065e2082, 243 | 0x0b1d065b, 244 | 0x0fdc1bec, 245 | 0x3793a651, 246 | 0x3352bbe6, 247 | 0x3e119d3f, 248 | 0x3ad08088, 249 | 0x2497d08d, 250 | 0x2056cd3a, 251 | 0x2d15ebe3, 252 | 0x29d4f654, 253 | 0xc5a92679, 254 | 0xc1683bce, 255 | 0xcc2b1d17, 256 | 0xc8ea00a0, 257 | 0xd6ad50a5, 258 | 0xd26c4d12, 259 | 0xdf2f6bcb, 260 | 0xdbee767c, 261 | 0xe3a1cbc1, 262 | 0xe760d676, 263 | 0xea23f0af, 264 | 0xeee2ed18, 265 | 0xf0a5bd1d, 266 | 0xf464a0aa, 267 | 0xf9278673, 268 | 0xfde69bc4, 269 | 0x89b8fd09, 270 | 0x8d79e0be, 271 | 0x803ac667, 272 | 0x84fbdbd0, 273 | 0x9abc8bd5, 274 | 0x9e7d9662, 275 | 0x933eb0bb, 276 | 0x97ffad0c, 277 | 0xafb010b1, 278 | 0xab710d06, 279 | 0xa6322bdf, 280 | 0xa2f33668, 281 | 0xbcb4666d, 282 | 0xb8757bda, 283 | 0xb5365d03, 284 | 0xb1f740b4 285 | ]; 286 | } 287 | -------------------------------------------------------------------------------- /lib/src/util/crc32.dart: -------------------------------------------------------------------------------- 1 | /// Get the CRC-32 checksum of the given int. 2 | int getCrc32Byte(int crc, int b) => _crc32Table[(crc ^ b) & 0xff] ^ (crc >> 8); 3 | 4 | /// Get the CRC-32 checksum of the given array. You can append bytes to an 5 | /// already computed crc by specifying the previous [crc] value. 6 | int getCrc32(List array, [int crc = 0]) { 7 | var len = array.length; 8 | crc = crc ^ 0xffffffff; 9 | var ip = 0; 10 | while (len >= 8) { 11 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 12 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 13 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 14 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 15 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 16 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 17 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 18 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 19 | len -= 8; 20 | } 21 | if (len > 0) { 22 | do { 23 | crc = _crc32Table[(crc ^ array[ip++]) & 0xff] ^ (crc >> 8); 24 | } while (--len > 0); 25 | } 26 | return crc ^ 0xffffffff; 27 | } 28 | 29 | // Precomputed CRC table for faster calculations. 30 | const _crc32Table = [ 31 | 0, 32 | 1996959894, 33 | 3993919788, 34 | 2567524794, 35 | 124634137, 36 | 1886057615, 37 | 3915621685, 38 | 2657392035, 39 | 249268274, 40 | 2044508324, 41 | 3772115230, 42 | 2547177864, 43 | 162941995, 44 | 2125561021, 45 | 3887607047, 46 | 2428444049, 47 | 498536548, 48 | 1789927666, 49 | 4089016648, 50 | 2227061214, 51 | 450548861, 52 | 1843258603, 53 | 4107580753, 54 | 2211677639, 55 | 325883990, 56 | 1684777152, 57 | 4251122042, 58 | 2321926636, 59 | 335633487, 60 | 1661365465, 61 | 4195302755, 62 | 2366115317, 63 | 997073096, 64 | 1281953886, 65 | 3579855332, 66 | 2724688242, 67 | 1006888145, 68 | 1258607687, 69 | 3524101629, 70 | 2768942443, 71 | 901097722, 72 | 1119000684, 73 | 3686517206, 74 | 2898065728, 75 | 853044451, 76 | 1172266101, 77 | 3705015759, 78 | 2882616665, 79 | 651767980, 80 | 1373503546, 81 | 3369554304, 82 | 3218104598, 83 | 565507253, 84 | 1454621731, 85 | 3485111705, 86 | 3099436303, 87 | 671266974, 88 | 1594198024, 89 | 3322730930, 90 | 2970347812, 91 | 795835527, 92 | 1483230225, 93 | 3244367275, 94 | 3060149565, 95 | 1994146192, 96 | 31158534, 97 | 2563907772, 98 | 4023717930, 99 | 1907459465, 100 | 112637215, 101 | 2680153253, 102 | 3904427059, 103 | 2013776290, 104 | 251722036, 105 | 2517215374, 106 | 3775830040, 107 | 2137656763, 108 | 141376813, 109 | 2439277719, 110 | 3865271297, 111 | 1802195444, 112 | 476864866, 113 | 2238001368, 114 | 4066508878, 115 | 1812370925, 116 | 453092731, 117 | 2181625025, 118 | 4111451223, 119 | 1706088902, 120 | 314042704, 121 | 2344532202, 122 | 4240017532, 123 | 1658658271, 124 | 366619977, 125 | 2362670323, 126 | 4224994405, 127 | 1303535960, 128 | 984961486, 129 | 2747007092, 130 | 3569037538, 131 | 1256170817, 132 | 1037604311, 133 | 2765210733, 134 | 3554079995, 135 | 1131014506, 136 | 879679996, 137 | 2909243462, 138 | 3663771856, 139 | 1141124467, 140 | 855842277, 141 | 2852801631, 142 | 3708648649, 143 | 1342533948, 144 | 654459306, 145 | 3188396048, 146 | 3373015174, 147 | 1466479909, 148 | 544179635, 149 | 3110523913, 150 | 3462522015, 151 | 1591671054, 152 | 702138776, 153 | 2966460450, 154 | 3352799412, 155 | 1504918807, 156 | 783551873, 157 | 3082640443, 158 | 3233442989, 159 | 3988292384, 160 | 2596254646, 161 | 62317068, 162 | 1957810842, 163 | 3939845945, 164 | 2647816111, 165 | 81470997, 166 | 1943803523, 167 | 3814918930, 168 | 2489596804, 169 | 225274430, 170 | 2053790376, 171 | 3826175755, 172 | 2466906013, 173 | 167816743, 174 | 2097651377, 175 | 4027552580, 176 | 2265490386, 177 | 503444072, 178 | 1762050814, 179 | 4150417245, 180 | 2154129355, 181 | 426522225, 182 | 1852507879, 183 | 4275313526, 184 | 2312317920, 185 | 282753626, 186 | 1742555852, 187 | 4189708143, 188 | 2394877945, 189 | 397917763, 190 | 1622183637, 191 | 3604390888, 192 | 2714866558, 193 | 953729732, 194 | 1340076626, 195 | 3518719985, 196 | 2797360999, 197 | 1068828381, 198 | 1219638859, 199 | 3624741850, 200 | 2936675148, 201 | 906185462, 202 | 1090812512, 203 | 3747672003, 204 | 2825379669, 205 | 829329135, 206 | 1181335161, 207 | 3412177804, 208 | 3160834842, 209 | 628085408, 210 | 1382605366, 211 | 3423369109, 212 | 3138078467, 213 | 570562233, 214 | 1426400815, 215 | 3317316542, 216 | 2998733608, 217 | 733239954, 218 | 1555261956, 219 | 3268935591, 220 | 3050360625, 221 | 752459403, 222 | 1541320221, 223 | 2607071920, 224 | 3965973030, 225 | 1969922972, 226 | 40735498, 227 | 2617837225, 228 | 3943577151, 229 | 1913087877, 230 | 83908371, 231 | 2512341634, 232 | 3803740692, 233 | 2075208622, 234 | 213261112, 235 | 2463272603, 236 | 3855990285, 237 | 2094854071, 238 | 198958881, 239 | 2262029012, 240 | 4057260610, 241 | 1759359992, 242 | 534414190, 243 | 2176718541, 244 | 4139329115, 245 | 1873836001, 246 | 414664567, 247 | 2282248934, 248 | 4279200368, 249 | 1711684554, 250 | 285281116, 251 | 2405801727, 252 | 4167216745, 253 | 1634467795, 254 | 376229701, 255 | 2685067896, 256 | 3608007406, 257 | 1308918612, 258 | 956543938, 259 | 2808555105, 260 | 3495958263, 261 | 1231636301, 262 | 1047427035, 263 | 2932959818, 264 | 3654703836, 265 | 1088359270, 266 | 936918000, 267 | 2847714899, 268 | 3736837829, 269 | 1202900863, 270 | 817233897, 271 | 3183342108, 272 | 3401237130, 273 | 1404277552, 274 | 615818150, 275 | 3134207493, 276 | 3453421203, 277 | 1423857449, 278 | 601450431, 279 | 3009837614, 280 | 3294710456, 281 | 1567103746, 282 | 711928724, 283 | 3020668471, 284 | 3272380065, 285 | 1510334235, 286 | 755167117 287 | ]; 288 | -------------------------------------------------------------------------------- /lib/src/util/input_file_stream.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'abstract_file_handle.dart'; 4 | import 'byte_order.dart'; 5 | import 'file_buffer.dart'; 6 | import 'file_handle.dart'; 7 | import 'input_stream.dart'; 8 | import 'ram_file_handle.dart'; 9 | 10 | /// Stream in data from a file. 11 | class InputFileStream extends InputStream { 12 | final FileBuffer _file; 13 | final int _fileOffset; 14 | late int _fileSize; 15 | int _position; 16 | 17 | /// Create an [InputFileStream] with the given [FileBuffer]. 18 | /// [byteOrder] determines if multi-byte values are read in bigEndian or 19 | /// littleEndian order. 20 | InputFileStream.withFileBuffer(this._file, 21 | {super.byteOrder = ByteOrder.littleEndian}) 22 | : _fileOffset = 0, 23 | _position = 0 { 24 | _fileSize = _file.length; 25 | } 26 | 27 | /// Create an [InputFileStream] with the given [AbstractFileHandle]. 28 | /// [byteOrder] determines if multi-byte values are read in bigEndian or 29 | /// littleEndian order. 30 | InputFileStream.withFileHandle(AbstractFileHandle fh, 31 | {super.byteOrder = ByteOrder.littleEndian}) 32 | : _file = FileBuffer(fh), 33 | _fileOffset = 0, 34 | _position = 0 { 35 | _fileSize = _file.length; 36 | } 37 | 38 | /// Create an [InputFileStream] with the given file system [path\. 39 | /// A file handle will be created to read from the file at that [path]. 40 | /// [byteOrder] determines if multi-byte values are read in bigEndian or 41 | /// littleEndian order. 42 | /// [bufferSize] determines the size of the cache used by the created 43 | /// [FileBuffer]. 44 | factory InputFileStream( 45 | String path, { 46 | ByteOrder byteOrder = ByteOrder.littleEndian, 47 | int bufferSize = FileBuffer.kDefaultBufferSize, 48 | }) { 49 | return InputFileStream.withFileBuffer( 50 | FileBuffer(FileHandle(path), bufferSize: bufferSize), 51 | byteOrder: byteOrder); 52 | } 53 | 54 | static Future asRamFile( 55 | Stream stream, int fileLength, 56 | {ByteOrder byteOrder = ByteOrder.littleEndian}) async { 57 | return InputFileStream.withFileBuffer( 58 | FileBuffer(await RamFileHandle.fromStream(stream, fileLength)), 59 | byteOrder: byteOrder); 60 | } 61 | 62 | /// Create an [InputFileStream] from another [InputFileStream]. 63 | /// If [position] is provided, it is the offset into [other] to start reading, 64 | /// relative to the current position of [other]. Otherwise the current 65 | /// position of [other] is used. 66 | /// If [length] is provided, it sets the length of this [InputFileStream], 67 | /// otherwise the remaining bytes in [other] is used. 68 | /// [bufferSize] determines the size of the cache used by the created 69 | /// [FileBuffer]. 70 | InputFileStream.fromFileStream(InputFileStream other, 71 | {int? position, int? length, int? bufferSize}) 72 | : _file = bufferSize != null 73 | ? new FileBuffer.from(other._file, bufferSize: bufferSize) 74 | : other._file, 75 | _fileOffset = other._fileOffset + (position ?? 0), 76 | _fileSize = length ?? other._fileSize, 77 | _position = 0, 78 | super(byteOrder: other.byteOrder); 79 | 80 | @override 81 | bool open() => _file.open(); 82 | 83 | @override 84 | Future close() async { 85 | await _file.close(); 86 | _position = 0; 87 | _fileSize = 0; 88 | } 89 | 90 | @override 91 | void closeSync() { 92 | _file.closeSync(); 93 | _position = 0; 94 | _fileSize = 0; 95 | } 96 | 97 | @override 98 | int get length => fileRemaining; 99 | 100 | @override 101 | int get position => _position; 102 | 103 | @override 104 | set position(int v) => setPosition(v); 105 | 106 | @override 107 | void setPosition(int v) { 108 | if (v < _position) { 109 | rewind(_position - v); 110 | } else if (v > _position) { 111 | skip(v - _position); 112 | } 113 | } 114 | 115 | @override 116 | bool get isEOS => _position >= _fileSize; 117 | 118 | int get fileRemaining => _fileSize - _position; 119 | 120 | @override 121 | void reset() { 122 | _position = 0; 123 | } 124 | 125 | @override 126 | void skip(int length) { 127 | _position += length; 128 | } 129 | 130 | @override 131 | void rewind([int length = 1]) { 132 | _position -= length; 133 | if (_position < 0) { 134 | _position = 0; 135 | } 136 | } 137 | 138 | @override 139 | InputStream subset({int? position, int? length, int? bufferSize}) { 140 | return InputFileStream.fromFileStream(this, 141 | position: position, length: length, bufferSize: bufferSize); 142 | } 143 | 144 | @override 145 | int readByte() { 146 | if (isEOS) { 147 | return 0; 148 | } 149 | final b = _file.readUint8(_fileOffset + _position, _fileSize); 150 | _position++; 151 | return b; 152 | } 153 | 154 | /// Read a 16-bit word from the stream. 155 | @override 156 | int readUint16() { 157 | if (isEOS) { 158 | return 0; 159 | } 160 | final b = _file.readUint16(_fileOffset + _position, _fileSize); 161 | _position += 2; 162 | return b; 163 | } 164 | 165 | /// Read a 24-bit word from the stream. 166 | @override 167 | int readUint24() { 168 | if (isEOS) { 169 | return 0; 170 | } 171 | final b = _file.readUint24(_fileOffset + _position, _fileSize); 172 | _position += 3; 173 | return b; 174 | } 175 | 176 | /// Read a 32-bit word from the stream. 177 | @override 178 | int readUint32() { 179 | if (isEOS) { 180 | return 0; 181 | } 182 | final b = _file.readUint32(_fileOffset + _position, _fileSize); 183 | _position += 4; 184 | return b; 185 | } 186 | 187 | /// Read a 64-bit word form the stream. 188 | @override 189 | int readUint64() { 190 | if (isEOS) { 191 | return 0; 192 | } 193 | final b = _file.readUint64(_fileOffset + _position, _fileSize); 194 | _position += 8; 195 | return b; 196 | } 197 | 198 | @override 199 | InputStream readBytes(int count) { 200 | if (isEOS) { 201 | return InputFileStream.fromFileStream(this, length: 0); 202 | } 203 | if ((_position + count) > _fileSize) { 204 | count = _fileSize - _position; 205 | } 206 | final bytes = InputFileStream.fromFileStream(this, 207 | position: _position, length: count); 208 | _position += bytes.length; 209 | return bytes; 210 | } 211 | 212 | @override 213 | Uint8List toUint8List([Uint8List? bytes]) { 214 | if (isEOS) { 215 | return Uint8List(0); 216 | } 217 | return _file.readBytes(_fileOffset + position, fileRemaining, _fileSize); 218 | } 219 | 220 | FileBuffer get file => _file; 221 | } 222 | --------------------------------------------------------------------------------