391 lines
11 KiB
C
391 lines
11 KiB
C
/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org>
|
|
* Copyright (c) 2010, Daniel Stenberg <daniel@haxx.se>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
*
|
|
* Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials
|
|
* provided with the distribution.
|
|
*
|
|
* Neither the name of the copyright holder nor the names
|
|
* of any other contributors may be used to endorse or
|
|
* promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
|
* OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "libssh2_priv.h"
|
|
#ifdef LIBSSH2_HAVE_ZLIB
|
|
# include <zlib.h>
|
|
#endif
|
|
|
|
#include "comp.h"
|
|
|
|
/* ********
|
|
* none *
|
|
******** */
|
|
|
|
/*
|
|
* comp_method_none_comp
|
|
*
|
|
* Minimalist compression: Absolutely none
|
|
*/
|
|
static int
|
|
comp_method_none_comp(LIBSSH2_SESSION *session,
|
|
unsigned char *dest,
|
|
size_t *dest_len,
|
|
const unsigned char *src,
|
|
size_t src_len,
|
|
void **abstract)
|
|
{
|
|
(void) session;
|
|
(void) abstract;
|
|
(void) dest;
|
|
(void) dest_len;
|
|
(void) src;
|
|
(void) src_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* comp_method_none_decomp
|
|
*
|
|
* Minimalist decompression: Absolutely none
|
|
*/
|
|
static int
|
|
comp_method_none_decomp(LIBSSH2_SESSION * session,
|
|
unsigned char **dest,
|
|
size_t *dest_len,
|
|
size_t payload_limit,
|
|
const unsigned char *src,
|
|
size_t src_len, void **abstract)
|
|
{
|
|
(void) session;
|
|
(void) payload_limit;
|
|
(void) abstract;
|
|
*dest = (unsigned char *) src;
|
|
*dest_len = src_len;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static const LIBSSH2_COMP_METHOD comp_method_none = {
|
|
"none",
|
|
0, /* not really compressing */
|
|
NULL,
|
|
comp_method_none_comp,
|
|
comp_method_none_decomp,
|
|
NULL
|
|
};
|
|
|
|
#ifdef LIBSSH2_HAVE_ZLIB
|
|
/* ********
|
|
* zlib *
|
|
******** */
|
|
|
|
/* Memory management wrappers
|
|
* Yes, I realize we're doing a callback to a callback,
|
|
* Deal...
|
|
*/
|
|
|
|
static voidpf
|
|
comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size)
|
|
{
|
|
LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque;
|
|
|
|
return (voidpf) LIBSSH2_ALLOC(session, items * size);
|
|
}
|
|
|
|
static void
|
|
comp_method_zlib_free(voidpf opaque, voidpf address)
|
|
{
|
|
LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque;
|
|
|
|
LIBSSH2_FREE(session, address);
|
|
}
|
|
|
|
|
|
|
|
/* libssh2_comp_method_zlib_init
|
|
* All your bandwidth are belong to us (so save some)
|
|
*/
|
|
static int
|
|
comp_method_zlib_init(LIBSSH2_SESSION * session, int compr,
|
|
void **abstract)
|
|
{
|
|
z_stream *strm;
|
|
int status;
|
|
|
|
strm = LIBSSH2_ALLOC(session, sizeof(z_stream));
|
|
if (!strm) {
|
|
return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
|
|
"Unable to allocate memory for "
|
|
"zlib compression/decompression");
|
|
}
|
|
memset(strm, 0, sizeof(z_stream));
|
|
|
|
strm->opaque = (voidpf) session;
|
|
strm->zalloc = (alloc_func) comp_method_zlib_alloc;
|
|
strm->zfree = (free_func) comp_method_zlib_free;
|
|
if (compr) {
|
|
/* deflate */
|
|
status = deflateInit(strm, Z_DEFAULT_COMPRESSION);
|
|
} else {
|
|
/* inflate */
|
|
status = inflateInit(strm);
|
|
}
|
|
|
|
if (status != Z_OK) {
|
|
LIBSSH2_FREE(session, strm);
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS,
|
|
"unhandled zlib error %d", status);
|
|
return LIBSSH2_ERROR_COMPRESS;
|
|
}
|
|
*abstract = strm;
|
|
|
|
return LIBSSH2_ERROR_NONE;
|
|
}
|
|
|
|
/*
|
|
* libssh2_comp_method_zlib_comp
|
|
*
|
|
* Compresses source to destination. Without allocation.
|
|
*/
|
|
static int
|
|
comp_method_zlib_comp(LIBSSH2_SESSION *session,
|
|
unsigned char *dest,
|
|
|
|
/* dest_len is a pointer to allow this function to
|
|
update it with the final actual size used */
|
|
size_t *dest_len,
|
|
const unsigned char *src,
|
|
size_t src_len,
|
|
void **abstract)
|
|
{
|
|
z_stream *strm = *abstract;
|
|
int out_maxlen = *dest_len;
|
|
int status;
|
|
|
|
strm->next_in = (unsigned char *) src;
|
|
strm->avail_in = src_len;
|
|
strm->next_out = dest;
|
|
strm->avail_out = out_maxlen;
|
|
|
|
status = deflate(strm, Z_PARTIAL_FLUSH);
|
|
|
|
if (status != Z_OK) {
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS,
|
|
"unhandled zlib compression error %d", status);
|
|
return _libssh2_error(session, LIBSSH2_ERROR_ZLIB,
|
|
"compression failure");
|
|
}
|
|
|
|
*dest_len = out_maxlen - strm->avail_out;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* libssh2_comp_method_zlib_decomp
|
|
*
|
|
* Decompresses source to destination. Allocates the output memory.
|
|
*/
|
|
static int
|
|
comp_method_zlib_decomp(LIBSSH2_SESSION * session,
|
|
unsigned char **dest,
|
|
size_t *dest_len,
|
|
size_t payload_limit,
|
|
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 = 8 * src_len;
|
|
int limiter = 0;
|
|
|
|
/* If strm is null, then we have not yet been initialized. */
|
|
if (strm == NULL)
|
|
return _libssh2_error(session, LIBSSH2_ERROR_COMPRESS,
|
|
"decompression unitilized");;
|
|
|
|
/* 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 decompression buffer");
|
|
while (strm->avail_in) {
|
|
int status;
|
|
|
|
status = inflate(strm, Z_PARTIAL_FLUSH);
|
|
|
|
if (status != Z_OK) {
|
|
LIBSSH2_FREE(session, out);
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS,
|
|
"unhandled zlib error %d", status);
|
|
return _libssh2_error(session, LIBSSH2_ERROR_ZLIB,
|
|
"decompression failure");
|
|
}
|
|
if (strm->avail_in) {
|
|
size_t out_ofs = out_maxlen - strm->avail_out;
|
|
char *newout;
|
|
|
|
out_maxlen += 8 * strm->avail_in;
|
|
|
|
if ((out_maxlen > (int) payload_limit) && 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 decompression buffer");
|
|
}
|
|
out = newout;
|
|
strm->next_out = (unsigned char *) out + out_ofs;
|
|
strm->avail_out += 8 * 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 = 2048;
|
|
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 "
|
|
"decompress buffer");
|
|
}
|
|
out = newout;
|
|
strm->next_out = (unsigned char *) out + out_maxlen -
|
|
grow_size;
|
|
|
|
status = inflate(strm, Z_PARTIAL_FLUSH);
|
|
|
|
if (status != Z_OK) {
|
|
LIBSSH2_FREE(session, out);
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS,
|
|
"unhandled zlib error %d", status);
|
|
return _libssh2_error(session, LIBSSH2_ERROR_ZLIB,
|
|
"decompression failure");
|
|
}
|
|
}
|
|
}
|
|
|
|
*dest = (unsigned char *) out;
|
|
*dest_len = out_maxlen - strm->avail_out;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* libssh2_comp_method_zlib_dtor
|
|
* All done, no more compression for you
|
|
*/
|
|
static int
|
|
comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compr, void **abstract)
|
|
{
|
|
z_stream *strm = *abstract;
|
|
|
|
if (strm) {
|
|
if (compr)
|
|
deflateEnd(strm);
|
|
else
|
|
inflateEnd(strm);
|
|
LIBSSH2_FREE(session, strm);
|
|
}
|
|
|
|
*abstract = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const LIBSSH2_COMP_METHOD comp_method_zlib = {
|
|
"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 */
|
|
|
|
/* If compression is enabled by the API, then this array is used which then
|
|
may allow compression if zlib is available at build time */
|
|
static const LIBSSH2_COMP_METHOD *comp_methods[] = {
|
|
#ifdef LIBSSH2_HAVE_ZLIB
|
|
&comp_method_zlib,
|
|
#endif /* LIBSSH2_HAVE_ZLIB */
|
|
&comp_method_none,
|
|
NULL
|
|
};
|
|
|
|
/* If compression is disabled by the API, then this array is used */
|
|
static const LIBSSH2_COMP_METHOD *no_comp_methods[] = {
|
|
&comp_method_none,
|
|
NULL
|
|
};
|
|
|
|
const LIBSSH2_COMP_METHOD **
|
|
_libssh2_comp_methods(LIBSSH2_SESSION *session)
|
|
{
|
|
if(session->flag.compress)
|
|
return comp_methods;
|
|
else
|
|
return no_comp_methods;
|
|
}
|