%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(zlib).
-export([open/0,close/1,deflateInit/1,deflateInit/2,deflateInit/6,
deflateSetDictionary/2,deflateReset/1,deflateParams/3,
deflate/2,deflate/3,deflateEnd/1,
inflateInit/1,inflateInit/2,
inflateSetDictionary/2,inflateGetDictionary/1, inflateReset/1,
inflate/2,inflate/3,inflateEnd/1,
inflateChunk/2,inflateChunk/1,
safeInflate/2,
setBufSize/2,getBufSize/1,
crc32/1,crc32/2,crc32/3,adler32/2,adler32/3,
crc32_combine/4,adler32_combine/4,
compress/1,uncompress/1,zip/1,unzip/1,
gzip/1,gunzip/1]).
-export([on_load/0]).
%% These are soft-deprecated until OTP 21.
% -deprecated([inflateChunk/1, inflateChunk/2,
% getBufSize/1, setBufSize/2,
% crc32/1,crc32/2,crc32/3,adler32/2,adler32/3,
% crc32_combine/4,adler32_combine/4]).
-export_type([zstream/0, zflush/0, zlevel/0, zwindowbits/0, zmemlevel/0,
zstrategy/0]).
%% flush argument encoding
-define(Z_NO_FLUSH, 0).
-define(Z_SYNC_FLUSH, 2).
-define(Z_FULL_FLUSH, 3).
-define(Z_FINISH, 4).
%% compression level
-define(Z_NO_COMPRESSION, 0).
-define(Z_BEST_SPEED, 1).
-define(Z_BEST_COMPRESSION, 9).
-define(Z_DEFAULT_COMPRESSION, (-1)).
%% compresssion strategy
-define(Z_FILTERED, 1).
-define(Z_HUFFMAN_ONLY, 2).
-define(Z_RLE, 3).
-define(Z_DEFAULT_STRATEGY, 0).
%% deflate compression method
-define(Z_DEFLATED, 8).
-define(MAX_WBITS, 15).
%% Chunk sizes are hardcoded on account of them screwing with the
%% predictability of the system. zlib is incapable of trapping so we need to
%% ensure that it never operates on any significant amount of data.
-define(DEFLATE_IN_CHUNKSIZE, 8 bsl 10).
-define(DEFLATE_OUT_CHUNKSIZE, 8 bsl 10).
-define(INFLATE_IN_CHUNKSIZE, 8 bsl 10).
-define(INFLATE_OUT_CHUNKSIZE, 16 bsl 10).
%%------------------------------------------------------------------------
%% Public data types.
-type zstream() :: term().
-type zflush() :: 'none' | 'sync' | 'full' | 'finish'.
-type zlevel() ::
'none' | 'default' | 'best_compression' | 'best_speed' | 0..9.
-type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'.
-type zmemlevel() :: 1..9.
-type zwindowbits() :: -15..-8 | 8..47.
%% Private data types.
-type zmethod() :: 'deflated'.
-record(zlib_opts, {
stream :: zstream(),
method :: term(),
input_chunk_size :: integer(),
output_chunk_size :: integer(),
flush :: integer()
}).
%%------------------------------------------------------------------------
on_load() ->
case erlang:load_nif(atom_to_list(?MODULE), 0) of
ok -> ok
end.
-spec open() -> zstream().
open() ->
open_nif().
open_nif() ->
erlang:nif_error(undef).
-spec close(Z) -> 'ok' when
Z :: zstream().
close(Z) ->
close_nif(Z).
close_nif(_Z) ->
erlang:nif_error(undef).
-spec deflateInit(Z) -> 'ok' when
Z :: zstream().
deflateInit(Z) ->
deflateInit(Z, default).
-spec deflateInit(Z, Level) -> 'ok' when
Z :: zstream(),
Level :: zlevel().
deflateInit(Z, Level) ->
deflateInit_nif(Z, arg_level(Level)).
deflateInit_nif(_Z, _Level) ->
erlang:nif_error(undef).
-spec deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) -> 'ok' when
Z :: zstream(),
Level :: zlevel(),
Method :: zmethod(),
WindowBits :: zwindowbits(),
MemLevel :: zmemlevel(),
Strategy :: zstrategy().
deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) ->
deflateInit_nif(Z,
arg_level(Level),
arg_method(Method),
arg_bitsz(WindowBits),
arg_mem(MemLevel),
arg_strategy(Strategy)).
deflateInit_nif(_Z, _Level, _Method, _WindowBits, _MemLevel, _Strategy) ->
erlang:nif_error(undef).
-spec deflateSetDictionary(Z, Dictionary) -> Adler32 when
Z :: zstream(),
Dictionary :: iodata(),
Adler32 :: integer().
deflateSetDictionary(Z, Dictionary) ->
deflateSetDictionary_nif(Z, Dictionary).
deflateSetDictionary_nif(_Z, _Dictionary) ->
erlang:nif_error(undef).
-spec deflateReset(Z) -> 'ok' when
Z :: zstream().
deflateReset(Z) ->
deflateReset_nif(Z).
deflateReset_nif(_Z) ->
erlang:nif_error(undef).
-spec deflateParams(Z, Level, Strategy) -> ok when
Z :: zstream(),
Level :: zlevel(),
Strategy :: zstrategy().
deflateParams(Z, Level0, Strategy0) ->
Level = arg_level(Level0),
Strategy = arg_strategy(Strategy0),
case deflateParams_nif(Z, Level, Strategy) of
buf_error ->
%% We had data left in the pipe; flush everything and stash it away
%% for the next deflate call before trying again.
Output = deflate(Z, <<>>, full),
save_progress(Z, deflate, Output),
deflateParams_nif(Z, Level, Strategy);
Any -> Any
end.
deflateParams_nif(_Z, _Level, _Strategy) ->
erlang:nif_error(undef).
-spec deflate(Z, Data) -> Compressed when
Z :: zstream(),
Data :: iodata(),
Compressed :: iolist().
deflate(Z, Data) ->
deflate(Z, Data, none).
-spec deflate(Z, Data, Flush) -> Compressed when
Z :: zstream(),
Data :: iodata(),
Flush :: zflush(),
Compressed :: iolist().
deflate(Z, Data, Flush) ->
Progress = restore_progress(Z, deflate),
enqueue_input(Z, Data),
append_iolist(Progress, dequeue_all_chunks(Z, deflate_opts(Flush))).
deflate_opts(Flush) ->
#zlib_opts{
method = fun deflate_nif/4,
input_chunk_size = ?DEFLATE_IN_CHUNKSIZE,
output_chunk_size = ?DEFLATE_OUT_CHUNKSIZE,
flush = arg_flush(Flush)
}.
deflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) ->
erlang:nif_error(undef).
-spec deflateEnd(Z) -> 'ok' when
Z :: zstream().
deflateEnd(Z) ->
deflateEnd_nif(Z).
deflateEnd_nif(_Z) ->
erlang:nif_error(undef).
-spec inflateInit(Z) -> 'ok' when
Z :: zstream().
inflateInit(Z) ->
inflateInit_nif(Z).
inflateInit_nif(_Z) ->
erlang:nif_error(undef).
-spec inflateInit(Z, WindowBits) -> 'ok' when
Z :: zstream(),
WindowBits :: zwindowbits().
inflateInit(Z, WindowBits) ->
inflateInit_nif(Z, WindowBits).
inflateInit_nif(_Z, _WindowBits) ->
erlang:nif_error(undef).
-spec inflateSetDictionary(Z, Dictionary) -> 'ok' when
Z :: zstream(),
Dictionary :: iodata().
inflateSetDictionary(Z, Dictionary) ->
inflateSetDictionary_nif(Z, Dictionary).
inflateSetDictionary_nif(_Z, _Dictionary) ->
erlang:nif_error(undef).
-spec inflateGetDictionary(Z) -> Dictionary when
Z :: zstream(),
Dictionary :: binary().
inflateGetDictionary(Z) ->
case inflateGetDictionary_nif(Z) of
Dictionary when is_binary(Dictionary) ->
Dictionary;
not_supported ->
erlang:error(enotsup)
end.
inflateGetDictionary_nif(_Z) ->
erlang:nif_error(undef).
-spec inflateReset(Z) -> 'ok' when
Z :: zstream().
inflateReset(Z) ->
inflateReset_nif(Z).
inflateReset_nif(_Z) ->
erlang:nif_error(undef).
-spec inflate(Z, Data) -> Decompressed when
Z :: zstream(),
Data :: iodata(),
Decompressed :: iolist().
inflate(Z, Data) ->
inflate(Z, Data, []).
-spec inflate(Z, Data, Options) -> Decompressed when
Z :: zstream(),
Data :: iodata(),
Options :: list({exception_on_need_dict, boolean()}),
Decompressed :: iolist() |
{need_dictionary,
Adler32 :: integer(),
Output :: iolist()}.
inflate(Z, Data, Options) ->
enqueue_input(Z, Data),
Result = dequeue_all_chunks(Z, inflate_opts()),
case proplist_get_value(Options, exception_on_need_dict, true) of
true -> exception_on_need_dict(Z, Result);
false -> Result
end.
inflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) ->
erlang:nif_error(undef).
inflate_opts() ->
#zlib_opts{
method = fun inflate_nif/4,
input_chunk_size = ?INFLATE_IN_CHUNKSIZE,
output_chunk_size = ?INFLATE_OUT_CHUNKSIZE,
flush = arg_flush(none)
}.
-spec inflateChunk(Z, Data) -> Decompressed | {more, Decompressed} when
Z :: zstream(),
Data :: iodata(),
Decompressed :: iolist().
inflateChunk(Z, Data) ->
enqueue_input(Z, Data),
inflateChunk(Z).
-spec inflateChunk(Z) -> Decompressed | {more, Decompressed} when
Z :: zstream(),
Decompressed :: iolist().
inflateChunk(Z) ->
Opts0 = inflate_opts(),
Opts = Opts0#zlib_opts { output_chunk_size = getBufSize(Z) },
Result0 = dequeue_next_chunk(Z, Opts),
Result1 = exception_on_need_dict(Z, Result0),
yield_inflateChunk(Z, Result1).
yield_inflateChunk(_Z, {continue, Output}) ->
{more, lists:flatten(Output)};
yield_inflateChunk(_Z, {finished, Output}) ->
lists:flatten(Output).
exception_on_need_dict(Z, {need_dictionary, Adler, Output}) ->
Progress = restore_progress(Z, inflate),
save_progress(Z, inflate, append_iolist(Progress, Output)),
erlang:error({need_dictionary, Adler});
exception_on_need_dict(Z, {Mark, Output}) ->
Progress = restore_progress(Z, inflate),
{Mark, append_iolist(Progress, Output)};
exception_on_need_dict(Z, Output) when is_list(Output); is_binary(Output) ->
Progress = restore_progress(Z, inflate),
append_iolist(Progress, Output).
-spec safeInflate(Z, Data) -> Result when
Z :: zstream(),
Data :: iodata(),
Result :: {continue, Output :: iolist()} |
{finished, Output :: iolist()} |
{need_dictionary,
Adler32 :: integer(),
Output :: iolist()}.
safeInflate(Z, Data) ->
enqueue_input(Z, Data),
dequeue_next_chunk(Z, inflate_opts()).
-spec inflateEnd(Z) -> 'ok' when
Z :: zstream().
inflateEnd(Z) ->
inflateEnd_nif(Z).
inflateEnd_nif(_Z) ->
erlang:nif_error(undef).
-spec setBufSize(Z, Size) -> 'ok' when
Z :: zstream(),
Size :: non_neg_integer().
setBufSize(Z, Size) when is_integer(Size), Size > 16, Size < (1 bsl 24) ->
setBufSize_nif(Z, Size);
setBufSize(_Z, _Size) ->
erlang:error(badarg).
setBufSize_nif(_Z, _Size) ->
erlang:nif_error(undef).
-spec getBufSize(Z) -> non_neg_integer() when
Z :: zstream().
getBufSize(Z) ->
getBufSize_nif(Z).
getBufSize_nif(_Z) ->
erlang:nif_error(undef).
-spec crc32(Z) -> CRC when
Z :: zstream(),
CRC :: integer().
crc32(Z) ->
crc32_nif(Z).
crc32_nif(_Z) ->
erlang:nif_error(undef).
-spec crc32(Z, Data) -> CRC when
Z :: zstream(),
Data :: iodata(),
CRC :: integer().
crc32(Z, Data) when is_reference(Z) ->
erlang:crc32(Data);
crc32(_Z, _Data) ->
erlang:error(badarg).
-spec crc32(Z, PrevCRC, Data) -> CRC when
Z :: zstream(),
PrevCRC :: integer(),
Data :: iodata(),
CRC :: integer().
crc32(Z, CRC, Data) when is_reference(Z) ->
erlang:crc32(CRC, Data);
crc32(_Z, _CRC, _Data) ->
erlang:error(badarg).
-spec crc32_combine(Z, CRC1, CRC2, Size2) -> CRC when
Z :: zstream(),
CRC :: integer(),
CRC1 :: integer(),
CRC2 :: integer(),
Size2 :: integer().
crc32_combine(Z, CRC1, CRC2, Size2) when is_reference(Z) ->
erlang:crc32_combine(CRC1, CRC2, Size2);
crc32_combine(_Z, _CRC1, _CRC2, _Size2) ->
erlang:error(badarg).
-spec adler32(Z, Data) -> CheckSum when
Z :: zstream(),
Data :: iodata(),
CheckSum :: integer().
adler32(Z, Data) when is_reference(Z) ->
erlang:adler32(Data);
adler32(_Z, _Data) ->
erlang:error(badarg).
-spec adler32(Z, PrevAdler, Data) -> CheckSum when
Z :: zstream(),
PrevAdler :: integer(),
Data :: iodata(),
CheckSum :: integer().
adler32(Z, Adler, Data) when is_reference(Z) ->
erlang:adler32(Adler, Data);
adler32(_Z, _Adler, _Data) ->
erlang:error(badarg).
-spec adler32_combine(Z, Adler1, Adler2, Size2) -> Adler when
Z :: zstream(),
Adler :: integer(),
Adler1 :: integer(),
Adler2 :: integer(),
Size2 :: integer().
adler32_combine(Z, Adler1, Adler2, Size2) when is_reference(Z) ->
erlang:adler32_combine(Adler1, Adler2, Size2);
adler32_combine(_Z, _Adler1, _Adler2, _Size2) ->
erlang:error(badarg).
%% compress/uncompress zlib with header
-spec compress(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
compress(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec uncompress(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
uncompress(Data) ->
try iolist_size(Data) of
Size ->
if
Size >= 8 ->
Z = open(),
Bs = try
inflateInit(Z),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs);
true ->
erlang:error(data_error)
end
catch
_:_ ->
erlang:error(badarg)
end.
%% unzip/zip zlib without header (zip members)
-spec zip(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
zip(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec unzip(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
unzip(Data) ->
Z = open(),
Bs = try
inflateInit(Z, -?MAX_WBITS),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec gzip(Data) -> Compressed when
Data :: iodata(),
Compressed :: binary().
gzip(Data) ->
Z = open(),
Bs = try
deflateInit(Z, default, deflated, 16+?MAX_WBITS, 8, default),
B = deflate(Z, Data, finish),
deflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec gunzip(Data) -> Decompressed when
Data :: iodata(),
Decompressed :: binary().
gunzip(Data) ->
Z = open(),
Bs = try
inflateInit(Z, 16+?MAX_WBITS),
B = inflate(Z, Data),
inflateEnd(Z),
B
after
close(Z)
end,
iolist_to_binary(Bs).
-spec dequeue_all_chunks(Z, Opts) -> Result when
Z :: zstream(),
Opts :: #zlib_opts{},
Result :: {need_dictionary, integer(), iolist()} |
iolist().
dequeue_all_chunks(Z, Opts) ->
dequeue_all_chunks_1(Z, Opts, []).
dequeue_all_chunks_1(Z, Opts, Output) ->
case dequeue_next_chunk(Z, Opts) of
{need_dictionary, _, _} = NeedDict ->
NeedDict;
{continue, Chunk} ->
dequeue_all_chunks_1(Z, Opts, append_iolist(Output, Chunk));
{finished, Chunk} ->
append_iolist(Output, Chunk)
end.
-spec dequeue_next_chunk(Z, Opts) -> Result when
Z :: zstream(),
Opts :: #zlib_opts{},
Result :: {need_dictionary, integer(), iolist()} |
{continue, iolist()} |
{finished, iolist()}.
dequeue_next_chunk(Z, Opts) ->
Method = Opts#zlib_opts.method,
IChSz = Opts#zlib_opts.input_chunk_size,
OChSz = Opts#zlib_opts.output_chunk_size,
Flush = Opts#zlib_opts.flush,
Method(Z, IChSz, OChSz, Flush).
-spec append_iolist(IO, D) -> iolist() when
IO :: iodata(),
D :: iodata().
append_iolist([], D) when is_list(D) -> D;
append_iolist([], D) -> [D];
append_iolist(IO, []) -> IO;
append_iolist(IO, [D]) -> [IO, D];
append_iolist(IO, D) -> [IO, D].
%% inflate/2 and friends are documented as throwing an error on Z_NEED_DICT
%% rather than simply returning something to that effect, and deflateParams/3
%% may flush behind the scenes. This requires us to stow away our current
%% progress in the handle and resume from that point on our next call.
%%
%% Generally speaking this is either a refc binary or nothing at all, so it's
%% pretty cheap.
-spec save_progress(Z, Kind, Output) -> ok when
Z :: zstream(),
Kind :: inflate | deflate,
Output :: iolist().
save_progress(Z, Kind, Output) ->
ok = setStash_nif(Z, {Kind, Output}).
-spec restore_progress(Z, Kind) -> iolist() when
Z :: zstream(),
Kind :: inflate | deflate.
restore_progress(Z, Kind) ->
case getStash_nif(Z) of
{ok, {Kind, Output}} ->
ok = clearStash_nif(Z),
Output;
empty ->
[]
end.
-spec clearStash_nif(Z) -> ok when
Z :: zstream().
clearStash_nif(_Z) ->
erlang:nif_error(undef).
-spec setStash_nif(Z, Term) -> ok when
Z :: zstream(),
Term :: term().
setStash_nif(_Z, _Term) ->
erlang:nif_error(undef).
-spec getStash_nif(Z) -> {ok, term()} | empty when
Z :: zstream().
getStash_nif(_Z) ->
erlang:nif_error(undef).
%% The 'proplists' module isn't preloaded so we can't rely on its existence.
proplist_get_value([], _Name, DefVal) -> DefVal;
proplist_get_value([{Name, Value} | _Opts], Name, _DefVal) -> Value;
proplist_get_value([_Head | Opts], Name, DefVal) ->
proplist_get_value(Opts, Name, DefVal).
arg_flush(none) -> ?Z_NO_FLUSH;
%% ?Z_PARTIAL_FLUSH is deprecated in zlib -- deliberately not included.
arg_flush(sync) -> ?Z_SYNC_FLUSH;
arg_flush(full) -> ?Z_FULL_FLUSH;
arg_flush(finish) -> ?Z_FINISH;
arg_flush(_) -> erlang:error(bad_flush_mode).
arg_level(none) -> ?Z_NO_COMPRESSION;
arg_level(best_speed) -> ?Z_BEST_SPEED;
arg_level(best_compression) -> ?Z_BEST_COMPRESSION;
arg_level(default) -> ?Z_DEFAULT_COMPRESSION;
arg_level(Level) when is_integer(Level), Level >= 0, Level =< 9 -> Level;
arg_level(_) -> erlang:error(bad_compression_level).
arg_strategy(filtered) -> ?Z_FILTERED;
arg_strategy(huffman_only) -> ?Z_HUFFMAN_ONLY;
arg_strategy(rle) -> ?Z_RLE;
arg_strategy(default) -> ?Z_DEFAULT_STRATEGY;
arg_strategy(_) -> erlang:error(bad_compression_strategy).
arg_method(deflated) -> ?Z_DEFLATED;
arg_method(_) -> erlang:error(bad_compression_method).
-spec arg_bitsz(zwindowbits()) -> zwindowbits().
arg_bitsz(Bits) when is_integer(Bits) andalso
((8 =< Bits andalso Bits < 48) orelse
(-15 =< Bits andalso Bits =< -8)) ->
Bits;
arg_bitsz(_) -> erlang:error(bad_windowbits).
-spec arg_mem(zmemlevel()) -> zmemlevel().
arg_mem(Level) when is_integer(Level), 1 =< Level, Level =< 9 -> Level;
arg_mem(_) -> erlang:error(bad_memlevel).
-spec enqueue_input(Z, IOData) -> ok when
Z :: zstream(),
IOData :: iodata().
enqueue_input(Z, IOData) ->
enqueue_input_1(Z, erlang:iolist_to_iovec(IOData)).
enqueue_input_1(_Z, []) ->
ok;
enqueue_input_1(Z, IOVec) ->
case enqueue_nif(Z, IOVec) of
{continue, Remainder} -> enqueue_input_1(Z, Remainder);
ok -> ok
end.
enqueue_nif(_Z, _IOVec) ->
erlang:nif_error(undef).