diff options
| -rw-r--r-- | erts/emulator/nifs/common/zlib_nif.c | 127 | ||||
| -rw-r--r-- | erts/preloaded/ebin/zlib.beam | bin | 19120 -> 19492 bytes | |||
| -rw-r--r-- | erts/preloaded/src/zlib.erl | 36 | ||||
| -rw-r--r-- | lib/kernel/test/zlib_SUITE.erl | 23 | 
4 files changed, 94 insertions, 92 deletions
diff --git a/erts/emulator/nifs/common/zlib_nif.c b/erts/emulator/nifs/common/zlib_nif.c index a1a65e1946..a9c5b05e47 100644 --- a/erts/emulator/nifs/common/zlib_nif.c +++ b/erts/emulator/nifs/common/zlib_nif.c @@ -69,11 +69,27 @@ typedef enum {      ST_DEFLATE = 1,      ST_INFLATE = 2,      ST_CLOSED = 3 -} zlib_state; +} zlib_state_t; + +/* Controls what to do when the user attempts to decompress more data after + * Z_STREAM_END has been returned: + * + * - 'cut' wipes all further input and returns empty results until reset by + * the user. This is the default behavior, matching that of the old driver. + * - 'reset' resets the state without discarding any input, making it possible + * to decompress blindly concatenated streams. + * - 'error' crashes with a data error. */ +typedef enum { +    EOS_BEHAVIOR_ERROR = 0, +    EOS_BEHAVIOR_RESET = 1, +    EOS_BEHAVIOR_CUT = 2 +} zlib_eos_behavior_t;  typedef struct {      z_stream s; -    zlib_state state; +    zlib_state_t state; + +    zlib_eos_behavior_t eos_behavior;      /* These refer to the plaintext CRC, and are only needed for zlib:crc32/1       * which is deprecated. */ @@ -102,7 +118,6 @@ typedef struct {  static ERL_NIF_TERM zlib_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_deflateInit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM zlib_deflateInit2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_deflateSetDictionary(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_deflateReset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_deflateEnd(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); @@ -110,7 +125,6 @@ static ERL_NIF_TERM zlib_deflateParams(ErlNifEnv *env, int argc, const ERL_NIF_T  static ERL_NIF_TERM zlib_deflate(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_inflateInit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM zlib_inflateInit2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_inflateSetDictionary(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_inflateGetDictionary(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);  static ERL_NIF_TERM zlib_inflateReset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); @@ -130,8 +144,7 @@ static ERL_NIF_TERM zlib_enqueue_input(ErlNifEnv *env, int argc, const ERL_NIF_T  static ErlNifFunc nif_funcs[] = {      /* deflate */ -    {"deflateInit_nif", 2, zlib_deflateInit}, -    {"deflateInit_nif", 6, zlib_deflateInit2}, +    {"deflateInit_nif", 6, zlib_deflateInit},      {"deflateSetDictionary_nif", 2, zlib_deflateSetDictionary},      {"deflateReset_nif", 1, zlib_deflateReset},      {"deflateEnd_nif", 1, zlib_deflateEnd}, @@ -139,8 +152,7 @@ static ErlNifFunc nif_funcs[] = {      {"deflate_nif", 4, zlib_deflate},      /* inflate */ -    {"inflateInit_nif", 1, zlib_inflateInit}, -    {"inflateInit_nif", 2, zlib_inflateInit2}, +    {"inflateInit_nif", 3, zlib_inflateInit},      {"inflateSetDictionary_nif", 2, zlib_inflateSetDictionary},      {"inflateGetDictionary_nif", 1, zlib_inflateGetDictionary},      {"inflateReset_nif", 1, zlib_inflateReset}, @@ -509,9 +521,11 @@ static ERL_NIF_TERM zlib_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[      d->s.opaque = d;      d->s.data_type = Z_BINARY; -    d->state = ST_NONE; +    d->eos_behavior = EOS_BEHAVIOR_CUT;      d->eos_seen = 0; +    d->state = ST_NONE; +      d->want_output_crc = 0;      d->want_input_crc = 0;      d->is_raw_stream = 0; @@ -551,42 +565,6 @@ static ERL_NIF_TERM zlib_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv  static ERL_NIF_TERM zlib_deflateInit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {      zlib_data_t *d; -    int level, res; - -    if(argc != 2 || !get_zlib_data(env, argv[0], &d) || -                    !enif_get_int(env, argv[1], &level)) { -        return enif_make_badarg(env); -    } else if(!zlib_process_check(env, d)) { -        return enif_raise_exception(env, am_not_on_controlling_process); -    } else if(d->state != ST_NONE) { -        return enif_raise_exception(env, am_already_initialized); -    } - -    res = deflateInit(&d->s, level); - -    if(res == Z_OK) { -        d->state = ST_DEFLATE; -        d->eos_seen = 0; - -        /* FIXME: crc32/1 is documented as returning "the current calculated -         * checksum," but failed to mention that the old implementation only -         * calculated it when WindowBits < 0 (See zlib_deflateInit2). -         * -         * We could fix this behavior by setting d->want_input_crc to 1 here, -         * but we've decided to retain this quirk since the performance hit is -         * quite significant. */ -        d->want_output_crc = 0; -        d->want_input_crc = 0; - -        d->output_crc = crc32(0L, Z_NULL, 0); -        d->input_crc = crc32(0L, Z_NULL, 0); -    } - -    return zlib_return(env, res); -} - -static ERL_NIF_TERM zlib_deflateInit2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { -    zlib_data_t *d;      int level, method, windowBits, memLevel, strategy, res;      if(argc != 6 || !get_zlib_data(env, argv[0], &d)  @@ -741,39 +719,12 @@ static ERL_NIF_TERM zlib_deflate(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar  static ERL_NIF_TERM zlib_inflateInit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {      zlib_data_t *d; -    int res; -    if(argc != 1 || !get_zlib_data(env, argv[0], &d)) { -        return enif_make_badarg(env); -    } else if(!zlib_process_check(env, d)) { -        return enif_raise_exception(env, am_not_on_controlling_process); -    } else if(d->state != ST_NONE) { -        return enif_raise_exception(env, am_already_initialized); -    } - -    res = inflateInit(&d->s); - -    if(res == Z_OK) { -        d->state = ST_INFLATE; -        d->eos_seen = 0; - -        d->want_output_crc = 0; -        d->want_input_crc = 0; -        d->is_raw_stream = 0; - -        d->output_crc = crc32(0L, Z_NULL, 0); -        d->input_crc = crc32(0L, Z_NULL, 0); -    } - -    return zlib_return(env, res); -} +    int windowBits, eosBehavior, res; -static ERL_NIF_TERM zlib_inflateInit2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { -    zlib_data_t *d; -    int windowBits, res; - -    if(argc != 2 || !get_zlib_data(env, argv[0], &d) -                 || !enif_get_int(env, argv[1], &windowBits)) { +    if(argc != 3 || !get_zlib_data(env, argv[0], &d) +                 || !enif_get_int(env, argv[1], &windowBits) +                 || !enif_get_int(env, argv[2], &eosBehavior)) {          return enif_make_badarg(env);      } else if(!zlib_process_check(env, d)) {          return enif_raise_exception(env, am_not_on_controlling_process); @@ -785,6 +736,8 @@ static ERL_NIF_TERM zlib_inflateInit2(ErlNifEnv *env, int argc, const ERL_NIF_TE      if(res == Z_OK) {          d->state = ST_INFLATE; + +        d->eos_behavior = eosBehavior;          d->eos_seen = 0;          d->is_raw_stream = (windowBits < 0); @@ -934,6 +887,28 @@ static ERL_NIF_TERM zlib_inflate(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar          return enif_raise_exception(env, am_not_initialized);      } +    if(d->eos_seen) { +        int res; + +        switch(d->eos_behavior) { +        case EOS_BEHAVIOR_ERROR: +            return zlib_return(env, Z_DATA_ERROR); +        case EOS_BEHAVIOR_RESET: +            res = inflateReset(&d->s); + +            if(res != Z_OK) { +                return zlib_return(env, res); +            } + +            d->eos_seen = 0; +            break; +        case EOS_BEHAVIOR_CUT: +            zlib_reset_input(d); + +            return enif_make_tuple2(env, am_finished, enif_make_list(env, 0)); +        } +    } +      return zlib_codec(&inflate, env, d, input_chunk_size, output_chunk_size, flush);  } diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam Binary files differindex 267b5cb0a8..5048bdb846 100644 --- a/erts/preloaded/ebin/zlib.beam +++ b/erts/preloaded/ebin/zlib.beam diff --git a/erts/preloaded/src/zlib.erl b/erts/preloaded/src/zlib.erl index dca5a42779..611010550f 100644 --- a/erts/preloaded/src/zlib.erl +++ b/erts/preloaded/src/zlib.erl @@ -23,7 +23,7 @@  -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, +         inflateInit/1,inflateInit/2,inflateInit/3,           inflateSetDictionary/2,inflateGetDictionary/1, inflateReset/1,           inflate/2,inflate/3,inflateEnd/1,           inflateChunk/2,inflateChunk/1, @@ -68,6 +68,13 @@  -define(MAX_WBITS, 15). +-define(DEFAULT_MEMLEVEL, 8). +-define(DEFAULT_WBITS, 15). + +-define(EOS_BEHAVIOR_ERROR, 0). +-define(EOS_BEHAVIOR_RESET, 1). +-define(EOS_BEHAVIOR_CUT, 2). +  %% 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. @@ -130,10 +137,7 @@ deflateInit(Z) ->        Z :: zstream(),        Level :: zlevel().  deflateInit(Z, Level) -> -    deflateInit_nif(Z, arg_level(Level)). - -deflateInit_nif(_Z, _Level) -> -    erlang:nif_error(undef). +    deflateInit(Z, Level, deflated, ?DEFAULT_WBITS, ?DEFAULT_MEMLEVEL, default).  -spec deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) -> 'ok' when        Z :: zstream(), @@ -225,16 +229,21 @@ deflateEnd_nif(_Z) ->  -spec inflateInit(Z) -> 'ok' when        Z :: zstream().  inflateInit(Z) -> -    inflateInit_nif(Z). -inflateInit_nif(_Z) -> -    erlang:nif_error(undef). +    inflateInit(Z, ?DEFAULT_WBITS).  -spec inflateInit(Z, WindowBits) -> 'ok' when        Z :: zstream(),        WindowBits :: zwindowbits().  inflateInit(Z, WindowBits) -> -    inflateInit_nif(Z, WindowBits). -inflateInit_nif(_Z, _WindowBits) -> +    inflateInit(Z, WindowBits, cut). + +-spec inflateInit(Z, WindowBits, EoSBehavior) -> 'ok' when +      Z :: zstream(), +      WindowBits :: zwindowbits(), +      EoSBehavior :: error | reset | cut. +inflateInit(Z, WindowBits, EoSBehavior) -> +    inflateInit_nif(Z, arg_bitsz(WindowBits), arg_eos_behavior(EoSBehavior)). +inflateInit_nif(_Z, _WindowBits, _EoSBehavior) ->      erlang:nif_error(undef).  -spec inflateSetDictionary(Z, Dictionary) -> 'ok' when @@ -532,7 +541,7 @@ gzip(Data) ->  gunzip(Data) ->      Z = open(),      Bs = try -             inflateInit(Z, 16+?MAX_WBITS), +             inflateInit(Z, 16+?MAX_WBITS, reset),               B = inflate(Z, Data),               inflateEnd(Z),               B @@ -652,6 +661,11 @@ arg_strategy(_) -> erlang:error(bad_compression_strategy).  arg_method(deflated) -> ?Z_DEFLATED;  arg_method(_) -> erlang:error(bad_compression_method). +arg_eos_behavior(error) -> ?EOS_BEHAVIOR_ERROR; +arg_eos_behavior(reset) -> ?EOS_BEHAVIOR_RESET; +arg_eos_behavior(cut) -> ?EOS_BEHAVIOR_CUT; +arg_eos_behavior(_) -> erlang:error(bad_eos_behavior). +  -spec arg_bitsz(zwindowbits()) -> zwindowbits().  arg_bitsz(Bits) when is_integer(Bits) andalso                       ((8 =< Bits andalso Bits < 48) orelse diff --git a/lib/kernel/test/zlib_SUITE.erl b/lib/kernel/test/zlib_SUITE.erl index e246276262..d17eded811 100644 --- a/lib/kernel/test/zlib_SUITE.erl +++ b/lib/kernel/test/zlib_SUITE.erl @@ -276,10 +276,10 @@ api_inflateInit(Config) when is_list(Config) ->  			  ?m(ok,zlib:close(Z12))  		  end, lists:seq(8,15)),      ?m(?EXIT(badarg), zlib:inflateInit(gurka, -15)), -    ?m(?EXIT(already_initialized), zlib:inflateInit(Z1, 7)), -    ?m(?EXIT(already_initialized), zlib:inflateInit(Z1, -7)), -    ?m(?EXIT(already_initialized), zlib:inflateInit(Z1, 48)), -    ?m(?EXIT(already_initialized), zlib:inflateInit(Z1, -16)), +    ?m(?EXIT(bad_windowbits), zlib:inflateInit(Z1, 7)), +    ?m(?EXIT(bad_windowbits), zlib:inflateInit(Z1, -7)), +    ?m(?EXIT(bad_windowbits), zlib:inflateInit(Z1, 48)), +    ?m(?EXIT(bad_windowbits), zlib:inflateInit(Z1, -16)),      ?m(ok, zlib:close(Z1)).  %% Test inflateSetDictionary. @@ -416,6 +416,9 @@ api_inflateChunk(Config) when is_list(Config) ->      {more, Part1AsIOList} = zlib:inflateChunk(Z1, Compressed),      {more, Part2AsIOList} = zlib:inflateChunk(Z1),      {more, Part3AsIOList} = zlib:inflateChunk(Z1), + +    [] = zlib:inflateChunk(Z1), +    [] = zlib:inflateChunk(Z1),      [] = zlib:inflateChunk(Z1),      ?m(Part1, iolist_to_binary(Part1AsIOList)), @@ -483,7 +486,8 @@ api_safeInflate(Config) when is_list(Config) ->      SafeInflateLoop(zlib:safeInflate(Z1, Compressed), []), -    ?m(?EXIT(data_error), zlib:safeInflate(Z1, Compressed)), +    ?m({finished, []}, zlib:safeInflate(Z1, Compressed)), +    ?m({finished, []}, zlib:safeInflate(Z1, Compressed)),      ?m(ok, zlib:inflateReset(Z1)),      ?m(?EXIT(badarg), zlib:safeInflate(gurka, Compressed)), @@ -632,6 +636,7 @@ api_g_un_zip(Config) when is_list(Config) ->      ?m(?EXIT(badarg),zlib:gzip(not_a_binary)),      Bin = <<1,11,1,23,45>>,      Comp = zlib:gzip(Bin), +      ?m(Comp, zlib:gzip(binary_to_list(Bin))),      ?m(?EXIT(badarg), zlib:gunzip(not_a_binary)),      ?m(?EXIT(data_error), zlib:gunzip(<<171,171,171,171,171>>)), @@ -639,6 +644,14 @@ api_g_un_zip(Config) when is_list(Config) ->      ?m(Bin, zlib:gunzip(Comp)),      ?m(Bin, zlib:gunzip(binary_to_list(Comp))), +    %% RFC 1952: +    %% +    %% "A gzip file consists of a series of "members" (compressed data +    %% sets). [...] The members simply appear one after another in the file, +    %% with no additional information before, between, or after them." +    Concatenated = <<Bin/binary, Bin/binary>>, +    ?m(Concatenated, zlib:gunzip([Comp, Comp])), +      %% Bad CRC; bad length.      BadCrc = bad_crc_data(),      ?m(?EXIT(data_error),(catch zlib:gunzip(BadCrc))),  | 
