Coverage Report

Created: 2026-08-11 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}