/src/httrack/src/htscodec.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------ */ |
2 | | /* |
3 | | HTTrack Website Copier, Offline Browser for Windows and Unix |
4 | | Copyright (C) 2026 Xavier Roche and other contributors |
5 | | |
6 | | SPDX-License-Identifier: GPL-3.0-or-later |
7 | | |
8 | | This program is free software: you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation, either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | |
21 | | Ethical use: we kindly ask that you NOT use this software to harvest email |
22 | | addresses or to collect any other private information about people. Doing so |
23 | | would dishonor our work and waste the many hours we have spent on it. |
24 | | |
25 | | Please visit our Website: http://www.httrack.com |
26 | | */ |
27 | | |
28 | | /* ------------------------------------------------------------ */ |
29 | | /* File: HTTP content codings (gzip/deflate, brotli, zstd) */ |
30 | | /* Author: Xavier Roche */ |
31 | | /* ------------------------------------------------------------ */ |
32 | | |
33 | | /* Internal engine bytecode */ |
34 | | #define HTS_INTERNAL_BYTECODE |
35 | | |
36 | | #include "htsbase.h" |
37 | | #include "htscore.h" |
38 | | #include "htscodec.h" |
39 | | #include "htszlib.h" |
40 | | |
41 | | #if HTS_USEBROTLI |
42 | | #include <brotli/decode.h> |
43 | | #endif |
44 | | |
45 | | #if HTS_USEZSTD |
46 | | #include <zstd.h> |
47 | | /* 8 MB, the window an HTTP zstd decoder must honor (RFC 9659); libzstd |
48 | | defaults to 128 MB. */ |
49 | | #define HTS_ZSTD_WINDOWLOG_MAX 23 |
50 | | #endif |
51 | | |
52 | | /* Decoded-size budget, bounding a bomb: brotli and zstd reach a million to one. |
53 | | Deflate cannot pass 1032x, so it only meets this at the 2 GiB ceiling. */ |
54 | 0 | #define HTS_CODEC_MAX_RATIO 4096 |
55 | 0 | #define HTS_CODEC_MIN_MAXOUT (1024 * 1024) |
56 | | |
57 | 0 | LLint hts_codec_maxout(LLint in_size) { |
58 | 0 | LLint maxout; |
59 | |
|
60 | 0 | if (in_size <= 0 || in_size > INT_MAX / HTS_CODEC_MAX_RATIO) |
61 | 0 | maxout = INT_MAX; |
62 | 0 | else |
63 | 0 | maxout = in_size * HTS_CODEC_MAX_RATIO; |
64 | 0 | if (maxout < HTS_CODEC_MIN_MAXOUT) |
65 | 0 | maxout = HTS_CODEC_MIN_MAXOUT; |
66 | 0 | return maxout; |
67 | 0 | } |
68 | | |
69 | 0 | hts_codec hts_codec_parse(const char *encoding) { |
70 | 0 | if (encoding == NULL || encoding[0] == '\0' || |
71 | 0 | strfield2(encoding, "identity")) |
72 | 0 | return HTS_CODEC_IDENTITY; |
73 | 0 | if (strfield2(encoding, "gzip") || strfield2(encoding, "x-gzip") || |
74 | 0 | strfield2(encoding, "deflate") || strfield2(encoding, "x-deflate")) |
75 | 0 | return HTS_CODEC_DEFLATE; |
76 | 0 | if (strfield2(encoding, "br")) |
77 | 0 | return HTS_USEBROTLI ? HTS_CODEC_BROTLI : HTS_CODEC_UNSUPPORTED; |
78 | 0 | if (strfield2(encoding, "zstd")) |
79 | 0 | return HTS_USEZSTD ? HTS_CODEC_ZSTD : HTS_CODEC_UNSUPPORTED; |
80 | 0 | if (strfield2(encoding, "compress") || strfield2(encoding, "x-compress")) |
81 | 0 | return HTS_CODEC_UNSUPPORTED; /* LZW: never advertised, no decoder here */ |
82 | | /* Not a coding at all: broken servers put charsets and the like here, and |
83 | | the plain body they sent must survive it. */ |
84 | 0 | return HTS_CODEC_IDENTITY; |
85 | 0 | } |
86 | | |
87 | | #if HTS_USEBROTLI |
88 | | #define HTS_AE_BROTLI ", br" |
89 | | #else |
90 | | #define HTS_AE_BROTLI "" |
91 | | #endif |
92 | | #if HTS_USEZSTD |
93 | | #define HTS_AE_ZSTD ", zstd" |
94 | | #else |
95 | | #define HTS_AE_ZSTD "" |
96 | | #endif |
97 | | |
98 | 0 | const char *hts_acceptencoding(hts_boolean compressible, hts_boolean secure) { |
99 | 0 | if (!compressible) |
100 | 0 | return "identity"; |
101 | | /* br and zstd over TLS only, as browsers do: a cleartext intermediary that |
102 | | rewrites a coding it can not read would corrupt the mirror. */ |
103 | 0 | if (secure) |
104 | 0 | return "gzip, deflate" HTS_AE_BROTLI HTS_AE_ZSTD ", identity;q=0.9"; |
105 | 0 | return "gzip, deflate, identity;q=0.9"; |
106 | 0 | } |
107 | | |
108 | 0 | hts_boolean hts_codec_is_archive_ext(hts_codec codec, const char *ext) { |
109 | 0 | if (ext == NULL || ext[0] == '\0') |
110 | 0 | return HTS_FALSE; |
111 | 0 | switch (codec) { |
112 | 0 | case HTS_CODEC_DEFLATE: |
113 | 0 | return strfield2(ext, "gz") || strfield2(ext, "tgz") ? HTS_TRUE : HTS_FALSE; |
114 | 0 | case HTS_CODEC_BROTLI: |
115 | 0 | return strfield2(ext, "br") ? HTS_TRUE : HTS_FALSE; |
116 | 0 | case HTS_CODEC_ZSTD: |
117 | 0 | return strfield2(ext, "zst") || strfield2(ext, "tzst") ? HTS_TRUE |
118 | 0 | : HTS_FALSE; |
119 | 0 | default: |
120 | 0 | return HTS_FALSE; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | #if HTS_USEBROTLI || HTS_USEZSTD |
125 | | /* Append produced bytes to out under the decoded-size budget, advancing *total. |
126 | | HTS_FALSE on a short write or once the budget is exceeded (a bomb); the |
127 | | over-budget bytes are never written. */ |
128 | | static hts_boolean codec_sink(FILE *out, const void *buf, size_t produced, |
129 | | LLint *total, LLint maxout) { |
130 | | if (produced == 0) |
131 | | return HTS_TRUE; |
132 | | *total += (LLint) produced; |
133 | | if (*total > maxout) |
134 | | return HTS_FALSE; |
135 | | return fwrite(buf, 1, produced, out) == produced ? HTS_TRUE : HTS_FALSE; |
136 | | } |
137 | | #endif |
138 | | |
139 | | #if HTS_USEBROTLI |
140 | | static int codec_unpack_brotli(FILE *in, FILE *out, LLint maxout) { |
141 | | BrotliDecoderState *const state = |
142 | | BrotliDecoderCreateInstance(NULL, NULL, NULL); |
143 | | hts_boolean ok = HTS_TRUE, done = HTS_FALSE; |
144 | | LLint total = 0; |
145 | | |
146 | | if (state == NULL) |
147 | | return -1; |
148 | | while (ok && !done) { |
149 | | unsigned char BIGSTK inbuf[8192]; |
150 | | size_t avail_in = fread(inbuf, 1, sizeof(inbuf), in); |
151 | | const uint8_t *next_in = inbuf; |
152 | | |
153 | | if (avail_in == 0) |
154 | | break; /* EOF; a stream still hungry for input is truncated */ |
155 | | for (;;) { |
156 | | unsigned char BIGSTK outbuf[8192]; |
157 | | uint8_t *next_out = outbuf; |
158 | | size_t avail_out = sizeof(outbuf); |
159 | | size_t produced; |
160 | | BrotliDecoderResult res; |
161 | | |
162 | | res = BrotliDecoderDecompressStream(state, &avail_in, &next_in, |
163 | | &avail_out, &next_out, NULL); |
164 | | produced = sizeof(outbuf) - avail_out; |
165 | | if (!codec_sink(out, outbuf, produced, &total, maxout)) { |
166 | | ok = HTS_FALSE; |
167 | | break; |
168 | | } |
169 | | if (res == BROTLI_DECODER_RESULT_ERROR) { |
170 | | ok = HTS_FALSE; |
171 | | break; |
172 | | } |
173 | | if (res == BROTLI_DECODER_RESULT_SUCCESS) { |
174 | | done = HTS_TRUE; |
175 | | break; |
176 | | } |
177 | | if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) |
178 | | break; |
179 | | } |
180 | | if (ferror(in)) |
181 | | ok = HTS_FALSE; |
182 | | } |
183 | | BrotliDecoderDestroyInstance(state); |
184 | | return (ok && done && !ferror(in)) ? (int) total : -1; |
185 | | } |
186 | | |
187 | | static size_t codec_head_brotli(const void *in, size_t in_len, void *out, |
188 | | size_t out_len) { |
189 | | BrotliDecoderState *const state = |
190 | | BrotliDecoderCreateInstance(NULL, NULL, NULL); |
191 | | const uint8_t *next_in = (const uint8_t *) in; |
192 | | uint8_t *next_out = (uint8_t *) out; |
193 | | size_t avail_in = in_len, avail_out = out_len; |
194 | | BrotliDecoderResult res; |
195 | | |
196 | | if (state == NULL) |
197 | | return 0; |
198 | | res = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, |
199 | | &next_out, NULL); |
200 | | BrotliDecoderDestroyInstance(state); |
201 | | return (res != BROTLI_DECODER_RESULT_ERROR) ? out_len - avail_out : 0; |
202 | | } |
203 | | #endif |
204 | | |
205 | | #if HTS_USEZSTD |
206 | | static int codec_unpack_zstd(FILE *in, FILE *out, LLint maxout) { |
207 | | ZSTD_DStream *const zds = ZSTD_createDStream(); |
208 | | hts_boolean ok = HTS_TRUE, eof = HTS_FALSE; |
209 | | size_t zret = 1; /* 0 once a frame is fully flushed */ |
210 | | LLint total = 0; |
211 | | |
212 | | if (zds == NULL) |
213 | | return -1; |
214 | | if (ZSTD_isError(ZSTD_DCtx_setParameter(zds, ZSTD_d_windowLogMax, |
215 | | HTS_ZSTD_WINDOWLOG_MAX))) { |
216 | | ZSTD_freeDStream(zds); |
217 | | return -1; |
218 | | } |
219 | | while (ok && !eof) { |
220 | | unsigned char BIGSTK inbuf[8192]; |
221 | | ZSTD_inBuffer input; |
222 | | |
223 | | input.src = inbuf; |
224 | | input.size = fread(inbuf, 1, sizeof(inbuf), in); |
225 | | input.pos = 0; |
226 | | if (input.size == 0) { |
227 | | eof = HTS_TRUE; |
228 | | break; |
229 | | } |
230 | | while (input.pos < input.size) { |
231 | | unsigned char BIGSTK outbuf[8192]; |
232 | | ZSTD_outBuffer output; |
233 | | |
234 | | output.dst = outbuf; |
235 | | output.size = sizeof(outbuf); |
236 | | output.pos = 0; |
237 | | zret = ZSTD_decompressStream(zds, &output, &input); |
238 | | if (ZSTD_isError(zret)) { |
239 | | ok = HTS_FALSE; |
240 | | break; |
241 | | } |
242 | | if (!codec_sink(out, outbuf, output.pos, &total, maxout)) { |
243 | | ok = HTS_FALSE; |
244 | | break; |
245 | | } |
246 | | } |
247 | | if (ferror(in)) |
248 | | ok = HTS_FALSE; |
249 | | } |
250 | | ZSTD_freeDStream(zds); |
251 | | /* zret != 0 at EOF: the last frame never completed */ |
252 | | return (ok && zret == 0 && !ferror(in)) ? (int) total : -1; |
253 | | } |
254 | | |
255 | | static size_t codec_head_zstd(const void *in, size_t in_len, void *out, |
256 | | size_t out_len) { |
257 | | ZSTD_DStream *const zds = ZSTD_createDStream(); |
258 | | ZSTD_inBuffer input; |
259 | | ZSTD_outBuffer output; |
260 | | size_t zret; |
261 | | |
262 | | if (zds == NULL) |
263 | | return 0; |
264 | | if (ZSTD_isError(ZSTD_DCtx_setParameter(zds, ZSTD_d_windowLogMax, |
265 | | HTS_ZSTD_WINDOWLOG_MAX))) { |
266 | | ZSTD_freeDStream(zds); |
267 | | return 0; |
268 | | } |
269 | | input.src = in; |
270 | | input.size = in_len; |
271 | | input.pos = 0; |
272 | | output.dst = out; |
273 | | output.size = out_len; |
274 | | output.pos = 0; |
275 | | zret = ZSTD_decompressStream(zds, &output, &input); |
276 | | ZSTD_freeDStream(zds); |
277 | | return ZSTD_isError(zret) ? 0 : output.pos; |
278 | | } |
279 | | #endif |
280 | | |
281 | 0 | LLint hts_codec_coded_size(FILE *in) { |
282 | 0 | long size; |
283 | |
|
284 | 0 | if (fseek(in, 0, SEEK_END) != 0) |
285 | 0 | return -1; |
286 | 0 | size = ftell(in); |
287 | 0 | if (size < 0 || fseek(in, 0, SEEK_SET) != 0) |
288 | 0 | return -1; |
289 | 0 | return (LLint) size; |
290 | 0 | } |
291 | | |
292 | | int hts_codec_unpack(hts_codec codec, const char *filename, |
293 | 0 | const char *newfile) { |
294 | 0 | if (filename == NULL || newfile == NULL || !filename[0] || !newfile[0]) |
295 | 0 | return -1; |
296 | 0 | switch (codec) { |
297 | 0 | case HTS_CODEC_DEFLATE: |
298 | 0 | return hts_zunpack(filename, newfile); |
299 | 0 | case HTS_CODEC_BROTLI: |
300 | 0 | case HTS_CODEC_ZSTD: |
301 | 0 | break; |
302 | 0 | default: /* IDENTITY never reaches the unpacker; UNSUPPORTED must fail */ |
303 | 0 | return -1; |
304 | 0 | } |
305 | | #if HTS_USEBROTLI || HTS_USEZSTD |
306 | | { |
307 | | char catbuff[CATBUFF_SIZE]; |
308 | | FILE *out, *in = FOPEN(fconv(catbuff, sizeof(catbuff), filename), "rb"); |
309 | | LLint maxout; |
310 | | int ret = -1; |
311 | | |
312 | | if (in == NULL) |
313 | | return -1; |
314 | | maxout = hts_codec_maxout(hts_codec_coded_size(in)); |
315 | | out = FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb"); |
316 | | if (out == NULL) { |
317 | | fclose(in); |
318 | | return -1; |
319 | | } |
320 | | #if HTS_USEBROTLI |
321 | | if (codec == HTS_CODEC_BROTLI) |
322 | | ret = codec_unpack_brotli(in, out, maxout); |
323 | | #endif |
324 | | #if HTS_USEZSTD |
325 | | if (codec == HTS_CODEC_ZSTD) |
326 | | ret = codec_unpack_zstd(in, out, maxout); |
327 | | #endif |
328 | | fclose(out); |
329 | | fclose(in); |
330 | | return ret; |
331 | | } |
332 | | #else |
333 | 0 | return -1; |
334 | 0 | #endif |
335 | 0 | } |
336 | | |
337 | | size_t hts_codec_head(hts_codec codec, const void *in, size_t in_len, void *out, |
338 | 0 | size_t out_len) { |
339 | 0 | if (in == NULL || in_len == 0 || out == NULL || out_len == 0) |
340 | 0 | return 0; |
341 | 0 | switch (codec) { |
342 | 0 | case HTS_CODEC_DEFLATE: |
343 | 0 | return hts_zhead(in, in_len, out, out_len); |
344 | | #if HTS_USEBROTLI |
345 | | case HTS_CODEC_BROTLI: |
346 | | return codec_head_brotli(in, in_len, out, out_len); |
347 | | #endif |
348 | | #if HTS_USEZSTD |
349 | | case HTS_CODEC_ZSTD: |
350 | | return codec_head_zstd(in, in_len, out, out_len); |
351 | | #endif |
352 | 0 | default: |
353 | 0 | return 0; |
354 | 0 | } |
355 | 0 | } |