// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. part of "dart:io"; /// Exposes ZLib options for input parameters. /// /// See http://www.zlib.net/manual.html for more documentation. abstract final class ZLibOption { /// Minimal value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits] /// and [ZLibDecoder.windowBits]. static const int minWindowBits = 8; /// Maximal value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits] /// and [ZLibDecoder.windowBits]. static const int maxWindowBits = 15; /// Default value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits] /// and [ZLibDecoder.windowBits]. static const int defaultWindowBits = 15; /// Minimal value for [ZLibCodec.level] and [ZLibEncoder.level]. static const int minLevel = -1; /// Maximal value for [ZLibCodec.level] and [ZLibEncoder.level] static const int maxLevel = 9; /// Default value for [ZLibCodec.level] and [ZLibEncoder.level]. static const int defaultLevel = 6; /// Minimal value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel]. static const int minMemLevel = 1; /// Maximal value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel]. static const int maxMemLevel = 9; /// Default value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel]. static const int defaultMemLevel = 8; /// Recommended strategy for data produced by a filter (or predictor) static const int strategyFiltered = 1; /// Use this strategy to force Huffman encoding only (no string match) static const int strategyHuffmanOnly = 2; /// Use this strategy to limit match distances to one (run-length encoding) static const int strategyRle = 3; /// This strategy prevents the use of dynamic Huffman codes, allowing for a /// simpler decoder static const int strategyFixed = 4; /// Recommended strategy for normal data static const int strategyDefault = 0; } /// An instance of the default implementation of the [ZLibCodec]. const ZLibCodec zlib = ZLibCodec._default(); /// The [ZLibCodec] encodes raw bytes to ZLib compressed bytes and decodes ZLib /// compressed bytes to raw bytes. final class ZLibCodec extends Codec, List> { /// When true, `GZip` frames will be added to the compressed data. final bool gzip; /// The compression-[level] can be set in the range of `-1..9`, with `6` being /// the default compression level. Levels above `6` will have higher /// compression rates at the cost of more CPU and memory usage. Levels below /// `6` will use less CPU and memory at the cost of lower compression rates. final int level; /// Specifies how much memory should be allocated for the internal compression /// state. `1` uses minimum memory but is slow and reduces compression ratio; /// `9` uses maximum memory for optimal speed. The default value is `8`. /// /// The memory requirements for deflate are (in bytes): /// ```dart /// (1 << (windowBits + 2)) + (1 << (memLevel + 9)) /// ``` /// that is: 128K for windowBits = 15 + 128K for memLevel = 8 (default values) final int memLevel; /// Tunes the compression algorithm. Use the value strategyDefault for normal /// data, strategyFiltered for data produced by a filter (or predictor), /// strategyHuffmanOnly to force Huffman encoding only (no string match), or /// strategyRle to limit match distances to one (run-length encoding). final int strategy; /// Base two logarithm of the window size (the size of the history buffer). It /// should be in the range 8..15. Larger values result in better compression at /// the expense of memory usage. The default value is 15 final int windowBits; /// When true, deflate generates raw data with no zlib header or trailer, and /// will not compute an adler32 check value final bool raw; /// Initial compression dictionary. /// /// It should consist of strings (byte sequences) that are likely to be /// encountered later in the data to be compressed, with the most commonly used /// strings preferably put towards the end of the dictionary. Using a /// dictionary is most useful when the data to be compressed is short and can /// be predicted with good accuracy; the data can then be compressed better /// than with the default empty dictionary. final List? dictionary; ZLibCodec({ this.level = ZLibOption.defaultLevel, this.windowBits = ZLibOption.defaultWindowBits, this.memLevel = ZLibOption.defaultMemLevel, this.strategy = ZLibOption.strategyDefault, this.dictionary, this.raw = false, this.gzip = false, }) { _validateZLibeLevel(level); _validateZLibMemLevel(memLevel); _validateZLibStrategy(strategy); _validateZLibWindowBits(windowBits); } const ZLibCodec._default() : level = ZLibOption.defaultLevel, windowBits = ZLibOption.defaultWindowBits, memLevel = ZLibOption.defaultMemLevel, strategy = ZLibOption.strategyDefault, raw = false, gzip = false, dictionary = null; /// Get a [ZLibEncoder] for encoding to `ZLib` compressed data. ZLibEncoder get encoder => ZLibEncoder( gzip: false, level: level, windowBits: windowBits, memLevel: memLevel, strategy: strategy, dictionary: dictionary, raw: raw, ); /// Get a [ZLibDecoder] for decoding `ZLib` compressed data. ZLibDecoder get decoder => ZLibDecoder(windowBits: windowBits, dictionary: dictionary, raw: raw); } /// An instance of the default implementation of the [GZipCodec]. const GZipCodec gzip = GZipCodec._default(); /// The [GZipCodec] encodes raw bytes to GZip compressed bytes and decodes GZip /// compressed bytes to raw bytes. final class GZipCodec extends Codec, List> { /// When true, `GZip` frames will be added to the compressed data. final bool gzip; /// The compression-[level] can be set in the range of `-1..9`, with `6` being /// the default compression level. Levels above `6` will have higher /// compression rates at the cost of more CPU and memory usage. Levels below /// `6` will use less CPU and memory at the cost of lower compression rates. final int level; /// Specifies how much memory should be allocated for the internal compression /// state. `1` uses minimum memory but is slow and reduces compression ratio; /// `9` uses maximum memory for optimal speed. The default value is `8`. /// /// The memory requirements for deflate are (in bytes): /// ```dart /// (1 << (windowBits + 2)) + (1 << (memLevel + 9)) /// ``` /// that is: 128K for windowBits = 15 + 128K for memLevel = 8 (default values) final int memLevel; /// Tunes the compression algorithm. Use the value /// [ZLibOption.strategyDefault] for normal data, /// [ZLibOption.strategyFiltered] for data produced by a filter /// (or predictor), [ZLibOption.strategyHuffmanOnly] to force Huffman /// encoding only (no string match), or [ZLibOption.strategyRle] to limit /// match distances to one (run-length encoding). final int strategy; /// Base two logarithm of the window size (the size of the history buffer). It /// should be in the range `8..15`. Larger values result in better compression /// at the expense of memory usage. The default value is `15` final int windowBits; /// Initial compression dictionary. /// /// It should consist of strings (byte sequences) that are likely to be /// encountered later in the data to be compressed, with the most commonly used /// strings preferably put towards the end of the dictionary. Using a /// dictionary is most useful when the data to be compressed is short and can /// be predicted with good accuracy; the data can then be compressed better /// than with the default empty dictionary. final List? dictionary; /// When true, deflate generates raw data with no zlib header or trailer, and /// will not compute an adler32 check value final bool raw; GZipCodec({ this.level = ZLibOption.defaultLevel, this.windowBits = ZLibOption.defaultWindowBits, this.memLevel = ZLibOption.defaultMemLevel, this.strategy = ZLibOption.strategyDefault, this.dictionary, this.raw = false, this.gzip = true, }) { _validateZLibeLevel(level); _validateZLibMemLevel(memLevel); _validateZLibStrategy(strategy); _validateZLibWindowBits(windowBits); } const GZipCodec._default() : level = ZLibOption.defaultLevel, windowBits = ZLibOption.defaultWindowBits, memLevel = ZLibOption.defaultMemLevel, strategy = ZLibOption.strategyDefault, raw = false, gzip = true, dictionary = null; /// Get a [ZLibEncoder] for encoding to `GZip` compressed data. ZLibEncoder get encoder => ZLibEncoder( gzip: true, level: level, windowBits: windowBits, memLevel: memLevel, strategy: strategy, dictionary: dictionary, raw: raw, ); /// Get a [ZLibDecoder] for decoding `GZip` compressed data. ZLibDecoder get decoder => ZLibDecoder( gzip: true, windowBits: windowBits, dictionary: dictionary, raw: raw, ); } /// The [ZLibEncoder] encoder is used by [ZLibCodec] and [GZipCodec] to compress /// data. final class ZLibEncoder extends Converter, List> { /// When true, `GZip` frames will be added to the compressed data. final bool gzip; /// The compression-[level] can be set in the range of `-1..9`, with `6` being /// the default compression level. Levels above `6` will have higher /// compression rates at the cost of more CPU and memory usage. Levels below /// `6` will use less CPU and memory at the cost of lower compression rates. final int level; /// Specifies how much memory should be allocated for the internal compression /// state. `1` uses minimum memory but is slow and reduces compression ratio; /// `9` uses maximum memory for optimal speed. The default value is `8`. /// /// The memory requirements for deflate are (in bytes): /// ```dart /// (1 << (windowBits + 2)) + (1 << (memLevel + 9)) /// ``` /// that is: 128K for windowBits = 15 + 128K for memLevel = 8 (default values) final int memLevel; /// Tunes the compression algorithm. Use the value /// [ZLibOption.strategyDefault] for normal data, /// [ZLibOption.strategyFiltered] for data produced by a filter /// (or predictor), [ZLibOption.strategyHuffmanOnly] to force Huffman /// encoding only (no string match), or [ZLibOption.strategyRle] to limit /// match distances to one (run-length encoding). final int strategy; /// Base two logarithm of the window size (the size of the history buffer). It /// should be in the range `8..15`. Larger values result in better compression /// at the expense of memory usage. The default value is `15` final int windowBits; /// Initial compression dictionary. /// /// It should consist of strings (byte sequences) that are likely to be /// encountered later in the data to be compressed, with the most commonly used /// strings preferably put towards the end of the dictionary. Using a /// dictionary is most useful when the data to be compressed is short and can /// be predicted with good accuracy; the data can then be compressed better /// than with the default empty dictionary. final List? dictionary; /// When true, deflate generates raw data with no zlib header or trailer, and /// will not compute an adler32 check value final bool raw; ZLibEncoder({ this.gzip = false, this.level = ZLibOption.defaultLevel, this.windowBits = ZLibOption.defaultWindowBits, this.memLevel = ZLibOption.defaultMemLevel, this.strategy = ZLibOption.strategyDefault, this.dictionary, this.raw = false, }) { _validateZLibeLevel(level); _validateZLibMemLevel(memLevel); _validateZLibStrategy(strategy); _validateZLibWindowBits(windowBits); } /// Convert a list of bytes using the options given to the ZLibEncoder /// constructor. List convert(List bytes) { _BufferSink sink = _BufferSink(); startChunkedConversion(sink) ..add(bytes) ..close(); return sink.builder.takeBytes(); } /// Start a chunked conversion using the options given to the [ZLibEncoder] /// constructor. /// /// Accepts any `Sink>`, but prefers a [ByteConversionSink], /// and converts any other sink to a [ByteConversionSink] before /// using it. ByteConversionSink startChunkedConversion(Sink> sink) { if (sink is! ByteConversionSink) { sink = ByteConversionSink.from(sink); } return _ZLibEncoderSink._( sink, gzip, level, windowBits, memLevel, strategy, dictionary, raw, ); } } /// The [ZLibDecoder] is used by [ZLibCodec] and [GZipCodec] to decompress data. final class ZLibDecoder extends Converter, List> { /// When true, all concatenated compressed data sets in the input are /// decompressed and concatenated in the output. final bool gzip; /// Base two logarithm of the window size (the size of the history buffer). It /// should be in the range `8..15`. Larger values result in better compression /// at the expense of memory usage. The default value is `15`. final int windowBits; /// Initial compression dictionary. /// /// It should consist of strings (byte sequences) that are likely to be /// encountered later in the data to be compressed, with the most commonly used /// strings preferably put towards the end of the dictionary. Using a /// dictionary is most useful when the data to be compressed is short and can /// be predicted with good accuracy; the data can then be compressed better /// than with the default empty dictionary. final List? dictionary; /// When true, deflate generates raw data with no zlib header or trailer, and /// will not compute an adler32 check value final bool raw; ZLibDecoder({ this.gzip = false, this.windowBits = ZLibOption.defaultWindowBits, this.dictionary, this.raw = false, }) { _validateZLibWindowBits(windowBits); } /// Convert a list of bytes using the options given to the [ZLibDecoder] /// constructor. List convert(List bytes) { _BufferSink sink = _BufferSink(); startChunkedConversion(sink) ..add(bytes) ..close(); return sink.builder.takeBytes(); } /// Start a chunked conversion. /// /// Accepts any `Sink>`, but prefers a [ByteConversionSink], /// and converts any other sink to a [ByteConversionSink] before /// using it. ByteConversionSink startChunkedConversion(Sink> sink) { if (sink is! ByteConversionSink) { sink = ByteConversionSink.from(sink); } return _ZLibDecoderSink._(sink, gzip, windowBits, dictionary, raw); } } /// The [RawZLibFilter] class provides a low-level interface to zlib. abstract interface class RawZLibFilter { /// Returns a [RawZLibFilter] whose [process] and [processed] methods /// compress data. factory RawZLibFilter.deflateFilter({ bool gzip = false, int level = ZLibOption.defaultLevel, int windowBits = ZLibOption.defaultWindowBits, int memLevel = ZLibOption.defaultMemLevel, int strategy = ZLibOption.strategyDefault, List? dictionary, bool raw = false, }) { return _makeZLibDeflateFilter( gzip, level, windowBits, memLevel, strategy, dictionary, raw, ); } /// Returns a [RawZLibFilter] whose [process] and [processed] methods /// decompress data. factory RawZLibFilter.inflateFilter({ bool gzip = false, int windowBits = ZLibOption.defaultWindowBits, List? dictionary, bool raw = false, }) { return _makeZLibInflateFilter(gzip, windowBits, dictionary, raw); } /// Process a chunk of data. /// /// This method must only be called when [processed] returns `null`. void process(List data, int start, int end); /// Get a chunk of processed data. /// /// When there are no more data available, [processed] will return `null`. /// Set [flush] to `false` for non-final calls /// to improve performance of some filters. /// /// The last call to [processed] should have [end] set to `true`. This will /// make sure an 'end' packet is written on the stream. // TODO: Which stream? List? processed({bool flush = true, bool end = false}); external static RawZLibFilter _makeZLibDeflateFilter( bool gzip, int level, int windowBits, int memLevel, int strategy, List? dictionary, bool raw, ); external static RawZLibFilter _makeZLibInflateFilter( bool gzip, int windowBits, List? dictionary, bool raw, ); } class _BufferSink extends ByteConversionSink { final BytesBuilder builder = BytesBuilder(copy: false); void add(List chunk) { builder.add(chunk); } void addSlice(List chunk, int start, int end, bool isLast) { if (chunk is Uint8List) { Uint8List list = chunk; builder.add( Uint8List.view(list.buffer, list.offsetInBytes + start, end - start), ); } else { builder.add(chunk.sublist(start, end)); } } void close() {} } class _ZLibEncoderSink extends _FilterSink { _ZLibEncoderSink._( ByteConversionSink sink, bool gzip, int level, int windowBits, int memLevel, int strategy, List? dictionary, bool raw, ) : super( sink, RawZLibFilter._makeZLibDeflateFilter( gzip, level, windowBits, memLevel, strategy, dictionary, raw, ), ); } class _ZLibDecoderSink extends _FilterSink { _ZLibDecoderSink._( ByteConversionSink sink, bool gzip, int windowBits, List? dictionary, bool raw, ) : super( sink, RawZLibFilter._makeZLibInflateFilter(gzip, windowBits, dictionary, raw), ); } class _FilterSink extends ByteConversionSink { final RawZLibFilter _filter; final ByteConversionSink _sink; bool _closed = false; bool _empty = true; _FilterSink(this._sink, this._filter); void add(List data) { addSlice(data, 0, data.length, false); } void addSlice(List data, int start, int end, bool isLast) { if (_closed) return; RangeError.checkValidRange(start, end, data.length); try { _empty = false; _BufferAndStart bufferAndStart = _ensureFastAndSerializableByteData( data, start, end, ); _filter.process( bufferAndStart.buffer, bufferAndStart.start, end - (start - bufferAndStart.start), ); List? out; while (true) { final out = _filter.processed(flush: false); if (out == null) break; _sink.add(out); } } catch (e) { _closed = true; rethrow; } if (isLast) close(); } void close() { if (_closed) return; // Be sure to send process an empty chunk of data. Without this, the empty // message would not have a GZip frame (if compressed with GZip). if (_empty) _filter.process(const [], 0, 0); try { while (true) { final out = _filter.processed(end: true); if (out == null) break; _sink.add(out); } } catch (e) { // TODO(kevmoo): not sure why this isn't a try/finally _closed = true; rethrow; } _closed = true; _sink.close(); } } void _validateZLibWindowBits(int windowBits) { if (ZLibOption.minWindowBits > windowBits || ZLibOption.maxWindowBits < windowBits) { throw RangeError.range( windowBits, ZLibOption.minWindowBits, ZLibOption.maxWindowBits, ); } } void _validateZLibeLevel(int level) { if (ZLibOption.minLevel > level || ZLibOption.maxLevel < level) { throw RangeError.range(level, ZLibOption.minLevel, ZLibOption.maxLevel); } } void _validateZLibMemLevel(int memLevel) { if (ZLibOption.minMemLevel > memLevel || ZLibOption.maxMemLevel < memLevel) { throw RangeError.range( memLevel, ZLibOption.minMemLevel, ZLibOption.maxMemLevel, ); } } void _validateZLibStrategy(int strategy) { const strategies = [ ZLibOption.strategyFiltered, ZLibOption.strategyHuffmanOnly, ZLibOption.strategyRle, ZLibOption.strategyFixed, ZLibOption.strategyDefault, ]; if (strategies.indexOf(strategy) == -1) { throw ArgumentError("Unsupported 'strategy'"); } }