Skip to content

Commit 5e32254

Browse files
committed
correction gzip
1 parent cf5385e commit 5e32254

4 files changed

Lines changed: 111 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ Notes:
597597
- `enableGzipAcceptEncoding(false)` removes `Accept-Encoding` from the request's header list (or call `request.removeHeader("Accept-Encoding")`).
598598
- `Content-Length` (when present) refers to the *compressed* payload size; completion detection still follows the wire length.
599599
- RAM impact: enabling gzip decode allocates an internal 32KB sliding window per active gzip-decoded response (plus small state).
600+
- Integrity: the gzip trailer is verified (CRC32 + ISIZE); corrupted payloads raise `GZIP_DECODE_FAILED`.
600601

601602
### HTTPS quick reference
602603

src/GzipDecoder.cpp

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
GzipDecoder::GzipDecoder()
88
: _state(State::kError), _headerStage(HeaderStage::kFixed10), _error("Gzip decode disabled"), _fixedLen(0),
99
_flags(0), _extraLenRead(0), _extraRemaining(0), _needName(false), _needComment(false), _needHcrc(false),
10-
_trailerLen(0), _dict(nullptr), _dictOfs(0), _decomp(nullptr) {
10+
_trailerLen(0), _crc32(0), _outSize(0), _dict(nullptr), _dictOfs(0), _decomp(nullptr) {
1111
memset(_fixed, 0, sizeof(_fixed));
1212
memset(_extraLenBytes, 0, sizeof(_extraLenBytes));
1313
memset(_trailer, 0, sizeof(_trailer));
@@ -73,10 +73,42 @@ static constexpr uint8_t kGzipFlagExtra = 0x04;
7373
static constexpr uint8_t kGzipFlagName = 0x08;
7474
static constexpr uint8_t kGzipFlagComment = 0x10;
7575

76+
static uint32_t readLe32(const uint8_t* p) {
77+
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
78+
}
79+
80+
static uint32_t* crc32Table() {
81+
static bool inited = false;
82+
static uint32_t table[256];
83+
if (inited)
84+
return table;
85+
for (uint32_t i = 0; i < 256; ++i) {
86+
uint32_t c = i;
87+
for (uint32_t k = 0; k < 8; ++k) {
88+
if (c & 1U)
89+
c = 0xEDB88320U ^ (c >> 1);
90+
else
91+
c >>= 1;
92+
}
93+
table[i] = c;
94+
}
95+
inited = true;
96+
return table;
97+
}
98+
99+
static uint32_t crc32Update(uint32_t crc, const uint8_t* data, size_t len) {
100+
uint32_t c = crc;
101+
uint32_t* t = crc32Table();
102+
for (size_t i = 0; i < len; ++i) {
103+
c = t[(c ^ data[i]) & 0xFFU] ^ (c >> 8);
104+
}
105+
return c;
106+
}
107+
76108
GzipDecoder::GzipDecoder()
77109
: _state(State::kHeader), _headerStage(HeaderStage::kFixed10), _error(nullptr), _fixedLen(0), _flags(0),
78110
_extraLenRead(0), _extraRemaining(0), _needName(false), _needComment(false), _needHcrc(false), _trailerLen(0),
79-
_dict(nullptr), _dictOfs(0), _decomp(nullptr) {
111+
_crc32(0), _outSize(0), _dict(nullptr), _dictOfs(0), _decomp(nullptr) {
80112
memset(_fixed, 0, sizeof(_fixed));
81113
memset(_extraLenBytes, 0, sizeof(_extraLenBytes));
82114
memset(_trailer, 0, sizeof(_trailer));
@@ -110,6 +142,8 @@ void GzipDecoder::reset() {
110142

111143
_trailerLen = 0;
112144
_dictOfs = 0;
145+
_crc32 = 0xFFFFFFFFU;
146+
_outSize = 0;
113147
}
114148

115149
bool GzipDecoder::begin() {
@@ -288,6 +322,20 @@ GzipDecoder::Result GzipDecoder::consumeTrailer(const uint8_t* in, size_t inLen,
288322
}
289323
if (_trailerLen < kGzipTrailerSize)
290324
return Result::kNeedMoreInput;
325+
uint32_t expectedCrc = readLe32(_trailer);
326+
uint32_t expectedISize = readLe32(_trailer + 4);
327+
uint32_t gotCrc = _crc32 ^ 0xFFFFFFFFU;
328+
uint32_t gotISize = _outSize;
329+
330+
if (expectedCrc != gotCrc) {
331+
setError("Gzip CRC32 mismatch");
332+
return Result::kError;
333+
}
334+
if (expectedISize != gotISize) {
335+
setError("Gzip ISIZE mismatch");
336+
return Result::kError;
337+
}
338+
291339
_state = State::kDone;
292340
return Result::kDone;
293341
}
@@ -354,8 +402,11 @@ GzipDecoder::Result GzipDecoder::write(const uint8_t* in, size_t inLen, size_t*
354402
totalConsumed += srcBufSize;
355403
*inConsumed = totalConsumed;
356404
if (dstBufSize > 0) {
357-
*outPtr = reinterpret_cast<const uint8_t*>(dict + _dictOfs);
405+
const uint8_t* produced = reinterpret_cast<const uint8_t*>(dict + _dictOfs);
406+
*outPtr = produced;
358407
*outLen = dstBufSize;
408+
_crc32 = crc32Update(_crc32, produced, dstBufSize);
409+
_outSize += (uint32_t)dstBufSize;
359410
_dictOfs = (_dictOfs + dstBufSize) & (kTinflDictSize - 1);
360411
}
361412

src/GzipDecoder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class GzipDecoder {
7171
uint8_t _trailer[8];
7272
size_t _trailerLen;
7373

74+
uint32_t _crc32;
75+
uint32_t _outSize;
76+
7477
// Deflate (tinfl) state
7578
void* _dict;
7679
size_t _dictOfs;

test/test_gzip_decode_native/test_main.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,57 @@ static void test_truncated_gzip_fails() {
108108
TEST_ASSERT_TRUE(strlen(dec.lastError()) > 0);
109109
}
110110

111+
static void test_gzip_crc_mismatch_fails() {
112+
std::vector<uint8_t> gz(kGzipHello, kGzipHello + sizeof(kGzipHello));
113+
// Flip one CRC byte in trailer (bytes 4 from end..)
114+
gz[gz.size() - 8] ^= 0x01;
115+
116+
GzipDecoder dec;
117+
TEST_ASSERT_TRUE(dec.begin());
118+
119+
size_t offset = 0;
120+
std::string out;
121+
while (offset < gz.size()) {
122+
const uint8_t* outPtr = nullptr;
123+
size_t outLen = 0;
124+
size_t consumed = 0;
125+
GzipDecoder::Result r = dec.write(gz.data() + offset, gz.size() - offset, &consumed, &outPtr, &outLen, true);
126+
if (outLen > 0)
127+
out.append(reinterpret_cast<const char*>(outPtr), outLen);
128+
if (r == GzipDecoder::Result::kError)
129+
break;
130+
TEST_ASSERT_TRUE(consumed > 0 || outLen > 0);
131+
offset += consumed;
132+
}
133+
134+
TEST_ASSERT_FALSE_MESSAGE(dec.isDone(), "should not be done on CRC mismatch");
135+
TEST_ASSERT_TRUE(strlen(dec.lastError()) > 0);
136+
}
137+
138+
static void test_gzip_isize_mismatch_fails() {
139+
std::vector<uint8_t> gz(kGzipHello, kGzipHello + sizeof(kGzipHello));
140+
// Flip one ISIZE byte in trailer (last 4 bytes).
141+
gz[gz.size() - 1] ^= 0x01;
142+
143+
GzipDecoder dec;
144+
TEST_ASSERT_TRUE(dec.begin());
145+
146+
size_t offset = 0;
147+
while (offset < gz.size()) {
148+
const uint8_t* outPtr = nullptr;
149+
size_t outLen = 0;
150+
size_t consumed = 0;
151+
GzipDecoder::Result r = dec.write(gz.data() + offset, gz.size() - offset, &consumed, &outPtr, &outLen, true);
152+
if (r == GzipDecoder::Result::kError)
153+
break;
154+
TEST_ASSERT_TRUE(consumed > 0 || outLen > 0);
155+
offset += consumed;
156+
}
157+
158+
TEST_ASSERT_FALSE(dec.isDone());
159+
TEST_ASSERT_TRUE(strlen(dec.lastError()) > 0);
160+
}
161+
111162
int main(int argc, char** argv) {
112163
(void)argc;
113164
(void)argv;
@@ -117,5 +168,7 @@ int main(int argc, char** argv) {
117168
RUN_TEST(test_gzip_decode_byte_by_byte);
118169
RUN_TEST(test_gzip_header_with_fname);
119170
RUN_TEST(test_truncated_gzip_fails);
171+
RUN_TEST(test_gzip_crc_mismatch_fails);
172+
RUN_TEST(test_gzip_isize_mismatch_fails);
120173
return UNITY_END();
121174
}

0 commit comments

Comments
 (0)