1 /***********************************************************************************************************************************
2 Gz Decompress
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <stdio.h>
7 #include <zlib.h>
8
9 #include "common/compress/gz/common.h"
10 #include "common/compress/gz/decompress.h"
11 #include "common/debug.h"
12 #include "common/io/filter/filter.h"
13 #include "common/log.h"
14 #include "common/macro.h"
15 #include "common/memContext.h"
16 #include "common/type/object.h"
17
18 /***********************************************************************************************************************************
19 Filter type constant
20 ***********************************************************************************************************************************/
21 STRING_EXTERN(GZ_DECOMPRESS_FILTER_TYPE_STR, GZ_DECOMPRESS_FILTER_TYPE);
22
23 /***********************************************************************************************************************************
24 Object type
25 ***********************************************************************************************************************************/
26 typedef struct GzDecompress
27 {
28 MemContext *memContext; // Context to store data
29 z_stream stream; // Decompression stream state
30
31 int result; // Result of last operation
32 bool inputSame; // Is the same input required on the next process call?
33 bool done; // Is decompression done?
34 } GzDecompress;
35
36 /***********************************************************************************************************************************
37 Macros for function logging
38 ***********************************************************************************************************************************/
39 static String *
gzDecompressToLog(const GzDecompress * this)40 gzDecompressToLog(const GzDecompress *this)
41 {
42 return strNewFmt(
43 "{inputSame: %s, done: %s, availIn: %u}", cvtBoolToConstZ(this->inputSame), cvtBoolToConstZ(this->done),
44 this->stream.avail_in);
45 }
46
47 #define FUNCTION_LOG_GZ_DECOMPRESS_TYPE \
48 GzDecompress *
49 #define FUNCTION_LOG_GZ_DECOMPRESS_FORMAT(value, buffer, bufferSize) \
50 FUNCTION_LOG_STRING_OBJECT_FORMAT(value, gzDecompressToLog, buffer, bufferSize)
51
52 /***********************************************************************************************************************************
53 Free inflate stream
54 ***********************************************************************************************************************************/
55 static void
gzDecompressFreeResource(THIS_VOID)56 gzDecompressFreeResource(THIS_VOID)
57 {
58 THIS(GzDecompress);
59
60 FUNCTION_LOG_BEGIN(logLevelTrace);
61 FUNCTION_LOG_PARAM(GZ_DECOMPRESS, this);
62 FUNCTION_LOG_END();
63
64 ASSERT(this != NULL);
65
66 inflateEnd(&this->stream);
67
68 FUNCTION_LOG_RETURN_VOID();
69 }
70
71 /***********************************************************************************************************************************
72 Decompress data
73 ***********************************************************************************************************************************/
74 static void
gzDecompressProcess(THIS_VOID,const Buffer * compressed,Buffer * uncompressed)75 gzDecompressProcess(THIS_VOID, const Buffer *compressed, Buffer *uncompressed)
76 {
77 THIS(GzDecompress);
78
79 FUNCTION_LOG_BEGIN(logLevelTrace);
80 FUNCTION_LOG_PARAM(GZ_DECOMPRESS, this);
81 FUNCTION_LOG_PARAM(BUFFER, compressed);
82 FUNCTION_LOG_PARAM(BUFFER, uncompressed);
83 FUNCTION_LOG_END();
84
85 ASSERT(this != NULL);
86 ASSERT(uncompressed != NULL);
87
88 // There should never be a flush because in a valid compressed stream the end of data can be determined and done will be set.
89 // If a flush is received it means the compressed stream terminated early, e.g. a zero-length or truncated file.
90 if (compressed == NULL)
91 THROW(FormatError, "unexpected eof in compressed data");
92
93 if (!this->inputSame)
94 {
95 this->stream.avail_in = (unsigned int)bufUsed(compressed);
96
97 // Not all versions of zlib (and none by default) will accept const input buffers
98 this->stream.next_in = UNCONSTIFY(unsigned char *, bufPtrConst(compressed));
99 }
100
101 this->stream.avail_out = (unsigned int)bufRemains(uncompressed);
102 this->stream.next_out = bufPtr(uncompressed) + bufUsed(uncompressed);
103
104 this->result = gzError(inflate(&this->stream, Z_NO_FLUSH));
105
106 // Set buffer used space
107 bufUsedSet(uncompressed, bufSize(uncompressed) - (size_t)this->stream.avail_out);
108
109 // Is decompression done?
110 this->done = this->result == Z_STREAM_END;
111
112 // Is the same input expected on the next call?
113 this->inputSame = this->done ? false : this->stream.avail_in != 0;
114
115 FUNCTION_LOG_RETURN_VOID();
116 }
117
118 /***********************************************************************************************************************************
119 Is decompress done?
120 ***********************************************************************************************************************************/
121 static bool
gzDecompressDone(const THIS_VOID)122 gzDecompressDone(const THIS_VOID)
123 {
124 THIS(const GzDecompress);
125
126 FUNCTION_TEST_BEGIN();
127 FUNCTION_TEST_PARAM(GZ_DECOMPRESS, this);
128 FUNCTION_TEST_END();
129
130 ASSERT(this != NULL);
131
132 FUNCTION_TEST_RETURN(this->done);
133 }
134
135 /***********************************************************************************************************************************
136 Is the same input required on the next process call?
137 ***********************************************************************************************************************************/
138 static bool
gzDecompressInputSame(const THIS_VOID)139 gzDecompressInputSame(const THIS_VOID)
140 {
141 THIS(const GzDecompress);
142
143 FUNCTION_TEST_BEGIN();
144 FUNCTION_TEST_PARAM(GZ_DECOMPRESS, this);
145 FUNCTION_TEST_END();
146
147 ASSERT(this != NULL);
148
149 FUNCTION_TEST_RETURN(this->inputSame);
150 }
151
152 /**********************************************************************************************************************************/
153 IoFilter *
gzDecompressNew(void)154 gzDecompressNew(void)
155 {
156 FUNCTION_LOG_VOID(logLevelTrace);
157
158 IoFilter *this = NULL;
159
160 MEM_CONTEXT_NEW_BEGIN("GzDecompress")
161 {
162 // Allocate state and set context
163 GzDecompress *driver = memNew(sizeof(GzDecompress));
164
165 *driver = (GzDecompress)
166 {
167 .memContext = MEM_CONTEXT_NEW(),
168 .stream = {.zalloc = NULL},
169 };
170
171 // Create gz stream
172 gzError(driver->result = inflateInit2(&driver->stream, WANT_GZ | WINDOW_BITS));
173
174 // Set free callback to ensure gz context is freed
175 memContextCallbackSet(driver->memContext, gzDecompressFreeResource, driver);
176
177 // Create filter interface
178 this = ioFilterNewP(
179 GZ_DECOMPRESS_FILTER_TYPE_STR, driver, NULL, .done = gzDecompressDone, .inOut = gzDecompressProcess,
180 .inputSame = gzDecompressInputSame);
181 }
182 MEM_CONTEXT_NEW_END();
183
184 FUNCTION_LOG_RETURN(IO_FILTER, this);
185 }
186