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