Skip to content
This repository was archived by the owner on Apr 14, 2024. It is now read-only.

Commit 796b5e9

Browse files
committed
zlib: added status constants to module; compression and decompression objects now maintain the status of the stream to decompress objects of unknown length
1 parent af0040b commit 796b5e9

File tree

3 files changed

+147
-28
lines changed

3 files changed

+147
-28
lines changed

mod/zlibmodule.c

+77-28
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ typedef struct
6868
z_stream zst;
6969
PyObject *unused_data;
7070
PyObject *unconsumed_tail;
71+
PyObject *status;
7172
int is_initialised;
7273

7374
#ifdef WITH_THREAD
@@ -100,18 +101,19 @@ newcompobject(PyTypeObject *type)
100101
compobject *self;
101102
self = PyObject_New(compobject, type);
102103
if (self == NULL)
103-
return NULL;
104+
return NULL;
104105
self->is_initialised = 0;
105106
self->unused_data = PyString_FromString("");
106107
if (self->unused_data == NULL) {
107-
Py_DECREF(self);
108-
return NULL;
108+
Py_DECREF(self);
109+
return NULL;
109110
}
110111
self->unconsumed_tail = PyString_FromString("");
111112
if (self->unconsumed_tail == NULL) {
112-
Py_DECREF(self);
113-
return NULL;
113+
Py_DECREF(self);
114+
return NULL;
114115
}
116+
self->status = PyLong_FromLong(~0);
115117

116118
#ifdef WITH_THREAD
117119
self->zlib_lock = PyThread_allocate_lock();
@@ -378,6 +380,7 @@ Comp_dealloc(compobject *self)
378380
deflateEnd(&self->zst);
379381
Py_XDECREF(self->unused_data);
380382
Py_XDECREF(self->unconsumed_tail);
383+
Py_XDECREF(self->status);
381384

382385
#ifdef WITH_THREAD
383386
PyThread_free_lock(self->zlib_lock);
@@ -393,6 +396,7 @@ Decomp_dealloc(compobject *self)
393396
inflateEnd(&self->zst);
394397
Py_XDECREF(self->unused_data);
395398
Py_XDECREF(self->unconsumed_tail);
399+
Py_XDECREF(self->status);
396400

397401
#ifdef WITH_THREAD
398402
PyThread_free_lock(self->zlib_lock);
@@ -438,27 +442,32 @@ PyZlib_objcompress(compobject *self, PyObject *args)
438442
/* while Z_OK and the output buffer is full, there might be more output,
439443
so extend the output buffer and try again */
440444
while (err == Z_OK && self->zst.avail_out == 0) {
441-
if (_PyString_Resize(&RetVal, length << 1) < 0)
442-
goto error;
443-
self->zst.next_out = (unsigned char *)PyString_AS_STRING(RetVal) \
444-
+ length;
445-
self->zst.avail_out = length;
446-
length = length << 1;
447-
448-
Py_BEGIN_ALLOW_THREADS
449-
err = deflate(&(self->zst), Z_NO_FLUSH);
450-
Py_END_ALLOW_THREADS
445+
if (_PyString_Resize(&RetVal, length << 1) < 0)
446+
goto error;
447+
self->zst.next_out = (unsigned char *)PyString_AS_STRING(RetVal) \
448+
+ length;
449+
self->zst.avail_out = length;
450+
length = length << 1;
451+
452+
Py_BEGIN_ALLOW_THREADS
453+
err = deflate(&(self->zst), Z_NO_FLUSH);
454+
Py_END_ALLOW_THREADS
451455
}
456+
457+
// Set status
458+
Py_DECREF(self->status);
459+
self->status = PyLong_FromLong(err);
460+
452461
/* We will only get Z_BUF_ERROR if the output buffer was full but
453462
there wasn't more output when we tried again, so it is not an error
454463
condition.
455464
*/
456465

457466
if (err != Z_OK && err != Z_BUF_ERROR) {
458-
zlib_error(self->zst, err, "while compressing");
459-
Py_DECREF(RetVal);
460-
RetVal = NULL;
461-
goto error;
467+
zlib_error(self->zst, err, "while compressing");
468+
Py_DECREF(RetVal);
469+
RetVal = NULL;
470+
goto error;
462471
}
463472
_PyString_Resize(&RetVal, self->zst.total_out - start_total_out);
464473

@@ -541,6 +550,10 @@ PyZlib_objdecompress(compobject *self, PyObject *args)
541550
Py_END_ALLOW_THREADS
542551
}
543552

553+
// Set status
554+
Py_DECREF(self->status);
555+
self->status = PyLong_FromLong(err);
556+
544557
/* Not all of the compressed data could be accommodated in the output buffer
545558
of specified size. Return the unconsumed tail in an attribute.*/
546559
if(max_length) {
@@ -640,6 +653,10 @@ PyZlib_flush(compobject *self, PyObject *args)
640653
err = deflate(&(self->zst), flushmode);
641654
Py_END_ALLOW_THREADS
642655
}
656+
657+
// update final status
658+
Py_DECREF(self->status);
659+
self->status = PyLong_FromLong(err);
643660

644661
/* If flushmode is Z_FINISH, we also have to call deflateEnd() to free
645662
various data structures. Note we should only get Z_STREAM_END when
@@ -709,10 +726,13 @@ PyZlib_copy(compobject *self)
709726

710727
Py_INCREF(self->unused_data);
711728
Py_INCREF(self->unconsumed_tail);
729+
Py_INCREF(self->status);
712730
Py_XDECREF(retval->unused_data);
713731
Py_XDECREF(retval->unconsumed_tail);
732+
Py_XDECREF(retval->status);
714733
retval->unused_data = self->unused_data;
715734
retval->unconsumed_tail = self->unconsumed_tail;
735+
retval->status = self->status;
716736

717737
/* Mark it as being initialized */
718738
retval->is_initialised = 1;
@@ -760,10 +780,13 @@ PyZlib_uncopy(compobject *self)
760780

761781
Py_INCREF(self->unused_data);
762782
Py_INCREF(self->unconsumed_tail);
783+
Py_INCREF(self->status);
763784
Py_XDECREF(retval->unused_data);
764785
Py_XDECREF(retval->unconsumed_tail);
786+
Py_XDECREF(retval->status);
765787
retval->unused_data = self->unused_data;
766788
retval->unconsumed_tail = self->unconsumed_tail;
789+
retval->status = self->status;
767790

768791
/* Mark it as being initialized */
769792
retval->is_initialised = 1;
@@ -877,10 +900,20 @@ static PyMethodDef Decomp_methods[] =
877900
static PyObject *
878901
Comp_getattr(compobject *self, char *name)
879902
{
880-
/* No ENTER/LEAVE_ZLIB is necessary because this fn doesn't touch
881-
internal data. */
903+
PyObject * retval;
904+
905+
ENTER_ZLIB
882906

883-
return Py_FindMethod(comp_methods, (PyObject *)self, name);
907+
if (strcmp(name, "status") == 0) {
908+
Py_INCREF(self->status);
909+
retval = self->status;
910+
} else {
911+
retval = Py_FindMethod(comp_methods, (PyObject *)self, name);
912+
}
913+
914+
LEAVE_ZLIB
915+
916+
return retval;
884917
}
885918

886919
static PyObject *
@@ -891,13 +924,17 @@ Decomp_getattr(compobject *self, char *name)
891924
ENTER_ZLIB
892925

893926
if (strcmp(name, "unused_data") == 0) {
894-
Py_INCREF(self->unused_data);
895-
retval = self->unused_data;
927+
Py_INCREF(self->unused_data);
928+
retval = self->unused_data;
896929
} else if (strcmp(name, "unconsumed_tail") == 0) {
897-
Py_INCREF(self->unconsumed_tail);
898-
retval = self->unconsumed_tail;
899-
} else
900-
retval = Py_FindMethod(Decomp_methods, (PyObject *)self, name);
930+
Py_INCREF(self->unconsumed_tail);
931+
retval = self->unconsumed_tail;
932+
} else if (strcmp(name, "status") == 0) {
933+
Py_INCREF(self->status);
934+
retval = self->status;
935+
} else {
936+
retval = Py_FindMethod(Decomp_methods, (PyObject *)self, name);
937+
}
901938

902939
LEAVE_ZLIB
903940

@@ -1047,6 +1084,18 @@ PyInit_zlib(void)
10471084
PyModule_AddIntConstant(m, "Z_SYNC_FLUSH", Z_SYNC_FLUSH);
10481085
PyModule_AddIntConstant(m, "Z_FULL_FLUSH", Z_FULL_FLUSH);
10491086

1087+
// error codes
1088+
PyModule_AddIntConstant(m, "Z_STATUS_UNSET", ~0);
1089+
PyModule_AddIntConstant(m, "Z_OK", Z_OK);
1090+
PyModule_AddIntConstant(m, "Z_STREAM_END", Z_STREAM_END);
1091+
PyModule_AddIntConstant(m, "Z_NEED_DICT", Z_NEED_DICT);
1092+
PyModule_AddIntConstant(m, "Z_ERRNO", Z_ERRNO);
1093+
PyModule_AddIntConstant(m, "Z_STREAM_ERROR", Z_STREAM_ERROR);
1094+
PyModule_AddIntConstant(m, "Z_DATA_ERROR", Z_DATA_ERROR);
1095+
PyModule_AddIntConstant(m, "Z_MEM_ERROR", Z_MEM_ERROR);
1096+
PyModule_AddIntConstant(m, "Z_BUF_ERROR", Z_BUF_ERROR);
1097+
PyModule_AddIntConstant(m, "Z_VERSION_ERROR", Z_VERSION_ERROR);
1098+
10501099
ver = PyString_FromString(ZLIB_VERSION);
10511100
if (ver != NULL)
10521101
PyModule_AddObject(m, "ZLIB_VERSION", ver);

test/mod/__init__.py

Whitespace-only changes.

test/mod/test_zlib.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""ZLib module testing"""
2+
from async.test.lib import *
3+
import async.mod.zlib as zlib
4+
5+
import sys
6+
import struct
7+
8+
class TestZLib(TestBase):
9+
def test_constants(self):
10+
# check constants
11+
assert zlib.Z_STATUS_UNSET == ~0
12+
assert hasattr(zlib, "Z_OK")
13+
assert hasattr(zlib, "Z_STREAM_END")
14+
assert hasattr(zlib, "Z_NEED_DICT")
15+
assert hasattr(zlib, "Z_ERRNO")
16+
assert hasattr(zlib, "Z_STREAM_ERROR")
17+
assert hasattr(zlib, "Z_DATA_ERROR")
18+
assert hasattr(zlib, "Z_MEM_ERROR")
19+
assert hasattr(zlib, "Z_BUF_ERROR")
20+
assert hasattr(zlib, "Z_VERSION_ERROR")
21+
22+
23+
def test_status(self):
24+
# test the newly introduced status code
25+
data = struct.pack(">L", (1<<31) + (1<<15) + (1<<2))
26+
assert len(data) == 4
27+
28+
# compress
29+
cobj = zlib.compressobj(zlib.Z_BEST_SPEED)
30+
assert cobj.status == zlib.Z_STATUS_UNSET
31+
32+
cchunk = ''
33+
for c in data:
34+
cchunk += cobj.compress(c)
35+
assert cobj.status == zlib.Z_OK
36+
# END for each databyte
37+
# its not yet done, but soon it will
38+
cchunk += cobj.flush()
39+
assert cobj.status == zlib.Z_STREAM_END
40+
41+
# zip should have added a few bytes of info
42+
assert len(cchunk) > len(data)
43+
44+
45+
# decompress - need status to determine decompession finished
46+
dcobj = zlib.decompressobj()
47+
idata = '' # inflated data
48+
for i, c in enumerate(cchunk):
49+
idata += dcobj.decompress(c)
50+
assert dcobj.status == zlib.Z_OK
51+
52+
# break if we have it
53+
if len(idata) == len(data):
54+
break
55+
# END for each character
56+
assert idata == data
57+
58+
# we should still have some bytes left
59+
assert i < len(cchunk) - 1
60+
61+
# feed the remaining data, we don't expect to decompress anything, but
62+
# want to see the status change
63+
while dcobj.status == zlib.Z_OK:
64+
i += 1
65+
assert len(dcobj.decompress(cchunk[i])) == 0
66+
# END deplete compressed stream
67+
68+
# now we are done
69+
assert dcobj.status == zlib.Z_STREAM_END
70+
assert i == len(cchunk) - 1

0 commit comments

Comments
 (0)