diff --git a/src/comp.c b/src/comp.c index da2f9f6..c4e8c81 100644 --- a/src/comp.c +++ b/src/comp.c @@ -1,4 +1,5 @@ /* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2010, Daniel Stenberg * All rights reserved. * * Redistribution and use in source and binary forms, @@ -80,6 +81,7 @@ static const LIBSSH2_COMP_METHOD comp_method_none = { 0, /* not really compressing */ NULL, comp_method_none_comp, + comp_method_none_comp, NULL }; @@ -149,10 +151,10 @@ comp_method_zlib_init(LIBSSH2_SESSION * session, int compress, return LIBSSH2_ERROR_NONE; } - - -/* libssh2_comp_method_zlib_comp - * zlib, a compression standard for all occasions +/* + * libssh2_comp_method_zlib_comp + * + * Compresses source to destination. Without any allocation. */ static int comp_method_zlib_comp(LIBSSH2_SESSION * session, @@ -290,6 +292,147 @@ comp_method_zlib_comp(LIBSSH2_SESSION * session, return 0; } +/* + * libssh2_comp_method_zlib_decomp + * + * Decompresses source to destination. Allocates the output memory. + */ +static int +comp_method_zlib_decomp(LIBSSH2_SESSION * session, + int compress, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + int *free_dest, + const unsigned char *src, + size_t src_len, void **abstract) +{ + z_stream *strm = *abstract; + /* A short-term alloc of a full data chunk is better than a series of + reallocs */ + char *out; + int out_maxlen = compress ? (src_len + 4) : (2 * src_len); + int limiter = 0; + + /* If strm is null, then we have not yet been initialized. */ + if (strm == NULL) { + *dest = (unsigned char *) src; + *dest_len = src_len; + + *free_dest = 0; + return 0; + } + + /* In practice they never come smaller than this */ + if (out_maxlen < 25) { + out_maxlen = 25; + } + + if (out_maxlen > (int) payload_limit) { + out_maxlen = payload_limit; + } + + strm->next_in = (unsigned char *) src; + strm->avail_in = src_len; + strm->next_out = (unsigned char *) LIBSSH2_ALLOC(session, out_maxlen); + out = (char *) strm->next_out; + strm->avail_out = out_maxlen; + if (!strm->next_out) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate compression/decompression " + "buffer"); + } + while (strm->avail_in) { + int status; + + if (compress) { + status = deflate(strm, Z_PARTIAL_FLUSH); + } else { + status = inflate(strm, Z_PARTIAL_FLUSH); + } + if (status != Z_OK) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "compress/decompression failure"); + } + if (strm->avail_in) { + size_t out_ofs = out_maxlen - strm->avail_out; + char *newout; + + out_maxlen += + compress ? (strm->avail_in + 4) : (2 * strm->avail_in); + + if ((out_maxlen > (int) payload_limit) && !compress && limiter++) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "Excessive growth in decompression phase"); + } + + newout = LIBSSH2_REALLOC(session, out, out_maxlen); + if (!newout) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to expand compress/" + "decompression buffer"); + } + out = newout; + strm->next_out = (unsigned char *) out + out_ofs; + strm->avail_out += + compress ? (strm->avail_in + 4) : (2 * strm->avail_in); + } else + while (!strm->avail_out) { + /* Done with input, might be a byte or two in internal buffer + * during compress. Or potentially many bytes if it's a + * decompress + */ + int grow_size = compress ? 8 : 1024; + char *newout; + + if (out_maxlen >= (int) payload_limit) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "Excessive growth in decompression " + "phase"); + } + + if (grow_size > (int) (payload_limit - out_maxlen)) { + grow_size = payload_limit - out_maxlen; + } + + out_maxlen += grow_size; + strm->avail_out = grow_size; + + newout = LIBSSH2_REALLOC(session, out, out_maxlen); + if (!newout) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to expand final compress/" + "decompress buffer"); + } + out = newout; + strm->next_out = (unsigned char *) out + out_maxlen - + grow_size; + + if (compress) { + status = deflate(strm, Z_PARTIAL_FLUSH); + } else { + status = inflate(strm, Z_PARTIAL_FLUSH); + } + if (status != Z_OK) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "compress/decompression failure"); + } + } + } + + *dest = (unsigned char *) out; + *dest_len = out_maxlen - strm->avail_out; + *free_dest = 1; + + return 0; +} + /* libssh2_comp_method_zlib_dtor * All done, no more compression for you @@ -322,6 +465,7 @@ static const LIBSSH2_COMP_METHOD comp_method_zlib = { 1, /* yes, this compresses */ comp_method_zlib_init, comp_method_zlib_comp, + comp_method_zlib_decomp, comp_method_zlib_dtor, }; #endif /* LIBSSH2_HAVE_ZLIB */ diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index f1aaf08..00da23a 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -1007,6 +1007,11 @@ struct _LIBSSH2_COMP_METHOD size_t *dest_len, size_t payload_limit, int *free_dest, const unsigned char *src, size_t src_len, void **abstract); + int (*decomp) (LIBSSH2_SESSION * session, int compress, + unsigned char **dest, + size_t *dest_len, size_t payload_limit, + int *free_dest, const unsigned char *src, + size_t src_len, void **abstract); int (*dtor) (LIBSSH2_SESSION * session, int compress, void **abstract); };