Patch #103373 from Donovan Baarda: This patch:

* fixes the zlib decompress sync flush bug as reported in bug #124981
  * avoids repeat calls to (in|de)flateEnd when destroying (de)compression
    objects
  * raises exception when allocating unused_data fails
  * fixes memory leak when allocating unused_data fails
  * raises exception when allocating decompress data fails
  * removes vestigial code from decompress flush now that decompression
    returns all available data
  * tidies code so object compress/decompress/flush routines are consistent
This commit is contained in:
Andrew M. Kuchling 2001-02-21 02:15:56 +00:00
parent bc8f72cccc
commit 9aff4a2ad0

View file

@ -381,7 +381,8 @@ Comp_dealloc(compobject *self)
static void static void
Decomp_dealloc(compobject *self) Decomp_dealloc(compobject *self)
{ {
inflateEnd(&self->zst); if (self->is_initialised)
inflateEnd(&self->zst);
Py_XDECREF(self->unused_data); Py_XDECREF(self->unused_data);
PyObject_Del(self); PyObject_Del(self);
} }
@ -397,49 +398,49 @@ static char comp_compress__doc__[] =
static PyObject * static PyObject *
PyZlib_objcompress(compobject *self, PyObject *args) PyZlib_objcompress(compobject *self, PyObject *args)
{ {
int err = Z_OK, inplen; int err, inplen, length = DEFAULTALLOC;
int length = DEFAULTALLOC;
PyObject *RetVal; PyObject *RetVal;
Byte *input; Byte *input;
unsigned long start_total_out; unsigned long start_total_out;
if (!PyArg_ParseTuple(args, "s#:compress", &input, &inplen)) if (!PyArg_ParseTuple(args, "s#:compress", &input, &inplen))
return NULL; return NULL;
if (!(RetVal = PyString_FromStringAndSize(NULL, length))) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data");
return NULL;
}
start_total_out = self->zst.total_out;
self->zst.avail_in = inplen; self->zst.avail_in = inplen;
self->zst.next_in = input; self->zst.next_in = input;
if (!(RetVal = PyString_FromStringAndSize(NULL, length))) { self->zst.avail_out = length;
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal);
err = deflate(&(self->zst), Z_NO_FLUSH);
/* while Z_OK and the output buffer is full, there might be more output,
so extend the output buffer and try again */
while (err == Z_OK && self->zst.avail_out == 0) {
if (_PyString_Resize(&RetVal, length << 1) == -1) {
PyErr_SetString(PyExc_MemoryError, PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data"); "Can't allocate memory to compress data");
return NULL; return NULL;
}
start_total_out = self->zst.total_out;
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal);
self->zst.avail_out = length;
while (self->zst.avail_in != 0 && err == Z_OK)
{
err = deflate(&(self->zst), Z_NO_FLUSH);
if (self->zst.avail_out <= 0) {
if (_PyString_Resize(&RetVal, length << 1) == -1) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data");
return NULL;
}
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
}
}
if (err != Z_OK)
{
if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i while compressing",
err);
else
PyErr_Format(ZlibError, "Error %i while compressing: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
} }
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
err = deflate(&(self->zst), Z_NO_FLUSH);
}
/* We will only get Z_BUF_ERROR if the output buffer was full but there
wasn't more output when we tried again, so it is not an error condition */
if (err != Z_OK && err != Z_BUF_ERROR) {
if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i while compressing",
err);
else
PyErr_Format(ZlibError, "Error %i while compressing: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
}
_PyString_Resize(&RetVal, self->zst.total_out - start_total_out); _PyString_Resize(&RetVal, self->zst.total_out - start_total_out);
return RetVal; return RetVal;
} }
@ -454,63 +455,63 @@ static char decomp_decompress__doc__[] =
static PyObject * static PyObject *
PyZlib_objdecompress(compobject *self, PyObject *args) PyZlib_objdecompress(compobject *self, PyObject *args)
{ {
int length, err, inplen; int err, inplen, length = DEFAULTALLOC;
PyObject *RetVal; PyObject *RetVal;
Byte *input; Byte *input;
unsigned long start_total_out; unsigned long start_total_out;
if (!PyArg_ParseTuple(args, "s#:decompress", &input, &inplen)) if (!PyArg_ParseTuple(args, "s#:decompress", &input, &inplen))
return NULL; return NULL;
if (!(RetVal = PyString_FromStringAndSize(NULL, length))) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data");
return NULL;
}
start_total_out = self->zst.total_out; start_total_out = self->zst.total_out;
RetVal = PyString_FromStringAndSize(NULL, DEFAULTALLOC);
self->zst.avail_in = inplen; self->zst.avail_in = inplen;
self->zst.next_in = input; self->zst.next_in = input;
self->zst.avail_out = length = DEFAULTALLOC; self->zst.avail_out = length;
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal); self->zst.next_out = (unsigned char *)PyString_AsString(RetVal);
err = Z_OK; err = inflate(&(self->zst), Z_SYNC_FLUSH);
/* while Z_OK and the output buffer is full, there might be more output,
while (self->zst.avail_in != 0 && err == Z_OK) so extend the output buffer and try again */
{ while (err == Z_OK && self->zst.avail_out == 0) {
err = inflate(&(self->zst), Z_NO_FLUSH); if (_PyString_Resize(&RetVal, length << 1) == -1) {
if (err == Z_OK && self->zst.avail_out <= 0) PyErr_SetString(PyExc_MemoryError,
{ "Can't allocate memory to compress data");
if (_PyString_Resize(&RetVal, length << 1) == -1) return NULL;
{ }
PyErr_SetString(PyExc_MemoryError, self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
"Can't allocate memory to compress data"); self->zst.avail_out = length;
return NULL; length = length << 1;
} err = inflate(&(self->zst), Z_SYNC_FLUSH);
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
}
} }
/* The end of the compressed data has been reached, so set the unused_data
if (err != Z_OK && err != Z_STREAM_END) attribute to a string containing the remainder of the data in the string.
{ Note that this is also a logical place to call inflateEnd, but the old
if (self->zst.msg == Z_NULL) behaviour of only calling it on flush() is preserved.*/
PyErr_Format(ZlibError, "Error %i while decompressing", if (err == Z_STREAM_END) {
err); Py_XDECREF(self->unused_data); /* Free the original, empty string */
else self->unused_data = PyString_FromStringAndSize(self->zst.next_in,
PyErr_Format(ZlibError, "Error %i while decompressing: %.200s", self->zst.avail_in);
err, self->zst.msg); if (self->unused_data == NULL) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to unused_data");
Py_DECREF(RetVal); Py_DECREF(RetVal);
return NULL; return NULL;
} }
/* We will only get Z_BUF_ERROR if the output buffer was full but there
if (err == Z_STREAM_END) wasn't more output when we tried again, so it is not an error condition */
{ } else if (err != Z_OK && err != Z_BUF_ERROR) {
/* The end of the compressed data has been reached, so set if (self->zst.msg == Z_NULL)
the unused_data attribute to a string containing the PyErr_Format(ZlibError, "Error %i while decompressing",
remainder of the data in the string. */ err);
int pos = self->zst.next_in - input; /* Position in the string */ else
Py_XDECREF(self->unused_data); /* Free the original, empty string */ PyErr_Format(ZlibError, "Error %i while decompressing: %.200s",
err, self->zst.msg);
self->unused_data = PyString_FromStringAndSize((char *)input+pos, Py_DECREF(RetVal);
inplen-pos); return NULL;
if (self->unused_data == NULL) return NULL;
} }
_PyString_Resize(&RetVal, self->zst.total_out - start_total_out); _PyString_Resize(&RetVal, self->zst.total_out - start_total_out);
return RetVal; return RetVal;
} }
@ -526,90 +527,70 @@ static char comp_flush__doc__[] =
static PyObject * static PyObject *
PyZlib_flush(compobject *self, PyObject *args) PyZlib_flush(compobject *self, PyObject *args)
{ {
int length=DEFAULTALLOC, err = Z_OK; int err, length=DEFAULTALLOC;
PyObject *RetVal; PyObject *RetVal;
int flushmode = Z_FINISH; int flushmode = Z_FINISH;
unsigned long start_total_out; unsigned long start_total_out;
if (!PyArg_ParseTuple(args, "|i:flush", &flushmode)) if (!PyArg_ParseTuple(args, "|i:flush", &flushmode))
return NULL; return NULL;
/* Flushing with Z_NO_FLUSH is a no-op, so there's no point in /* Flushing with Z_NO_FLUSH is a no-op, so there's no point in
doing any work at all; just return an empty string. */ doing any work at all; just return an empty string. */
if (flushmode == Z_NO_FLUSH) if (flushmode == Z_NO_FLUSH) {
{ return PyString_FromStringAndSize(NULL, 0);
return PyString_FromStringAndSize(NULL, 0); }
}
self->zst.avail_in = 0;
self->zst.next_in = Z_NULL;
if (!(RetVal = PyString_FromStringAndSize(NULL, length))) { if (!(RetVal = PyString_FromStringAndSize(NULL, length))) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data");
return NULL;
}
start_total_out = self->zst.total_out;
self->zst.avail_in = 0;
self->zst.avail_out = length;
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal);
err = deflate(&(self->zst), flushmode);
/* while Z_OK and the output buffer is full, there might be more output,
so extend the output buffer and try again */
while (err == Z_OK && self->zst.avail_out == 0) {
if (_PyString_Resize(&RetVal, length << 1) == -1) {
PyErr_SetString(PyExc_MemoryError, PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data"); "Can't allocate memory to compress data");
return NULL; return NULL;
}
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
err = deflate(&(self->zst), flushmode);
} }
start_total_out = self->zst.total_out; /* If flushmode is Z_FINISH, we also have to call deflateEnd() to free
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal); various data structures. Note we should only get Z_STREAM_END when
self->zst.avail_out = length; flushmode is Z_FINISH, but checking both for safety*/
if (err == Z_STREAM_END && flushmode == Z_FINISH) {
/* When flushing the zstream, there's no input data.
If zst.avail_out == 0, that means that more output space is
needed to complete the flush operation. */
while (1) {
err = deflate(&(self->zst), flushmode);
/* If the output is Z_OK, and there's still room in the output
buffer, then the flush is complete. */
if ( (err == Z_OK) && self->zst.avail_out > 0) break;
/* A nonzero return indicates some sort of error (but see
the comment for the error handler below) */
if ( err != Z_OK ) break;
/* There's no space left for output, so increase the buffer and loop
again */
if (_PyString_Resize(&RetVal, length << 1) == -1) {
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to compress data");
return NULL;
}
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
}
/* Raise an exception indicating an error. The condition for
detecting a error is kind of complicated; Z_OK indicates no
error, but if the flushmode is Z_FINISH, then Z_STREAM_END is
also not an error. */
if (err!=Z_OK && !(flushmode == Z_FINISH && err == Z_STREAM_END) )
{
if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i while flushing",
err);
else
PyErr_Format(ZlibError, "Error %i while flushing: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
}
/* If flushmode is Z_FINISH, we also have to call deflateEnd() to
free various data structures */
if (flushmode == Z_FINISH) {
err=deflateEnd(&(self->zst)); err=deflateEnd(&(self->zst));
if (err!=Z_OK) { if (err!=Z_OK) {
if (self->zst.msg == Z_NULL) if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i from deflateEnd()", PyErr_Format(ZlibError, "Error %i from deflateEnd()",
err); err);
else else
PyErr_Format(ZlibError, PyErr_Format(ZlibError,"Error %i from deflateEnd(): %.200s",
"Error %i from deflateEnd(): %.200s",
err, self->zst.msg); err, self->zst.msg);
Py_DECREF(RetVal); Py_DECREF(RetVal);
return NULL; return NULL;
} }
self->is_initialised = 0;
/* We will only get Z_BUF_ERROR if the output buffer was full but there
wasn't more output when we tried again, so it is not an error condition */
} else if (err!=Z_OK && err!=Z_BUF_ERROR) {
if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i while flushing",
err);
else
PyErr_Format(ZlibError, "Error %i while flushing: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
} }
_PyString_Resize(&RetVal, self->zst.total_out - start_total_out); _PyString_Resize(&RetVal, self->zst.total_out - start_total_out);
return RetVal; return RetVal;
@ -622,71 +603,27 @@ static char decomp_flush__doc__[] =
static PyObject * static PyObject *
PyZlib_unflush(compobject *self, PyObject *args) PyZlib_unflush(compobject *self, PyObject *args)
/*decompressor flush is a no-op because all pending data would have been
flushed by the decompress method. However, this routine previously called
inflateEnd, causing any further decompress or flush calls to raise
exceptions. This behaviour has been preserved.*/
{ {
int length=0, err; int err;
PyObject *RetVal;
if (!PyArg_ParseTuple(args, "")) if (!PyArg_ParseTuple(args, ""))
return NULL; return NULL;
if (!(RetVal = PyString_FromStringAndSize(NULL, DEFAULTALLOC)))
{
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to decompress data");
return NULL;
}
self->zst.avail_in=0;
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal);
length = self->zst.avail_out = DEFAULTALLOC;
/* I suspect that Z_BUF_ERROR is the only error code we need to check for
in the following loop, but will leave the Z_OK in for now to avoid
destabilizing this function. --amk */
err = Z_OK;
while ( err == Z_OK )
{
err = inflate(&(self->zst), Z_FINISH);
if ( ( err == Z_OK || err == Z_BUF_ERROR ) && self->zst.avail_out == 0)
{
if (_PyString_Resize(&RetVal, length << 1) == -1)
{
PyErr_SetString(PyExc_MemoryError,
"Can't allocate memory to decompress data");
return NULL;
}
self->zst.next_out = (unsigned char *)PyString_AsString(RetVal) + length;
self->zst.avail_out = length;
length = length << 1;
err = Z_OK;
}
}
if (err!=Z_STREAM_END)
{
if (self->zst.msg == Z_NULL)
PyErr_Format(ZlibError, "Error %i while decompressing",
err);
else
PyErr_Format(ZlibError, "Error %i while decompressing: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
}
err=inflateEnd(&(self->zst)); err=inflateEnd(&(self->zst));
if (err!=Z_OK) if (err!=Z_OK) {
{ if (self->zst.msg == Z_NULL)
if (self->zst.msg == Z_NULL) PyErr_Format(ZlibError, "Error %i from inflateEnd()",
PyErr_Format(ZlibError, err);
"Error %i while flushing decompression object", else
err); PyErr_Format(ZlibError, "Error %i from inflateEnd(): %.200s",
else err, self->zst.msg);
PyErr_Format(ZlibError, return NULL;
"Error %i while flushing decompression object: %.200s",
err, self->zst.msg);
Py_DECREF(RetVal);
return NULL;
} }
_PyString_Resize(&RetVal, self->is_initialised = 0;
(char *)self->zst.next_out - PyString_AsString(RetVal)); return PyString_FromStringAndSize(NULL, 0);
return RetVal;
} }
static PyMethodDef comp_methods[] = static PyMethodDef comp_methods[] =