blob: 0928116767fcf5af5f662f9560d45d06a41542ea [file] [log] [blame]
// 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 class ZLibOption {
/// Minimal value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits]
/// and [ZLibDecoder.windowBits].
static const int minWindowBits = 8;
@Deprecated("Use minWindowBits instead")
static const int MIN_WINDOW_BITS = 8;
/// Maximal value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits]
/// and [ZLibDecoder.windowBits].
static const int maxWindowBits = 15;
@Deprecated("Use maxWindowBits instead")
static const int MAX_WINDOW_BITS = 15;
/// Default value for [ZLibCodec.windowBits], [ZLibEncoder.windowBits]
/// and [ZLibDecoder.windowBits].
static const int defaultWindowBits = 15;
@Deprecated("Use defaultWindowBits instead")
static const int DEFAULT_WINDOW_BITS = 15;
/// Minimal value for [ZLibCodec.level] and [ZLibEncoder.level].
static const int minLevel = -1;
@Deprecated("Use minLevel instead")
static const int MIN_LEVEL = -1;
/// Maximal value for [ZLibCodec.level] and [ZLibEncoder.level]
static const int maxLevel = 9;
@Deprecated("Use maxLevel instead")
static const int MAX_LEVEL = 9;
/// Default value for [ZLibCodec.level] and [ZLibEncoder.level].
static const int defaultLevel = 6;
@Deprecated("Use defaultLevel instead")
static const int DEFAULT_LEVEL = 6;
/// Minimal value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel].
static const int minMemLevel = 1;
@Deprecated("Use minMemLevel instead")
static const int MIN_MEM_LEVEL = 1;
/// Maximal value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel].
static const int maxMemLevel = 9;
@Deprecated("Use maxMemLevel instead")
static const int MAX_MEM_LEVEL = 9;
/// Default value for [ZLibCodec.memLevel] and [ZLibEncoder.memLevel].
static const int defaultMemLevel = 8;
@Deprecated("Use defaultMemLevel instead")
static const int DEFAULT_MEM_LEVEL = 8;
/// Recommended strategy for data produced by a filter (or predictor)
static const int strategyFiltered = 1;
@Deprecated("Use strategyFiltered instead")
static const int STRATEGY_FILTERED = 1;
/// Use this strategy to force Huffman encoding only (no string match)
static const int strategyHuffmanOnly = 2;
@Deprecated("Use strategyHuffmanOnly instead")
static const int STRATEGY_HUFFMAN_ONLY = 2;
/// Use this strategy to limit match distances to one (run-length encoding)
static const int strategyRle = 3;
@Deprecated("Use strategyRle instead")
static const int STRATEGY_RLE = 3;
/// This strategy prevents the use of dynamic Huffman codes, allowing for a
/// simpler decoder
static const int strategyFixed = 4;
@Deprecated("Use strategyFixed instead")
static const int STRATEGY_FIXED = 4;
/// Recommended strategy for normal data
static const int strategyDefault = 0;
@Deprecated("Use strategyDefault instead")
static const int STRATEGY_DEFAULT = 0;
}
/// An instance of the default implementation of the [ZLibCodec].
const ZLibCodec zlib = const ZLibCodec._default();
@Deprecated("Use zlib instead")
const ZLibCodec ZLIB = zlib;
/// The [ZLibCodec] encodes raw bytes to ZLib compressed bytes and decodes ZLib
/// compressed bytes to raw bytes.
class ZLibCodec extends Codec<List<int>, List<int>> {
/// 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<int>? 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 => new 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 =>
new ZLibDecoder(windowBits: windowBits, dictionary: dictionary, raw: raw);
}
/// An instance of the default implementation of the [GZipCodec].
const GZipCodec gzip = const GZipCodec._default();
@Deprecated("Use gzip instead")
const GZipCodec GZIP = gzip;
/// The [GZipCodec] encodes raw bytes to GZip compressed bytes and decodes GZip
/// compressed bytes to raw bytes.
///
/// The difference between [ZLibCodec] and [GZipCodec] is that the [GZipCodec]
/// wraps the `ZLib` compressed bytes in `GZip` frames.
class GZipCodec extends Codec<List<int>, List<int>> {
/// 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<int>? 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 => new 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 =>
new ZLibDecoder(windowBits: windowBits, dictionary: dictionary, raw: raw);
}
/// The [ZLibEncoder] encoder is used by [ZLibCodec] and [GZipCodec] to compress
/// data.
class ZLibEncoder extends Converter<List<int>, List<int>> {
/// 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<int>? 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<int> convert(List<int> bytes) {
_BufferSink sink = new _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<List<int>>`, but prefers a [ByteConversionSink],
/// and converts any other sink to a [ByteConversionSink] before
/// using it.
ByteConversionSink startChunkedConversion(Sink<List<int>> sink) {
if (sink is! ByteConversionSink) {
sink = new ByteConversionSink.from(sink);
}
return new _ZLibEncoderSink._(
sink, gzip, level, windowBits, memLevel, strategy, dictionary, raw);
}
}
/// The [ZLibDecoder] is used by [ZLibCodec] and [GZipCodec] to decompress data.
class ZLibDecoder extends Converter<List<int>, List<int>> {
/// 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<int>? 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.windowBits = ZLibOption.defaultWindowBits,
this.dictionary,
this.raw = false}) {
_validateZLibWindowBits(windowBits);
}
/// Convert a list of bytes using the options given to the [ZLibDecoder]
/// constructor.
List<int> convert(List<int> bytes) {
_BufferSink sink = new _BufferSink();
startChunkedConversion(sink)
..add(bytes)
..close();
return sink.builder.takeBytes();
}
/// Start a chunked conversion.
///
/// Accepts any `Sink<List<int>>`, but prefers a [ByteConversionSink],
/// and converts any other sink to a [ByteConversionSink] before
/// using it.
ByteConversionSink startChunkedConversion(Sink<List<int>> sink) {
if (sink is! ByteConversionSink) {
sink = new ByteConversionSink.from(sink);
}
return new _ZLibDecoderSink._(sink, windowBits, dictionary, raw);
}
}
/// The [RawZLibFilter] class provides a low-level interface to zlib.
abstract class RawZLibFilter {
/// Returns a 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<int>? dictionary,
bool raw = false,
}) {
return _makeZLibDeflateFilter(
gzip, level, windowBits, memLevel, strategy, dictionary, raw);
}
/// Returns a a [RawZLibFilter] whose [process] and [processed] methods
/// decompress data.
factory RawZLibFilter.inflateFilter({
int windowBits = ZLibOption.defaultWindowBits,
List<int>? dictionary,
bool raw = false,
}) {
return _makeZLibInflateFilter(windowBits, dictionary, raw);
}
/// Process a chunk of data.
///
/// This method must only be called when [processed] returns `null`.
void process(List<int> 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<int>? processed({bool flush = true, bool end = false});
external static RawZLibFilter _makeZLibDeflateFilter(
bool gzip,
int level,
int windowBits,
int memLevel,
int strategy,
List<int>? dictionary,
bool raw);
external static RawZLibFilter _makeZLibInflateFilter(
int windowBits, List<int>? dictionary, bool raw);
}
class _BufferSink extends ByteConversionSink {
final BytesBuilder builder = new BytesBuilder(copy: false);
void add(List<int> chunk) {
builder.add(chunk);
}
void addSlice(List<int> chunk, int start, int end, bool isLast) {
if (chunk is Uint8List) {
Uint8List list = chunk;
builder.add(new 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<int>? dictionary,
bool raw)
: super(
sink,
RawZLibFilter._makeZLibDeflateFilter(
gzip, level, windowBits, memLevel, strategy, dictionary, raw));
}
class _ZLibDecoderSink extends _FilterSink {
_ZLibDecoderSink._(
ByteConversionSink sink, int windowBits, List<int>? dictionary, bool raw)
: super(sink,
RawZLibFilter._makeZLibInflateFilter(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<int> data) {
addSlice(data, 0, data.length, false);
}
void addSlice(List<int> data, int start, int end, bool isLast) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(end, "end");
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<int>? 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;
throw e;
}
_closed = true;
_sink.close();
}
}
void _validateZLibWindowBits(int windowBits) {
if (ZLibOption.minWindowBits > windowBits ||
ZLibOption.maxWindowBits < windowBits) {
throw new RangeError.range(
windowBits, ZLibOption.minWindowBits, ZLibOption.maxWindowBits);
}
}
void _validateZLibeLevel(int level) {
if (ZLibOption.minLevel > level || ZLibOption.maxLevel < level) {
throw new RangeError.range(level, ZLibOption.minLevel, ZLibOption.maxLevel);
}
}
void _validateZLibMemLevel(int memLevel) {
if (ZLibOption.minMemLevel > memLevel || ZLibOption.maxMemLevel < memLevel) {
throw new RangeError.range(
memLevel, ZLibOption.minMemLevel, ZLibOption.maxMemLevel);
}
}
void _validateZLibStrategy(int strategy) {
const strategies = const <int>[
ZLibOption.strategyFiltered,
ZLibOption.strategyHuffmanOnly,
ZLibOption.strategyRle,
ZLibOption.strategyFixed,
ZLibOption.strategyDefault
];
if (strategies.indexOf(strategy) == -1) {
throw new ArgumentError("Unsupported 'strategy'");
}
}