Coverage Report

Created: 2026-10-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htsheader_selftest.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: htsheader_selftest.c subroutines:                      */
30
/*       self-tests for HTTP headers and content codings        */
31
/* Author: Xavier Roche                                         */
32
/* ------------------------------------------------------------ */
33
34
#include "htsselftest_int.h"
35
36
#include <limits.h>
37
#include <stdint.h>
38
39
/* The launch gate: a hold withholds launches, the cap decides how long, and a
40
   stop outranks both. 77 to skip when no slot table could be allocated. */
41
0
static int st_retryafter_gate(httrackp *opt) {
42
0
  struct_back *sback = back_new(opt, 1);
43
0
  const hts_log_type quiet = opt->debug;
44
0
  int err = 0;
45
46
0
  if (sback == NULL) {
47
0
    printf("retry-after: SKIP (no slot table)\n");
48
0
    return 77;
49
0
  }
50
  /* the arming below logs, and the test driver exact-matches the output */
51
0
  opt->debug = LOG_PANIC;
52
0
  opt->maxsoc = 4;
53
0
  opt->maxconn = 0;
54
0
  opt->pause_max_ms = 0;
55
0
  opt->max_retry_after = 60;
56
0
  opt->state.stop = 0;
57
58
0
#define GATE_CHECK(cond)                                                       \
59
0
  do {                                                                         \
60
0
    if (!(cond)) {                                                             \
61
0
      printf("retry-after: FAIL line %d: %s\n", __LINE__, #cond);              \
62
0
      err = 1;                                                                 \
63
0
    }                                                                          \
64
0
  } while (0)
65
66
0
  GATE_CHECK(back_pluggable_sockets_strict(sback, opt) > 0);
67
68
0
  back_set_retry_after(sback, opt, 30);
69
0
  GATE_CHECK(sback->retry_after_until != 0);
70
  /* the ask is in seconds: a hold scaled in milliseconds would be up already */
71
0
  GATE_CHECK(sback->retry_after_until - mtime_local() > 25 * 1000);
72
0
  GATE_CHECK(back_pluggable_sockets_strict(sback, opt) == 0);
73
74
  /* a shorter delay must not cut a hold already running */
75
0
  {
76
0
    const TStamp held = sback->retry_after_until;
77
78
0
    back_set_retry_after(sback, opt, 1);
79
0
    GATE_CHECK(sback->retry_after_until == held);
80
0
  }
81
82
  /* the cap, not the server, decides: 100000s clips to opt->max_retry_after.
83
     The upper bounds sit far above what was armed because mtime_local() is the
84
     wall clock: a scale error at least doubles the hold, so slack costs no kill
85
     and a backward clock step cannot red them. */
86
0
  GATE_CHECK(sback->retry_after_until - mtime_local() <= 45 * 1000);
87
0
  back_set_retry_after(sback, opt, 100000);
88
0
  GATE_CHECK(sback->retry_after_until - mtime_local() > 55 * 1000);
89
0
  GATE_CHECK(sback->retry_after_until - mtime_local() <= 90 * 1000);
90
91
  /* a stop outranks the hold, or the user's Ctrl-C waits out the delay */
92
0
  opt->state.stop = 1;
93
0
  GATE_CHECK(back_pluggable_sockets_strict(sback, opt) > 0);
94
0
  opt->state.stop = 0;
95
0
  GATE_CHECK(back_pluggable_sockets_strict(sback, opt) == 0);
96
97
  /* a cap of zero waives the wait entirely */
98
0
  sback->retry_after_until = 0;
99
0
  opt->max_retry_after = 0;
100
0
  back_set_retry_after(sback, opt, 30);
101
0
  GATE_CHECK(sback->retry_after_until == 0);
102
0
  GATE_CHECK(back_pluggable_sockets_strict(sback, opt) > 0);
103
104
  /* --pause is the gate's other delay, and a stop outranks it the same way */
105
0
  {
106
0
    const TStamp connect_was = HTS_STAT.last_connect;
107
0
    const TStamp request_was = HTS_STAT.last_request;
108
0
    const int pause_min_was = opt->pause_min_ms;
109
110
0
    opt->pause_min_ms = opt->pause_max_ms = 60 * 1000;
111
0
    HTS_STAT.last_connect = mtime_local();
112
0
    HTS_STAT.last_request = 0;
113
0
    GATE_CHECK(back_pluggable_sockets_strict(sback, opt) == 0);
114
0
    opt->state.stop = 1;
115
0
    GATE_CHECK(back_pluggable_sockets_strict(sback, opt) > 0);
116
0
    opt->state.stop = 0;
117
0
    GATE_CHECK(back_pluggable_sockets_strict(sback, opt) == 0);
118
0
    opt->pause_min_ms = pause_min_was;
119
0
    opt->pause_max_ms = 0;
120
0
    HTS_STAT.last_connect = connect_was;
121
0
    HTS_STAT.last_request = request_was;
122
0
  }
123
124
0
#undef GATE_CHECK
125
0
  opt->debug = quiet;
126
0
  back_free(&sback);
127
0
  return err;
128
0
}
129
130
0
static int st_retryafter(httrackp *opt, int argc, char **argv) {
131
  /* 1994-11-06 08:49:37 GMT, the RFC's own example date */
132
0
  const time_t ref = (time_t) 784111777;
133
0
  int err = 0;
134
0
  size_t i;
135
136
0
  static const struct {
137
0
    const char *value;
138
0
    int expect;
139
0
  } cases[] = {
140
      /* delta-seconds */
141
0
      {"120", 120},
142
0
      {" 120", 120},
143
0
      {"120\r\n", 120},
144
0
      {"120 ", 120},
145
0
      {"120\t", 120},
146
0
      {"0", 0},
147
      /* clipped, never wrapped negative; INT_MAX itself must survive intact */
148
0
      {"2147483640", 2147483640}, /* just under: clipping it is an off-by-one */
149
0
      {"2147483647", INT_MAX},
150
0
      {"2147483648", INT_MAX},
151
0
      {"4294967296", INT_MAX},
152
0
      {"99999999999999999999", INT_MAX},
153
      /* HTTP-date, relative to ref */
154
0
      {"Sun, 06 Nov 1994 08:50:37 GMT", 60},
155
0
      {"Sun, 06 Nov 1994 08:49:37 GMT", 0},
156
0
      {"Sun, 06 Nov 1994 08:48:37 GMT", 0}, /* already past, never negative */
157
0
      {"06 Nov 1994 08:50:37 GMT", 60},     /* a date with no weekday */
158
      /* nothing usable: -1, which is what keeps a bare 503 fatal */
159
0
      {"", -1},
160
0
      {"   ", -1},
161
0
      {"soon", -1},
162
0
      {"12x", -1},
163
0
      {"12 x", -1},
164
0
      {"-5", -1},
165
0
      {"1.5", -1},
166
0
  };
167
168
0
  (void) argc;
169
0
  (void) argv;
170
0
  for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
171
0
    const int got = hts_parse_retry_after(cases[i].value, ref);
172
173
0
    if (got != cases[i].expect) {
174
0
      printf("retry-after: \"%s\" gave %d, expected %d\n", cases[i].value, got,
175
0
             cases[i].expect);
176
0
      err = 1;
177
0
    }
178
0
  }
179
0
  if (hts_parse_retry_after(NULL, ref) != -1)
180
0
    err = 1;
181
  /* An HTTP date's "60" is 1960, never 2060 as in a cookie. A time before 1970
182
     may not convert, so 0 and -1 both pass. */
183
0
  if (hts_parse_retry_after("Sunday, 06-Nov-60 08:49:37 GMT", ref) > 0) {
184
0
    printf("retry-after: a two-digit 60 was read as 2060\n");
185
0
    err = 1;
186
0
  }
187
0
  {
188
0
    const int gate = st_retryafter_gate(opt);
189
190
0
    if (gate == 77)
191
0
      return 77;
192
0
    err |= gate;
193
0
  }
194
0
  printf("retry-after: %s\n", err ? "FAIL" : "OK");
195
0
  return err;
196
0
}
197
198
/* Extra args are key=value: adr= cdispo= statuscode= status= strip= urlhack=
199
   no-www= no-slash= no-query= n83= type=, plus repeatable prior=adr|fil|sav
200
   registering an already-crawled link (dedup/collision paths). */
201
/* Parse raw response-header lines and print the naming-relevant fields. */
202
0
static int st_header(httrackp *opt, int argc, char **argv) {
203
0
  htsblk r;
204
0
  int i;
205
206
0
  (void) opt;
207
0
  if (argc < 1) {
208
0
    fprintf(stderr, "header: needs at least one raw header line\n");
209
0
    return 1;
210
0
  }
211
0
  memset(&r, 0, sizeof(r));
212
0
  for (i = 0; i < argc; i++) {
213
0
    char BIGSTK line[HTS_URLMAXSIZE * 2];
214
215
0
    strcpybuff(line, argv[i]);
216
0
    treathead(NULL, "www.example.com", "/", &r, line);
217
0
  }
218
0
  printf("contenttype=%s cdispo=%s\n", r.contenttype, r.cdispo);
219
0
  printf("contentencoding=%s\n", r.contentencoding);
220
0
  return 0;
221
0
}
222
223
/* Prints the code hts_status_line_code() reads from argv[0]. */
224
0
static int st_statusline(httrackp *opt, int argc, char **argv) {
225
0
  (void) opt;
226
0
  if (argc != 1) {
227
0
    fprintf(stderr, "statusline: needs one status line\n");
228
0
    return 1;
229
0
  }
230
0
  printf("code=%d\n", hts_status_line_code(argv[0]));
231
0
  return 0;
232
0
}
233
234
/* A header line that does not fit must be reported as cut and skipped whole,
235
   so the line after it is the server's next header and not its own tail. The
236
   block defaults to two headers and the blank line; \n and \r arrive escaped,
237
   since the command line filters both out. */
238
0
static int st_headerline(httrackp *opt, int argc, char **argv) {
239
0
  static const char fixture[] = "X-First: 0123456789abcdefghij\r\n"
240
0
                                "X-Second: ok\r\n"
241
0
                                "\r\n";
242
0
  char block[256], line[64];
243
0
  size_t blocklen;
244
0
  int max, ptr = 0, i;
245
246
0
  (void) opt;
247
0
  if (argc < 1) {
248
0
    fprintf(stderr, "headerline: needs a read size\n");
249
0
    return 1;
250
0
  }
251
0
  max = atoi(argv[0]);
252
0
  if (max < 2 || (size_t) max > sizeof(line)) {
253
0
    fprintf(stderr, "headerline: read size out of probe range\n");
254
0
    return 1;
255
0
  }
256
0
  if (argc < 2) {
257
0
    memcpy(block, fixture, sizeof(fixture));
258
0
    blocklen = sizeof(fixture) - 1;
259
0
  } else {
260
0
    const char *a = argv[1];
261
262
0
    if (strlen(a) >= sizeof(block)) {
263
0
      fprintf(stderr, "headerline: block too long\n");
264
0
      return 1;
265
0
    }
266
0
    for (blocklen = 0; *a != '\0'; a++) {
267
0
      if (*a == '\\' && a[1] == 'n')
268
0
        block[blocklen++] = '\n', a++;
269
0
      else if (*a == '\\' && a[1] == 'r')
270
0
        block[blocklen++] = '\r', a++;
271
0
      else
272
0
        block[blocklen++] = *a;
273
0
    }
274
0
    block[blocklen] = '\0';
275
0
  }
276
  /* one read past the block's lines, to show the walk stops at its end */
277
0
  for (i = 0; i < 4; i++) {
278
0
    int adv;
279
0
    const hts_boolean cut =
280
0
        binput_line(block + ptr, block + blocklen, line, max, &adv);
281
282
0
    ptr += adv;
283
0
    printf("cut=%d over=%d line=%s\n", cut != HTS_FALSE,
284
0
           ptr > (int) blocklen + 1, line);
285
0
  }
286
0
  return 0;
287
0
}
288
289
/* #1294: binput()'s advance must reach the next line even when it clipped the
290
   value, since the cache, robots and list parsers resume on it. */
291
0
static int st_binputline(httrackp *opt, int argc, char **argv) {
292
0
  static const char fixture[] = "Disallow: 0123456789abcdefghij\n"
293
0
                                "Allow: /open/\n";
294
0
  char block[256], line[64];
295
0
  size_t blocklen;
296
0
  int max, ptr = 0, i;
297
298
0
  (void) opt;
299
0
  if (argc < 1) {
300
0
    fprintf(stderr, "binputline: needs a read size\n");
301
0
    return 1;
302
0
  }
303
0
  max = atoi(argv[0]);
304
0
  if (max < 1 || (size_t) max >= sizeof(line)) {
305
0
    fprintf(stderr, "binputline: read size out of probe range\n");
306
0
    return 1;
307
0
  }
308
0
  if (argc < 2) {
309
0
    memcpy(block, fixture, sizeof(fixture));
310
0
    blocklen = sizeof(fixture) - 1;
311
0
  } else {
312
0
    const char *a = argv[1];
313
314
0
    if (strlen(a) >= sizeof(block)) {
315
0
      fprintf(stderr, "binputline: block too long\n");
316
0
      return 1;
317
0
    }
318
0
    for (blocklen = 0; *a != '\0'; a++) {
319
0
      if (*a == '\\' && a[1] == 'n')
320
0
        block[blocklen++] = '\n', a++;
321
0
      else if (*a == '\\' && a[1] == 'r')
322
0
        block[blocklen++] = '\r', a++;
323
0
      else
324
0
        block[blocklen++] = *a;
325
0
    }
326
0
    block[blocklen] = '\0';
327
0
  }
328
  /* one read past the block, to show the walk stops on its terminating NUL */
329
0
  for (i = 0; i < 3; i++) {
330
0
    const int adv = binput(block + ptr, line, max);
331
332
0
    printf("adv=%d line=%s\n", adv, line);
333
0
    ptr += adv;
334
0
    if (ptr > (int) blocklen)
335
0
      ptr = (int) blocklen;
336
0
  }
337
0
  return 0;
338
0
}
339
340
static int st_location_logged = 0;
341
342
static HTS_PRINTF_FUN(3, 0) void st_location_log(httrackp *opt, int type,
343
                                                 const char *format,
344
0
                                                 va_list args) {
345
0
  (void) opt;
346
0
  (void) type;
347
0
  (void) format;
348
0
  (void) args;
349
0
  st_location_logged++;
350
0
}
351
352
/* treathead's Location gate must match htsblk.location's buffer size: a
353
   redirect that fits is kept whole, a longer one refused and reported, and
354
   neither touches the canary behind the buffer. */
355
0
static int st_location(httrackp *opt, int argc, char **argv) {
356
0
  struct {
357
0
    char loc[HTS_LOCATION_SIZE];
358
0
    char canary[64];
359
0
  } probe;
360
361
0
  char BIGSTK line[HTS_LOCATION_SIZE + 1024];
362
0
  const char *const prefix = "http://www.example.com/";
363
0
  const char *url, *want;
364
0
  htsblk r;
365
0
  size_t n, urllen, kept, i;
366
0
  int asked, smashed = 0;
367
368
0
  (void) opt;
369
0
  if (argc < 1) {
370
0
    fprintf(stderr, "location: needs a Location URL length\n");
371
0
    return 1;
372
0
  }
373
  /* subtract, never add: a negative argument must not wrap the range check */
374
0
  asked = atoi(argv[0]);
375
0
  if (asked <= (int) strlen(prefix) ||
376
0
      (size_t) asked > sizeof(line) - sizeof("Location: ")) {
377
0
    fprintf(stderr, "location: length out of probe range\n");
378
0
    return 1;
379
0
  }
380
0
  urllen = (size_t) asked;
381
382
0
  memset(probe.loc, 0, sizeof(probe.loc));
383
0
  memset(probe.canary, '#', sizeof(probe.canary));
384
0
  memset(&r, 0, sizeof(r));
385
0
  r.location = probe.loc;
386
387
0
  n = (size_t) snprintf(line, sizeof(line), "Location: %s", prefix);
388
0
  memset(line + n, 'a', urllen - strlen(prefix));
389
0
  line[n + urllen - strlen(prefix)] = '\0';
390
0
  url = line + n - strlen(prefix);
391
392
0
  st_location_logged = 0;
393
0
  hts_set_log_vprint_callback(st_location_log);
394
0
  treathead(NULL, "www.example.com", "/", &r, line);
395
0
  hts_set_log_vprint_callback(NULL);
396
397
0
  for (i = 0; i < sizeof(probe.canary); i++) {
398
0
    if (probe.canary[i] != '#')
399
0
      smashed++;
400
0
  }
401
  /* bounded: an unterminated buffer reports capacity, never reads past it */
402
0
  for (kept = 0; kept < sizeof(probe.loc) && probe.loc[kept] != '\0'; kept++)
403
0
    ;
404
  /* the wanted value comes from the contract, not from what treathead did */
405
0
  want = urllen < HTS_LOCATION_SIZE ? url : "";
406
0
  printf("asked=%d kept=%d value_ok=%d canary_smashed=%d logged=%d\n", asked,
407
0
         (int) kept, strcmp(probe.loc, want) == 0, smashed,
408
0
         st_location_logged != 0);
409
0
  return 0;
410
0
}
411
412
/* An over-long header value must not overflow treathead's tempo[1100]. */
413
0
static int st_headerlong(httrackp *opt, int argc, char **argv) {
414
0
  htsblk r;
415
0
  char BIGSTK line[HTS_URLMAXSIZE * 2];
416
0
  const char *const name = argc >= 1 ? argv[0] : "Content-Type:";
417
0
  const int pad = 1500; /* > tempo[1100] */
418
0
  size_t n;
419
420
0
  (void) opt;
421
0
  memset(&r, 0, sizeof(r));
422
0
  n = (size_t) snprintf(line, sizeof(line), "%s ", name);
423
0
  memset(line + n, 'a', pad);
424
0
  line[n + pad] = '\0';
425
0
  treathead(NULL, "www.example.com", "/", &r, line);
426
0
  printf("contenttype_len=%d contentencoding_len=%d\n",
427
0
         (int) strlen(r.contenttype), (int) strlen(r.contentencoding));
428
0
  return 0;
429
0
}
430
431
/* Does the custom request-header block already carry <field>? (#1337) */
432
0
static int st_headerfield(httrackp *opt, int argc, char **argv) {
433
0
  char *headers, *field;
434
435
0
  (void) opt;
436
0
  if (argc < 2) {
437
0
    fprintf(stderr, "headerfield: needs a header block and a field name\n");
438
0
    return 1;
439
0
  }
440
  /* exact-size heap copies so a sanitizer traps an over-read */
441
0
  headers = strdupt(argv[0]);
442
0
  field = strdupt(argv[1]);
443
0
  printf("%s\n",
444
0
         http_headers_have_field(headers, field) ? "present" : "absent");
445
0
  freet(headers);
446
0
  freet(field);
447
0
  return 0;
448
0
}
449
450
/* Drive one http_xfread1 call. entered is the buffer the case came in with, so
451
   a guard that allocates before refusing shows up. */
452
static void st_xfread_case(const char *name, htsblk *r, int bufl,
453
0
                           const char *entered) {
454
  /* Separate statements: as printf arguments a compiler may sample r->adr
455
     before the call, where no allocation can show. */
456
0
  const LLint ret = http_xfread1(r, bufl);
457
0
  const char *const adr = r->adr;
458
0
  const int code = r->statuscode;
459
0
  const char *state;
460
461
0
  if (adr == NULL)
462
0
    state = "null";
463
0
  else if (adr == entered)
464
0
    state = "kept";
465
0
  else
466
0
    state = "alloc";
467
0
  printf("%s: refused=%d adr=%s code=%d msg=%s\n", name,
468
0
         ret == READ_ERROR ? 1 : 0, state, code, r->msg);
469
0
}
470
471
/* Drive back_set_decoded_size() on a slot still holding its coded body. */
472
0
static void st_decode_size_case(const char *name, int is_write, LLint size) {
473
0
  htsblk r;
474
0
  hts_boolean ok;
475
0
  const char *state;
476
0
  LLint committed;
477
478
0
  memset(&r, 0, sizeof(r));
479
0
  r.soc = INVALID_SOCKET;
480
0
  r.is_write = (short int) is_write;
481
0
  r.adr = (char *) malloct(64);
482
0
  r.size = 64;
483
0
  ok = back_set_decoded_size(&r, size);
484
0
  state = r.adr != NULL ? "kept" : "dropped";
485
0
  committed = r.size;
486
0
  printf("%s: ok=%d body=%s size=" LLintP " code=%d msg=%s\n", name,
487
0
         ok == HTS_TRUE ? 1 : 0, state, committed, r.statuscode, r.msg);
488
0
  freet(r.adr);
489
0
}
490
491
/* http_xfread1 must refuse an in-memory buffer whose size would exceed a 32-bit
492
   index (hostile Content-Length or endless stream) rather than allocate it.
493
   The guard returns before any socket read, so no real connection is needed.
494
   The same bound must hold on the two paths that fill r->adr without it: a
495
   chunk stream, and a decoded content coding. */
496
0
static int st_xfread_limit(httrackp *opt, int argc, char **argv) {
497
0
  static const LLint sizes[] = {
498
0
      0, 1000, (LLint) INT32_MAX - 1, (LLint) INT32_MAX, (LLint) INT32_MAX + 1,
499
0
      -1};
500
0
  htsblk r;
501
0
  size_t i;
502
503
0
  (void) opt;
504
505
  // Content-Length just over 2 GiB.
506
0
  memset(&r, 0, sizeof(r));
507
0
  r.soc = INVALID_SOCKET;
508
0
  r.totalsize = (LLint) INT32_MAX + 1;
509
0
  st_xfread_case("bylen", &r, 8192, NULL);
510
0
  freet(r.adr);
511
512
  // 4 GiB: truncated to an int this length reads as 0.
513
0
  memset(&r, 0, sizeof(r));
514
0
  r.soc = INVALID_SOCKET;
515
0
  r.totalsize = (LLint) 1 << 32;
516
0
  st_xfread_case("bylen4g", &r, 8192, NULL);
517
0
  freet(r.adr);
518
519
  // Unknown length, buffer already at the limit: the next read would exceed it.
520
0
  memset(&r, 0, sizeof(r));
521
0
  r.soc = INVALID_SOCKET;
522
0
  r.totalsize = -1;
523
0
  r.size = (LLint) INT32_MAX;
524
0
  st_xfread_case("bygrow", &r, 8192, NULL);
525
0
  freet(r.adr);
526
527
  // Exactly at the 2 GiB index (size + bufl == INT32_MAX): must also be
528
  // refused, since the reallocs below add 1 (a `> INT32_MAX` check would let
529
  // this through and overflow the int realloc size).
530
0
  memset(&r, 0, sizeof(r));
531
0
  r.soc = INVALID_SOCKET;
532
0
  r.totalsize = -1;
533
0
  r.size = (LLint) INT32_MAX - 8192;
534
0
  st_xfread_case("boundary", &r, 8192, NULL);
535
0
  freet(r.adr);
536
537
  /* Same size, entering with a buffer already held: the guard runs per call,
538
     not on the first allocation only, or the realloc it lets through takes an
539
     int that has already wrapped negative. */
540
0
  memset(&r, 0, sizeof(r));
541
0
  r.soc = INVALID_SOCKET;
542
0
  r.totalsize = -1;
543
0
  r.size = (LLint) INT32_MAX - 8192;
544
0
  r.adr = (char *) malloct(64);
545
0
  r.adr[0] = '\0';
546
0
  st_xfread_case("grown", &r, 8192, r.adr);
547
0
  freet(r.adr);
548
549
  // A legitimate small size must NOT be refused, and must still be allocated.
550
0
  memset(&r, 0, sizeof(r));
551
0
  r.soc = INVALID_SOCKET;
552
0
  r.totalsize = 1000;
553
0
  st_xfread_case("accept", &r, 8192, NULL);
554
0
  freet(r.adr);
555
556
  /* Control for the unknown-length arm: an undeclared length is not itself a
557
     reason to refuse, only the size the stream has reached. */
558
0
  memset(&r, 0, sizeof(r));
559
0
  r.soc = INVALID_SOCKET;
560
0
  r.totalsize = -1;
561
0
  st_xfread_case("accept_grow", &r, 8192, NULL);
562
0
  freet(r.adr);
563
564
  // The bound itself, which the chunk and decode paths share.
565
0
  for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++)
566
0
    printf("fits(" LLintP ")=%d\n", sizes[i],
567
0
           hts_inmem_size_fits(sizes[i]) ? 1 : 0);
568
569
  /* hts_codec_maxout() caps a decoded body at INT_MAX inclusive, so a content
570
     coding reaches this with the one size the receive guard refuses. */
571
0
  st_decode_size_case("decode_mem_max", 0, (LLint) INT32_MAX);
572
0
  st_decode_size_case("decode_mem_ok", 0, (LLint) INT32_MAX - 1);
573
0
  st_decode_size_case("decode_disk_max", 1, (LLint) INT32_MAX);
574
575
  /* Given a file at or past the bound, the reader must refuse on the stat that
576
     would size its own allocation, so nothing appended after a caller's own
577
     check can carry the body past it. The second file is the control: a bound
578
     that refuses everything reads the same as one that works. */
579
0
  for (i = 0; (int) i < argc && i < 2; i++) {
580
0
    LLint got = -1;
581
0
    char *adr = readfile2_inmem(argv[i], &got);
582
583
0
    printf("%s: adr=%s size=" LLintP "\n", i == 0 ? "bigfile" : "smallfile",
584
0
           adr != NULL ? "alloc" : "null", got);
585
0
    freet(adr);
586
0
  }
587
0
  return 0;
588
0
}
589
590
/* Parse a Content-Range header and print the sanitized triple. A hostile value
591
   (negative or INT64 extreme) must clamp to 0 without signed-overflow UB. */
592
0
static int st_crange(httrackp *opt, int argc, char **argv) {
593
0
  int i;
594
595
0
  (void) opt;
596
0
  if (argc < 1) {
597
0
    fprintf(stderr, "crange: needs at least one raw Content-Range line\n");
598
0
    return 1;
599
0
  }
600
0
  for (i = 0; i < argc; i++) {
601
0
    htsblk r;
602
0
    char BIGSTK line[HTS_URLMAXSIZE * 2];
603
604
0
    memset(&r, 0, sizeof(r));
605
0
    strcpybuff(line, argv[i]);
606
0
    treathead(NULL, "www.example.com", "/", &r, line);
607
0
    printf("crange_start=" LLintP " crange_end=" LLintP " crange=" LLintP "\n",
608
0
           (LLint) r.crange_start, (LLint) r.crange_end, (LLint) r.crange);
609
0
  }
610
0
  return 0;
611
0
}
612
613
/* What the custom header block appends to a request already carrying <request>
614
   (#1340). CR and LF come back escaped, so a case stays one line. */
615
0
static int st_headerdedup(httrackp *opt, int argc, char **argv) {
616
0
  const size_t half = 8192;
617
0
  char *req, *tmp, *headers;
618
0
  const char *appended;
619
0
  size_t i, reqlen, blocklen;
620
  /* Only an exact capacity puts a line's last byte on the buffer's last byte */
621
0
  size_t capacity = argc > 2 ? (size_t) atoi(argv[2]) : 2 * half;
622
623
0
  (void) opt;
624
0
  if (argc < 2) {
625
0
    fprintf(stderr, "headerdedup: needs a request and a header block\n");
626
0
    return 1;
627
0
  }
628
0
  req = malloct(capacity > 2 * half ? capacity : 2 * half);
629
0
  tmp = malloct(half);
630
0
  reqlen = st_decode_body(argv[0], req, half);
631
0
  blocklen = st_decode_body(argv[1], tmp, half);
632
  /* st_decode_body clips, and a clipped case grades an input nobody wrote */
633
0
  if (reqlen + 1 >= half || blocklen + 1 >= half || capacity <= reqlen) {
634
0
    fprintf(stderr, "headerdedup: argument longer than %d bytes\n",
635
0
            (int) half - 2);
636
0
    freet(tmp);
637
0
    freet(req);
638
0
    return 1;
639
0
  }
640
0
  appended = req + reqlen;
641
  /* exact-size copy so a sanitizer traps an over-read of the block */
642
0
  headers = strdupt(tmp);
643
0
  freet(tmp);
644
0
  http_append_custom_headers(req, capacity, headers);
645
0
  for (i = 0; appended[i] != '\0'; i++) {
646
0
    if (appended[i] == '\r')
647
0
      printf("\\r");
648
0
    else if (appended[i] == '\n')
649
0
      printf("\\n");
650
0
    else
651
0
      putchar(appended[i]);
652
0
  }
653
0
  printf("\n");
654
0
  freet(headers);
655
0
  freet(req);
656
0
  return 0;
657
0
}
658
659
/* Default User-Agent: honest HTTrack token, no resurrected Windows 98. */
660
0
static int st_useragent(httrackp *opt, int argc, char **argv) {
661
0
  const char *ua = StringBuff(opt->user_agent);
662
0
  (void) argc;
663
0
  (void) argv;
664
0
  assertf(ua != NULL);
665
0
  assertf(strcmp(ua, HTS_DEFAULT_USER_AGENT) == 0);
666
  /* Teeth independent of the macro: honest token + self-identifier, and no
667
     legacy Mozilla/4.x fake-browser string (rejects the whole relic family). */
668
0
  assertf(strstr(ua, "HTTrack/") != NULL);
669
0
  assertf(strstr(ua, "+https://www.httrack.com/") != NULL);
670
0
  assertf(strstr(ua, "Mozilla/4.") == NULL);
671
0
  printf("useragent self-test OK: %s\n", ua);
672
0
  return 0;
673
0
}
674
675
/* HTTP status code -> reason phrase, including the modern 429/451. */
676
0
static int st_status(httrackp *opt, int argc, char **argv) {
677
0
  const char *s;
678
0
  (void) opt;
679
0
  (void) argc;
680
0
  (void) argv;
681
0
  s = infostatuscode_const(429);
682
0
  assertf(s != NULL && strcmp(s, "Too Many Requests") == 0);
683
0
  s = infostatuscode_const(451);
684
0
  assertf(s != NULL && strcmp(s, "Unavailable For Legal Reasons") == 0);
685
  /* A spot-check of a long-standing code, and an unknown one. */
686
0
  s = infostatuscode_const(404);
687
0
  assertf(s != NULL && strcmp(s, "Not Found") == 0);
688
0
  assertf(infostatuscode_const(799) == NULL);
689
0
  printf("status self-test OK\n");
690
0
  return 0;
691
0
}
692
693
/* Which statuses excuse a response that stored no body. */
694
0
static int st_nobody(httrackp *opt, int argc, char **argv) {
695
  /* every status the engine excuses, and near misses that it must not */
696
0
  static const struct {
697
0
    int code;
698
0
    hts_boolean excused;
699
0
  } cases[] = {
700
0
      {301, HTS_TRUE},  {302, HTS_TRUE},  {303, HTS_TRUE},  {304, HTS_FALSE},
701
0
      {305, HTS_FALSE}, {306, HTS_FALSE}, {307, HTS_TRUE},  {308, HTS_TRUE},
702
0
      {309, HTS_FALSE}, {300, HTS_FALSE}, {411, HTS_FALSE}, {412, HTS_TRUE},
703
0
      {413, HTS_FALSE}, {415, HTS_FALSE}, {416, HTS_TRUE},  {417, HTS_FALSE},
704
0
      {200, HTS_FALSE}, {204, HTS_FALSE}, {404, HTS_FALSE}, {500, HTS_FALSE},
705
0
  };
706
707
0
  char body[] = "body";
708
0
  size_t i;
709
0
  htsblk r;
710
0
  (void) opt;
711
0
  (void) argc;
712
0
  (void) argv;
713
714
0
  for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
715
0
    memset(&r, 0, sizeof(r));
716
0
    r.statuscode = cases[i].code;
717
0
    if (hts_body_missing_unexpectedly(&r) == cases[i].excused) {
718
0
      fprintf(stderr, "status %d: expected %s\n", cases[i].code,
719
0
              cases[i].excused ? "excused" : "unexpected");
720
0
      return 1;
721
0
    }
722
0
  }
723
724
  /* a stored body is never missing, whatever the status */
725
0
  memset(&r, 0, sizeof(r));
726
0
  r.statuscode = 404;
727
0
  r.adr = body;
728
0
  assertf(!hts_body_missing_unexpectedly(&r));
729
0
  memset(&r, 0, sizeof(r));
730
0
  r.statuscode = 404;
731
0
  r.is_write = 1;
732
0
  assertf(!hts_body_missing_unexpectedly(&r));
733
734
0
  printf("nobody self-test OK\n");
735
0
  return 0;
736
0
}
737
738
/* Deflate src->path at windowBits (16+ gzip, + zlib, - raw); 0 on success. */
739
static int ae_write_packed(const char *path, int windowBits,
740
0
                           const unsigned char *src, size_t len) {
741
0
  unsigned char out[8192];
742
0
  z_stream strm;
743
0
  FILE *f;
744
0
  int zerr;
745
746
0
  memset(&strm, 0, sizeof(strm));
747
0
  if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits, 8,
748
0
                   Z_DEFAULT_STRATEGY) != Z_OK)
749
0
    return 1;
750
0
  if ((f = FOPEN(path, "wb")) == NULL) {
751
0
    deflateEnd(&strm);
752
0
    return 1;
753
0
  }
754
0
  strm.next_in = (const Bytef *) src;
755
0
  strm.avail_in = (uInt) len;
756
0
  do {
757
0
    size_t n;
758
759
0
    strm.next_out = out;
760
0
    strm.avail_out = sizeof(out);
761
0
    zerr = deflate(&strm, Z_FINISH);
762
0
    n = sizeof(out) - strm.avail_out;
763
0
    if (n > 0 && !hts_fwrite_exact(out, n, f)) {
764
0
      deflateEnd(&strm);
765
0
      fclose(f);
766
0
      return 1;
767
0
    }
768
0
  } while (zerr == Z_OK);
769
0
  deflateEnd(&strm);
770
0
  fclose(f);
771
0
  return (zerr == Z_STREAM_END) ? 0 : 1;
772
0
}
773
774
/* Forged raw deflate (08 1D) that misdetects as zlib; only fallback decodes */
775
static int ae_write_collision(const char *path, const unsigned char *src,
776
0
                              size_t len) {
777
  /* block-1 LEN low byte 0x1D: with 0x08, (0x081D)%31==0 */
778
0
  const size_t n1 = 29;
779
0
  size_t n2, p = 0;
780
0
  unsigned char *buf;
781
0
  FILE *f;
782
0
  int ok;
783
784
0
  if (len < n1 || len - n1 > 0xFFFF)
785
0
    return 1;
786
0
  n2 = len - n1;
787
0
  buf = malloct(10 + len);
788
0
  if (buf == NULL)
789
0
    return 1;
790
0
  buf[p++] = 0x08; /* BFINAL=0, BTYPE=00, forged padding -> zlib CMF nibble */
791
0
  buf[p++] = (unsigned char) (n1 & 0xff);
792
0
  buf[p++] = (unsigned char) (n1 >> 8);
793
0
  buf[p++] = (unsigned char) (~n1 & 0xff);
794
0
  buf[p++] = (unsigned char) ((~n1 >> 8) & 0xff);
795
0
  memcpy(buf + p, src, n1);
796
0
  p += n1;
797
0
  buf[p++] = 0x01; /* BFINAL=1, BTYPE=00 */
798
0
  buf[p++] = (unsigned char) (n2 & 0xff);
799
0
  buf[p++] = (unsigned char) (n2 >> 8);
800
0
  buf[p++] = (unsigned char) (~n2 & 0xff);
801
0
  buf[p++] = (unsigned char) ((~n2 >> 8) & 0xff);
802
0
  memcpy(buf + p, src + n1, n2);
803
0
  p += n2;
804
0
  f = FOPEN(path, "wb");
805
0
  ok = (f != NULL && hts_fwrite_exact(buf, p, f));
806
0
  if (f != NULL)
807
0
    fclose(f);
808
0
  freet(buf);
809
0
  return ok ? 0 : 1;
810
0
}
811
812
/* Write src[0..len) to path as-is; 0 on success. */
813
static int ae_write_raw(const char *path, const unsigned char *src,
814
0
                        size_t len) {
815
0
  FILE *const f = FOPEN(path, "wb");
816
0
  int ok;
817
818
0
  if (f == NULL)
819
0
    return 1;
820
0
  ok = hts_fwrite_exact(src, len, f);
821
0
  fclose(f);
822
0
  return ok ? 0 : 1;
823
0
}
824
825
/* Compare path's bytes to expect[0..len); 0 if equal. Streams (large files). */
826
static int ae_check_decoded(const char *path, const unsigned char *expect,
827
0
                            size_t len) {
828
0
  unsigned char buf[8192];
829
0
  FILE *f = FOPEN(path, "rb");
830
0
  size_t off = 0, n;
831
832
0
  if (f == NULL)
833
0
    return 1;
834
0
  while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
835
0
    if (n > len - off || memcmp(buf, expect + off, n) != 0) {
836
0
      fclose(f);
837
0
      return 1;
838
0
    }
839
0
    off += n;
840
0
  }
841
0
  fclose(f);
842
0
  return (off == len) ? 0 : 1;
843
0
}
844
845
/* Accept-Encoding (#450): advertise gzip+deflate; both decode (hts_zunpack) */
846
0
static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
847
0
  const char *off = hts_acceptencoding(HTS_FALSE, HTS_TRUE);
848
0
  const char *on = hts_acceptencoding(HTS_TRUE, HTS_FALSE);
849
0
  const char *tls = hts_acceptencoding(HTS_TRUE, HTS_TRUE);
850
851
0
  (void) opt;
852
0
  assertf(strcmp(off, "identity") == 0);
853
0
  assertf(strstr(on, "gzip") != NULL);
854
0
  assertf(strstr(on, "deflate") != NULL); /* fails on the old gzip-only list */
855
  /* br and zstd ride on TLS only, so a cleartext proxy can not be handed a
856
     coding it may try to rewrite */
857
0
  assertf(strstr(on, "br") == NULL && strstr(on, "zstd") == NULL);
858
0
  assertf((strstr(tls, ", br") != NULL) == (HTS_USEBROTLI != 0));
859
0
  assertf((strstr(tls, "zstd") != NULL) == (HTS_USEZSTD != 0));
860
0
  if (argc >= 1) {
861
0
    static const int windowBits[] = {16 + MAX_WBITS, MAX_WBITS, -MAX_WBITS};
862
0
    const unsigned char small[] =
863
0
        "deflate round-trip: HTTrack decodes gzip and deflate alike. "
864
0
        "deflate round-trip: HTTrack decodes gzip and deflate alike.";
865
0
    const size_t slen = sizeof(small) - 1;
866
    /* 64 KiB of varied (LCG) bytes: forces the multi-fread loop */
867
0
    const size_t blen = 64 * 1024;
868
0
    unsigned char *body = malloct(blen);
869
0
    uint32_t x = 0x1234567u;
870
0
    char inpath[HTS_URLMAXSIZE], outpath[HTS_URLMAXSIZE];
871
0
    size_t i;
872
873
0
    assertf(body != NULL);
874
0
    for (i = 0; i < blen; i++) {
875
0
      x = x * 1103515245u + 12345u;
876
0
      body[i] = (unsigned char) (x >> 16);
877
0
    }
878
    /* gzip, zlib (RFC1950) and raw deflate (RFC1951), both small and large. */
879
0
    for (i = 0; i < sizeof(windowBits) / sizeof(windowBits[0]); i++) {
880
0
      snprintf(inpath, sizeof(inpath), "%s/ae-in-%d.z", argv[0], windowBits[i]);
881
0
      snprintf(outpath, sizeof(outpath), "%s/ae-out-%d", argv[0],
882
0
               windowBits[i]);
883
0
      assertf(ae_write_packed(inpath, windowBits[i], small, slen) == 0);
884
0
      assertf(hts_zunpack(inpath, outpath) == (int) slen);
885
0
      assertf(ae_check_decoded(outpath, small, slen) == 0);
886
0
      assertf(ae_write_packed(inpath, windowBits[i], body, blen) == 0);
887
0
      assertf(hts_zunpack(inpath, outpath) == (int) blen);
888
0
      assertf(ae_check_decoded(outpath, body, blen) == 0);
889
0
    }
890
    /* Fallback teeth: raw deflate misdetected as zlib; -1 without the retry. */
891
0
    snprintf(inpath, sizeof(inpath), "%s/ae-collide.z", argv[0]);
892
0
    snprintf(outpath, sizeof(outpath), "%s/ae-collide.out", argv[0]);
893
0
    assertf(ae_write_collision(inpath, body, 64) == 0);
894
0
    assertf(hts_zunpack(inpath, outpath) == 64);
895
0
    assertf(ae_check_decoded(outpath, body, 64) == 0);
896
    /* Identity fallback (#47): a plain body mislabeled as compressed is kept
897
       verbatim, small and multi-chunk (> one 8 KiB fread). */
898
0
    assertf(ae_write_raw(inpath, small, slen) == 0);
899
0
    assertf(hts_zunpack(inpath, outpath) == (int) slen);
900
0
    assertf(ae_check_decoded(outpath, small, slen) == 0);
901
0
    {
902
0
      const size_t ilen = 16 * 1024;
903
0
      unsigned char *idbody = malloct(ilen);
904
905
0
      assertf(idbody != NULL);
906
0
      for (i = 0; i < ilen; i++)
907
0
        idbody[i] = small[i % slen];
908
0
      assertf(ae_write_raw(inpath, idbody, ilen) == 0);
909
0
      assertf(hts_zunpack(inpath, outpath) == (int) ilen);
910
0
      assertf(ae_check_decoded(outpath, idbody, ilen) == 0);
911
0
      freet(idbody);
912
0
    }
913
    /* Truncated gzip (CRC+ISIZE cut), zlib (ADLER32 cut) and raw deflate
914
       must all still fail, not fall back to a verbatim copy. */
915
0
    {
916
0
      static const struct {
917
0
        int wb;
918
0
        size_t cut;
919
0
      } tr[] = {{16 + MAX_WBITS, 8}, {MAX_WBITS, 4}, {-MAX_WBITS, 5}};
920
921
0
      for (i = 0; i < sizeof(tr) / sizeof(tr[0]); i++) {
922
0
        unsigned char z[512];
923
0
        size_t zlen;
924
0
        FILE *f;
925
926
0
        assertf(ae_write_packed(inpath, tr[i].wb, small, slen) == 0);
927
0
        f = FOPEN(inpath, "rb");
928
0
        assertf(f != NULL);
929
0
        zlen = fread(z, 1, sizeof(z), f);
930
0
        fclose(f);
931
0
        assertf(zlen > tr[i].cut && zlen < sizeof(z));
932
0
        assertf(ae_write_raw(inpath, z, zlen - tr[i].cut) == 0);
933
0
        assertf(hts_zunpack(inpath, outpath) < 0);
934
0
      }
935
0
    }
936
0
    freet(body);
937
0
  }
938
0
  printf("acceptencoding self-test OK: %s\n", on);
939
0
  return 0;
940
0
}
941
942
#if HTS_USEBROTLI
943
/* No brotli encoder is linked, so the coded bytes are canned: brotli quality 9
944
   over cc_text, and over 4 MiB of 'A' (14 bytes, ~300000x). */
945
static const unsigned char cc_br_text[] = {
946
    0x1b, 0x43, 0x00, 0x00, 0x44, 0xdd, 0x96, 0xea, 0xe8, 0x22, 0xdd, 0x90,
947
    0xa4, 0x1b, 0x8a, 0xf7, 0x47, 0x0e, 0xc2, 0xc5, 0x3d, 0x09, 0x1b, 0x70,
948
    0xe0, 0x1e, 0x60, 0xa0, 0x8b, 0xcc, 0xbe, 0xcb, 0xb0, 0x31, 0x76, 0x9e,
949
    0xcf, 0x6e, 0x41, 0xb5, 0xe8, 0x2e, 0x56, 0x78, 0x08, 0x1b, 0xfa, 0x08,
950
    0x8a, 0x50, 0x83, 0x4e, 0x62, 0x7f, 0xbf, 0x05, 0xf2, 0x22, 0x8f, 0xdf,
951
    0x28, 0xdc, 0x9f, 0xa9, 0x90, 0x50, 0x37, 0x62, 0x56, 0x4f, 0xa8};
952
static const unsigned char cc_br_bomb[] = {0x9b, 0xff, 0xff, 0x3f, 0x00,
953
                                           0x24, 0x82, 0xe2, 0xb1, 0x40,
954
                                           0x72, 0xef, 0x7f, 0x00};
955
#endif
956
957
#if HTS_USEBROTLI || HTS_USEZSTD
958
static const unsigned char cc_text[] =
959
    "content codings: HTTrack decodes gzip, brotli and zstd bodies alike.";
960
#endif
961
962
/* Content codings: br and zstd decode, junk tokens stay identity, a coding we
963
   can not undo fails the fetch, and a bomb never lands on disk. */
964
0
static int st_contentcodings(httrackp *opt, int argc, char **argv) {
965
#if HTS_USEBROTLI || HTS_USEZSTD
966
  const size_t tlen = sizeof(cc_text) - 1;
967
#endif
968
0
  char inpath[HTS_URLMAXSIZE], outpath[HTS_URLMAXSIZE];
969
970
0
  (void) opt;
971
0
  assertf(hts_codec_parse("gzip") == HTS_CODEC_DEFLATE);
972
0
  assertf(hts_codec_parse("x-deflate") == HTS_CODEC_DEFLATE);
973
0
  assertf(hts_codec_parse("") == HTS_CODEC_IDENTITY);
974
0
  assertf(hts_codec_parse("identity") == HTS_CODEC_IDENTITY);
975
  /* servers do label plain bodies with junk; the page must survive that */
976
0
  assertf(hts_codec_parse("utf-8") == HTS_CODEC_IDENTITY);
977
  /* a real coding with no decoder here: fail, never save the coded bytes */
978
0
  assertf(hts_codec_parse("compress") == HTS_CODEC_UNSUPPORTED);
979
0
  assertf(hts_codec_parse("br") ==
980
0
          (HTS_USEBROTLI ? HTS_CODEC_BROTLI : HTS_CODEC_UNSUPPORTED));
981
0
  assertf(hts_codec_parse("zstd") ==
982
0
          (HTS_USEZSTD ? HTS_CODEC_ZSTD : HTS_CODEC_UNSUPPORTED));
983
0
  assertf(hts_codec_is_archive_ext(HTS_CODEC_DEFLATE, "tgz"));
984
0
  assertf(!hts_codec_is_archive_ext(HTS_CODEC_DEFLATE, "html"));
985
0
  assertf(hts_codec_is_archive_ext(HTS_CODEC_BROTLI, "br"));
986
0
  assertf(hts_codec_is_archive_ext(HTS_CODEC_ZSTD, "zst"));
987
  /* decoded-size budget: 4096x, floor 1 MiB, ceiling INT_MAX */
988
0
  assertf(hts_codec_maxout(1) == 1024 * 1024);
989
0
  assertf(hts_codec_maxout(1024) == 4096 * 1024);
990
0
  assertf(hts_codec_maxout(1024 * 1024) == INT_MAX);
991
992
0
  if (argc >= 1) {
993
0
    snprintf(inpath, sizeof(inpath), "%s/cc-in", argv[0]);
994
0
    snprintf(outpath, sizeof(outpath), "%s/cc-out", argv[0]);
995
#if HTS_USEBROTLI
996
    {
997
      unsigned char head[16];
998
999
      assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text)) == 0);
1000
      assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) ==
1001
              (int) tlen);
1002
      assertf(ae_check_decoded(outpath, cc_text, tlen) == 0);
1003
      assertf(hts_codec_head(HTS_CODEC_BROTLI, cc_br_text, sizeof(cc_br_text),
1004
                             head, sizeof(head)) == sizeof(head));
1005
      assertf(memcmp(head, cc_text, sizeof(head)) == 0);
1006
      /* truncated: must fail, not fall back to a verbatim copy */
1007
      assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text) - 4) == 0);
1008
      assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) < 0);
1009
      /* cc_br_bomb is a valid stream that expands to 4 MiB; the budget, not a
1010
         corrupt frame, is what must reject it. */
1011
      assertf((LLint) (4 * 1024 * 1024) >
1012
              hts_codec_maxout((LLint) sizeof(cc_br_bomb)));
1013
      assertf(ae_write_raw(inpath, cc_br_bomb, sizeof(cc_br_bomb)) == 0);
1014
      assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) < 0);
1015
    }
1016
#endif
1017
#if HTS_USEZSTD
1018
    {
1019
      const size_t bomblen = 4 * 1024 * 1024;
1020
      const size_t bound = ZSTD_compressBound(bomblen);
1021
      unsigned char *bomb = malloct(bomblen);
1022
      unsigned char *zbuf = malloct(bound);
1023
      unsigned char head[16];
1024
      size_t zlen;
1025
1026
      assertf(bomb != NULL && zbuf != NULL);
1027
      zlen = ZSTD_compress(zbuf, bound, cc_text, tlen, 6);
1028
      assertf(!ZSTD_isError(zlen));
1029
      assertf(ae_write_raw(inpath, zbuf, zlen) == 0);
1030
      assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) == (int) tlen);
1031
      assertf(ae_check_decoded(outpath, cc_text, tlen) == 0);
1032
      assertf(hts_codec_head(HTS_CODEC_ZSTD, zbuf, zlen, head, sizeof(head)) ==
1033
              sizeof(head));
1034
      assertf(memcmp(head, cc_text, sizeof(head)) == 0);
1035
      assertf(ae_write_raw(inpath, zbuf, zlen - 4) == 0);
1036
      assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) < 0);
1037
      memset(bomb, 'A', bomblen);
1038
      zlen = ZSTD_compress(zbuf, bound, bomb, bomblen, 6);
1039
      assertf(!ZSTD_isError(zlen));
1040
      /* the fixture must really be past the budget, whatever the ratio is */
1041
      assertf((LLint) bomblen > hts_codec_maxout((LLint) zlen));
1042
      assertf(ae_write_raw(inpath, zbuf, zlen) == 0);
1043
      assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) < 0);
1044
      freet(bomb);
1045
      freet(zbuf);
1046
    }
1047
#endif
1048
    /* a coding we can not undo yields no file at all */
1049
0
    assertf(hts_codec_unpack(HTS_CODEC_UNSUPPORTED, inpath, outpath) < 0);
1050
0
  }
1051
0
  printf("contentcodings self-test OK\n");
1052
0
  return 0;
1053
0
}
1054
1055
/* The errno contract the disk-full classification rests on: a decode that
1056
   cannot write leaves the write's errno, a body the decoder refuses leaves 0
1057
   (#1398). Both are poisoned first, so a caller that never sets errno fails. */
1058
0
static int st_codecerrno(httrackp *opt, int argc, char **argv) {
1059
0
  static const unsigned char body[] = "codec errno contract, round-tripped";
1060
0
  const size_t len = sizeof(body) - 1;
1061
0
  char inpath[HTS_URLMAXSIZE], outpath[HTS_URLMAXSIZE];
1062
0
  FILE *fp;
1063
1064
0
  (void) opt;
1065
0
  if (argc < 2) {
1066
0
    fprintf(stderr, "usage: -#test=codecerrno <dir> <doomed-out>\n");
1067
0
    return 1;
1068
0
  }
1069
0
  snprintf(inpath, sizeof(inpath), "%s/ce-in.z", argv[0]);
1070
0
  snprintf(outpath, sizeof(outpath), "%s/ce-out", argv[0]);
1071
0
  assertf(ae_write_packed(inpath, 16 + MAX_WBITS, body, len) == 0);
1072
0
  errno = ENOSPC;
1073
0
  assertf(hts_zunpack(inpath, outpath) == (int) len);
1074
0
  errno = 0;
1075
0
  assertf(hts_zunpack(inpath, argv[1]) == -1);
1076
0
  assertf(errno == ENOSPC);
1077
0
  errno = 0;
1078
0
  assertf(hts_codec_unpack(HTS_CODEC_DEFLATE, inpath, argv[1]) == -1);
1079
0
  assertf(errno == ENOSPC);
1080
0
  errno = ENOSPC;
1081
0
  assertf(hts_codec_unpack(HTS_CODEC_UNSUPPORTED, inpath, outpath) == -1);
1082
0
  assertf(errno == 0);
1083
  /* Past the gzip magic, so the header still parses and the identity fallback
1084
     stays out of it: the stream itself is what fails. */
1085
0
  fp = FOPEN(inpath, "r+b");
1086
0
  assertf(fp != NULL);
1087
0
  assertf(fseek(fp, 12, SEEK_SET) == 0);
1088
0
  assertf(hts_fwrite_exact("\xff\xff\xff\xff\xff\xff\xff\xff", 8, fp));
1089
0
  assertf(fclose(fp) == 0);
1090
0
  errno = ENOSPC;
1091
0
  assertf(hts_zunpack(inpath, outpath) == -1);
1092
0
  assertf(errno == 0);
1093
0
  errno = ENOSPC;
1094
0
  assertf(hts_codec_unpack(HTS_CODEC_DEFLATE, inpath, outpath) == -1);
1095
0
  assertf(errno == 0);
1096
#if HTS_USEBROTLI
1097
  /* and a backend of its own, where the write goes through codec_sink() */
1098
  assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text)) == 0);
1099
  errno = 0;
1100
  assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, argv[1]) == -1);
1101
  assertf(errno == ENOSPC);
1102
  assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text) - 4) == 0);
1103
  errno = ENOSPC;
1104
  assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) == -1);
1105
  assertf(errno == 0);
1106
#endif
1107
0
  printf("codecerrno: write failure kept, refused stream cleared\n");
1108
0
  return 0;
1109
0
}
1110
1111
/* The library's own view of the structs HTS_INET6 and HTS_USEOPENSSL shape.
1112
   381 diffs it against a consumer compiled from the installed headers alone. */
1113
0
static int st_pubheaders(httrackp *opt, int argc, char **argv) {
1114
0
  (void) opt;
1115
0
  (void) argc;
1116
0
  (void) argv;
1117
0
#define PUBH_SIZE(t) printf("sizeof %s = %lu\n", #t, (unsigned long) sizeof(t))
1118
0
#define PUBH_OFF(t, f)                                                         \
1119
0
  printf("offsetof %s.%s = %lu\n", #t, #f, (unsigned long) offsetof(t, f))
1120
0
  PUBH_SIZE(SOCaddr);
1121
0
  PUBH_SIZE(INTsys);
1122
0
  PUBH_SIZE(htsblk);
1123
0
  PUBH_OFF(htsblk, soc);
1124
0
  PUBH_OFF(htsblk, address);
1125
0
  PUBH_OFF(htsblk, fp);
1126
0
  PUBH_OFF(htsblk, lastmodified);
1127
0
  PUBH_OFF(htsblk, etag);
1128
0
  PUBH_OFF(htsblk, debugid);
1129
0
  PUBH_SIZE(httrackp);
1130
0
  printf("HTS_INET6 = %d\n", (int) HTS_INET6);
1131
0
  printf("HTS_USEOPENSSL = %d\n", (int) HTS_USEOPENSSL);
1132
0
#undef PUBH_SIZE
1133
0
#undef PUBH_OFF
1134
0
  return 0;
1135
0
}
1136
1137
/* ------------------------------------------------------------ */
1138
/* Registry: this module's tests, in the order -#test lists them. */
1139
/* ------------------------------------------------------------ */
1140
1141
/* <parse|scan> <text> <min> <max>: what hts_parse_llint() or hts_scan_llint()
1142
   reads from text, and how many bytes it moves past. */
1143
0
static int st_intparse(httrackp *opt, int argc, char **argv) {
1144
0
  const char *end = argv[1];
1145
0
  LLint min, max, value = -1;
1146
1147
0
  (void) opt;
1148
0
  if (argc != 4) {
1149
0
    fprintf(stderr, "int-parse: needs parse|scan <text> <min> <max>\n");
1150
0
    return 1;
1151
0
  }
1152
0
  min = (LLint) strtoll(argv[2], NULL, 10);
1153
0
  max = (LLint) strtoll(argv[3], NULL, 10);
1154
0
  if (strcmp(argv[0], "parse") == 0) {
1155
0
    const hts_boolean ok = hts_parse_llint(argv[1], &end, min, max, &value);
1156
1157
0
    printf("%s value=" LLintP " end=%d\n", ok ? "ok" : "refused", value,
1158
0
           (int) (end - argv[1]));
1159
0
  } else {
1160
0
    const hts_scan_result got = hts_scan_llint(&end, min, max, &value);
1161
1162
0
    printf("got=%d value=" LLintP " end=%d\n", (int) got, value,
1163
0
           (int) (end - argv[1]));
1164
0
  }
1165
0
  return 0;
1166
0
}
1167
1168
/* The differential below: each converted site, as it parsed before (legacy_*,
1169
   a frozen copy using sscanf) and as it parses now, rendered into out. */
1170
typedef void (*st_int_site_fn)(const char *in, char *out, size_t size);
1171
1172
0
static void st_int_treathead(const char *field, const char *in, htsblk *r) {
1173
0
  char BIGSTK line[HTS_URLMAXSIZE * 2];
1174
1175
0
  memset(r, 0, sizeof(*r));
1176
0
  r->totalsize = -1;
1177
0
  line[0] = '\0';
1178
0
  strlncatbuff(line, field, sizeof(line), sizeof(line) - 1);
1179
0
  strlncatbuff(line, in, sizeof(line), sizeof(line) - 1);
1180
0
  treathead(NULL, "www.example.com", "/", r, line);
1181
0
}
1182
1183
0
static void legacy_clen(const char *in, char *out, size_t size) {
1184
0
  LLint totalsize = -1;
1185
0
  int empty = 0;
1186
1187
0
  if (sscanf(in, LLintP, &totalsize) == 1 && totalsize == 0)
1188
0
    empty = 1;
1189
0
  snprintf(out, size, "totalsize=" LLintP " empty=%d", totalsize, empty);
1190
0
}
1191
1192
0
static void current_clen(const char *in, char *out, size_t size) {
1193
0
  htsblk r;
1194
1195
0
  st_int_treathead("Content-Length:", in, &r);
1196
0
  snprintf(out, size, "totalsize=" LLintP " empty=%d", (LLint) r.totalsize,
1197
0
           (int) r.empty);
1198
0
}
1199
1200
0
static void legacy_crange(const char *in, char *out, size_t size) {
1201
0
  LLint crange_start = 0, crange_end = 0, crange = 0;
1202
0
  const char *a;
1203
1204
0
  for (a = in; is_space(*a); a++)
1205
0
    ;
1206
0
  if (strncasecmp(a, "bytes ", 6) == 0) {
1207
0
    for (a += 6; is_space(*a); a++)
1208
0
      ;
1209
0
    if (sscanf(a, LLintP "-" LLintP "/" LLintP, &crange_start, &crange_end,
1210
0
               &crange) != 3) {
1211
0
      crange_start = 0;
1212
0
      crange_end = 0;
1213
0
      crange = 0;
1214
0
      a = strchr(in, '/');
1215
0
      if (a != NULL) {
1216
0
        a++;
1217
0
        if (sscanf(a, LLintP, &crange) == 1 && crange >= 0) {
1218
0
          crange_start = 0;
1219
0
          crange_end = crange - 1;
1220
0
        } else {
1221
0
          crange = 0;
1222
0
        }
1223
0
      }
1224
0
    }
1225
0
    if (crange_start < 0 || crange_end < 0 || crange < 0)
1226
0
      crange_start = crange_end = crange = 0;
1227
0
  }
1228
0
  snprintf(out, size, LLintP " " LLintP " " LLintP, crange_start, crange_end,
1229
0
           crange);
1230
0
}
1231
1232
0
static void current_crange(const char *in, char *out, size_t size) {
1233
0
  htsblk r;
1234
1235
0
  st_int_treathead("Content-Range:", in, &r);
1236
0
  snprintf(out, size, LLintP " " LLintP " " LLintP, (LLint) r.crange_start,
1237
0
           (LLint) r.crange_end, (LLint) r.crange);
1238
0
}
1239
1240
0
static void legacy_keepalive(const char *in, char *out, size_t size) {
1241
0
  int keep_alive = 0, keep_alive_t = 0, keep_alive_max = 0;
1242
0
  const char *a = in;
1243
1244
0
  while (is_space(*a))
1245
0
    a++;
1246
0
  if (*a) {
1247
0
    const char *p;
1248
1249
0
    keep_alive = 1;
1250
0
    keep_alive_max = 10;
1251
0
    keep_alive_t = 15;
1252
0
    if ((p = strstr(a, "timeout="))) {
1253
0
      p += strlen("timeout=");
1254
0
      sscanf(p, "%d", &keep_alive_t);
1255
0
    }
1256
0
    if ((p = strstr(a, "max="))) {
1257
0
      p += strlen("max=");
1258
0
      sscanf(p, "%d", &keep_alive_max);
1259
0
    }
1260
0
    if (keep_alive_max <= 1 || keep_alive_t < 1)
1261
0
      keep_alive = 0;
1262
0
  }
1263
0
  snprintf(out, size, "keep_alive=%d timeout=%d max=%d", keep_alive,
1264
0
           keep_alive_t, keep_alive_max);
1265
0
}
1266
1267
0
static void current_keepalive(const char *in, char *out, size_t size) {
1268
0
  htsblk r;
1269
1270
0
  st_int_treathead("Keep-Alive:", in, &r);
1271
0
  snprintf(out, size, "keep_alive=%d timeout=%d max=%d", r.keep_alive,
1272
0
           r.keep_alive_t, r.keep_alive_max);
1273
0
}
1274
1275
0
static void legacy_ftpsize(const char *in, char *out, size_t size) {
1276
0
  const char *szstr = strchr(in, ' ');
1277
0
  LLint value = 0;
1278
1279
0
  if (szstr != NULL && sscanf(szstr + 1, LLintP, &value) == HTS_SCAN_OK)
1280
0
    snprintf(out, size, "size=" LLintP, value);
1281
0
  else
1282
0
    snprintf(out, size, "none");
1283
0
}
1284
1285
0
static void current_ftpsize(const char *in, char *out, size_t size) {
1286
0
  LLint value = 0;
1287
1288
0
  if (ftp_parse_size(in, &value))
1289
0
    snprintf(out, size, "size=" LLintP, value);
1290
0
  else
1291
0
    snprintf(out, size, "none");
1292
0
}
1293
1294
0
static struct tm *legacy_convert_time_rfc822(struct tm *result, const char *s) {
1295
0
  char months[] = "jan feb mar apr may jun jul aug sep oct nov dec";
1296
0
  char str[256];
1297
0
  char *a;
1298
0
  int result_mm = -1, result_dd = -1, result_n1 = -1, result_n2 = -1;
1299
0
  int result_n3 = -1, result_n4 = -1;
1300
1301
0
  if ((int) strlen(s) > 200)
1302
0
    return NULL;
1303
0
  strcpybuff(str, s);
1304
0
  hts_lowcase(str);
1305
0
  while ((a = strchr(str, '-')))
1306
0
    *a = ' ';
1307
0
  while ((a = strchr(str, ':')))
1308
0
    *a = ' ';
1309
0
  while ((a = strchr(str, ',')))
1310
0
    *a = ' ';
1311
0
  a = str;
1312
0
  while (*a) {
1313
0
    char *first, *last;
1314
0
    char tok[256];
1315
1316
0
    while (*a == ' ')
1317
0
      a++;
1318
0
    first = a;
1319
0
    while ((*a) && (*a != ' '))
1320
0
      a++;
1321
0
    last = a;
1322
0
    tok[0] = '\0';
1323
0
    if (first != last) {
1324
0
      char *pos;
1325
1326
0
      strncatbuff(tok, first, (int) (last - first));
1327
0
      if ((pos = strstr(months, tok))) {
1328
0
        result_mm = ((int) (pos - months)) / 4;
1329
0
      } else {
1330
0
        int number;
1331
1332
0
        if (sscanf(tok, "%d", &number) == 1) {
1333
0
          if (result_dd < 0)
1334
0
            result_dd = number;
1335
0
          else if (result_n1 < 0)
1336
0
            result_n1 = number;
1337
0
          else if (result_n2 < 0)
1338
0
            result_n2 = number;
1339
0
          else if (result_n3 < 0)
1340
0
            result_n3 = number;
1341
0
          else if (result_n4 < 0)
1342
0
            result_n4 = number;
1343
0
        }
1344
0
      }
1345
0
    }
1346
0
  }
1347
0
  if ((result_n1 >= 0) && (result_mm >= 0) && (result_dd >= 0) &&
1348
0
      (result_n2 >= 0) && (result_n3 >= 0) && (result_n4 >= 0)) {
1349
0
    memset(result, 0, sizeof(*result));
1350
0
    if (result_n4 >= 1000) {
1351
0
      result->tm_year = result_n4 - 1900;
1352
0
      result->tm_hour = result_n1;
1353
0
      result->tm_min = result_n2;
1354
0
      result->tm_sec = result_n3 > 0 ? result_n3 : 0;
1355
0
    } else {
1356
0
      result->tm_hour = result_n2;
1357
0
      result->tm_min = result_n3;
1358
0
      result->tm_sec = result_n4 > 0 ? result_n4 : 0;
1359
0
      if (result_n1 <= 50)
1360
0
        result->tm_year = result_n1 + 100;
1361
0
      else if (result_n1 < 1000)
1362
0
        result->tm_year = result_n1;
1363
0
      else
1364
0
        result->tm_year = result_n1 - 1900;
1365
0
    }
1366
0
    result->tm_mon = result_mm;
1367
0
    result->tm_mday = result_dd;
1368
0
    return result;
1369
0
  }
1370
0
  return NULL;
1371
0
}
1372
1373
0
static void st_int_print_tm(const struct tm *tm, char *out, size_t size) {
1374
0
  if (tm == NULL)
1375
0
    snprintf(out, size, "none");
1376
0
  else
1377
0
    snprintf(out, size, "y=%d m=%d d=%d %d:%d:%d", tm->tm_year, tm->tm_mon,
1378
0
             tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
1379
0
}
1380
1381
0
static void legacy_date(const char *in, char *out, size_t size) {
1382
0
  struct tm tm;
1383
1384
0
  st_int_print_tm(legacy_convert_time_rfc822(&tm, in), out, size);
1385
0
}
1386
1387
0
static void current_date(const char *in, char *out, size_t size) {
1388
0
  struct tm tm;
1389
1390
0
  st_int_print_tm(convert_time_rfc822(&tm, in), out, size);
1391
0
}
1392
1393
/* htsserver's and proxytrack's Content-length, which live outside the
1394
   library: their parse, then what it decides, copied from each program. */
1395
0
static void st_int_server_read(LLint length, char *out, size_t size) {
1396
0
  const LLint buffer = 1000; // stands for buffer_size - 2
1397
1398
0
  snprintf(out, size, "read=" LLintP,
1399
0
           length > buffer ? buffer
1400
0
           : length > 0    ? length
1401
0
                           : 0);
1402
0
}
1403
1404
0
static void legacy_server(const char *in, char *out, size_t size) {
1405
0
  LLint length = 1000;
1406
1407
0
  sscanf(in, LLintP, &length);
1408
0
  st_int_server_read(length, out, size);
1409
0
}
1410
1411
0
static void current_server(const char *in, char *out, size_t size) {
1412
0
  const char *a = in;
1413
0
  LLint length = 1000;
1414
1415
0
  if (hts_scan_llint(&a, 0, INT64_MAX, &length) == HTS_SCAN_REFUSED)
1416
0
    length = 0;
1417
0
  st_int_server_read(length, out, size);
1418
0
}
1419
1420
0
static void legacy_proxytrack(const char *in, char *out, size_t size) {
1421
0
  int length = 0;
1422
1423
0
  if (sscanf(in, "%d", &length) != 1)
1424
0
    snprintf(out, size, "error");
1425
0
  else
1426
0
    snprintf(out, size, "length=%d", length);
1427
0
}
1428
1429
0
static void current_proxytrack(const char *in, char *out, size_t size) {
1430
0
  const char *a = in;
1431
0
  LLint value;
1432
1433
0
  if (hts_scan_llint(&a, 0, INT_MAX, &value) == HTS_SCAN_OK)
1434
0
    snprintf(out, size, "length=%d", (int) value);
1435
0
  else
1436
0
    snprintf(out, size, "error");
1437
0
}
1438
1439
0
static void legacy_port(const char *in, char *out, size_t size) {
1440
0
  char *end;
1441
0
  long p;
1442
1443
0
  if (!isdigit((unsigned char) *in) ||
1444
0
      (p = strtol(in, &end, 10), *end != '\0') || p < 1 || p > 65535)
1445
0
    snprintf(out, size, "refused");
1446
0
  else
1447
0
    snprintf(out, size, "port=%ld", p);
1448
0
}
1449
1450
0
static void current_port(const char *in, char *out, size_t size) {
1451
0
  int port = -1;
1452
1453
0
  if (hts_parse_url_port(in, &port))
1454
0
    snprintf(out, size, "port=%d", port);
1455
0
  else
1456
0
    snprintf(out, size, "refused");
1457
0
}
1458
1459
struct st_int_site {
1460
  const char *name;
1461
  st_int_site_fn legacy, current;
1462
  LLint max;                // the site's bound, for the generated allowlist
1463
  hts_boolean sign_read;    // the date parser reads either sign as no sign
1464
  hts_boolean exact;        // no intended change at all
1465
  const char *templates[5]; // generated inputs: one "%s" for the number
1466
};
1467
1468
static const struct st_int_site st_int_sites[] = {
1469
    {"content-length",
1470
     legacy_clen,
1471
     current_clen,
1472
     INT64_MAX,
1473
     HTS_FALSE,
1474
     HTS_FALSE,
1475
     {"%s", " %s", NULL}},
1476
    {"content-range",
1477
     legacy_crange,
1478
     current_crange,
1479
     INT64_MAX,
1480
     HTS_FALSE,
1481
     HTS_FALSE,
1482
     {" bytes %s-9/10", " bytes 0-%s/10", " bytes 0-9/%s", " bytes */%s",
1483
      NULL}},
1484
    {"keep-alive",
1485
     legacy_keepalive,
1486
     current_keepalive,
1487
     INT_MAX,
1488
     HTS_FALSE,
1489
     HTS_FALSE,
1490
     {" timeout=%s, max=100", " timeout=5, max=%s", NULL}},
1491
    {"ftp-size",
1492
     legacy_ftpsize,
1493
     current_ftpsize,
1494
     INT64_MAX,
1495
     HTS_FALSE,
1496
     HTS_FALSE,
1497
     {"213 %s", "213%s", NULL}},
1498
    {"date",
1499
     legacy_date,
1500
     current_date,
1501
     INT_MAX,
1502
     HTS_TRUE,
1503
     HTS_FALSE,
1504
     {"Sun, 06 Nov 1994 08:49:%s GMT", "%s Nov 1994 08:49:37 GMT",
1505
      "Sun Nov  6 08:49:37 %s", "06 Nov 94 08:49 %s", NULL}},
1506
    {"server-length",
1507
     legacy_server,
1508
     current_server,
1509
     INT64_MAX,
1510
     HTS_FALSE,
1511
     HTS_FALSE,
1512
     {" %s", NULL}},
1513
    {"proxytrack-length",
1514
     legacy_proxytrack,
1515
     current_proxytrack,
1516
     INT_MAX,
1517
     HTS_FALSE,
1518
     HTS_FALSE,
1519
     {" %s", NULL}},
1520
    {"url-port",
1521
     legacy_port,
1522
     current_port,
1523
     65535,
1524
     HTS_FALSE,
1525
     HTS_TRUE,
1526
     {"%s", NULL}},
1527
};
1528
1529
/* A NULL want means the case parses as before, and a want names a change. */
1530
static const struct {
1531
  const char *site, *in, *want;
1532
} st_int_cases[] = {
1533
    {"content-length", " 0", NULL},
1534
    {"content-length", " 00123x", NULL},
1535
    {"content-length", "\t123 456", NULL},
1536
    {"content-length", " abc", NULL},
1537
    {"content-length", "", NULL},
1538
    {"content-length", " 9223372036854775807", NULL},
1539
    {"content-length", " -5", "totalsize=-1 empty=0"},
1540
    {"content-length", " +5", "totalsize=-1 empty=0"},
1541
    {"content-length", " -0", "totalsize=-1 empty=0"},
1542
    {"content-length", " 9223372036854775808", "totalsize=-1 empty=0"},
1543
    {"content-range", " bytes 0-70870/70871", NULL},
1544
    {"content-range", " bytes */70871", NULL},
1545
    {"content-range", " bytes 0 - 5/10", NULL},
1546
    {"content-range", " bytes 0- 5/ 10", NULL},
1547
    {"content-range", " bytes -5/10", NULL},
1548
    {"content-range", " bytes -5-10/20", NULL},
1549
    {"content-range", " bytes 0-5", NULL},
1550
    {"content-range", " bytes */0", NULL},
1551
    {"content-range", " bytes */-3", NULL},
1552
    {"content-range", " items 1-2/3", NULL},
1553
    {"content-range", " bytes 0-9223372036854775807/9223372036854775807", NULL},
1554
    {"content-range", " bytes +0-5/10", "0 0 0"},
1555
    {"content-range", " bytes 0-5/9223372036854775808", "0 0 0"},
1556
    {"content-range", " bytes */+10", "0 0 0"},
1557
    {"content-range", " bytes */9223372036854775808", "0 0 0"},
1558
    {"keep-alive", " timeout=5, max=100", NULL},
1559
    {"keep-alive", " timeout=0", NULL},
1560
    {"keep-alive", " max=1", NULL},
1561
    {"keep-alive", " timeout=abc", NULL},
1562
    {"keep-alive", " timeout= 7", NULL},
1563
    {"keep-alive", " xmax=50",
1564
     "keep_alive=1 timeout=15 max=10"}, // whole names only
1565
    {"keep-alive", " timeout=007x", NULL},
1566
    {"keep-alive", " timeout=-1", "keep_alive=0 timeout=0 max=10"},
1567
    {"keep-alive", " timeout=+30", "keep_alive=0 timeout=0 max=10"},
1568
    {"keep-alive", " timeout=4294967311", "keep_alive=0 timeout=0 max=10"},
1569
    {"keep-alive", " max=-1", "keep_alive=0 timeout=15 max=0"},
1570
    {"ftp-size", "213 1234", NULL},
1571
    {"ftp-size", "213  1234", NULL},
1572
    {"ftp-size", "213 12ab", NULL},
1573
    {"ftp-size", "213", NULL},
1574
    {"ftp-size", "213 x", NULL},
1575
    {"ftp-size", "213 9223372036854775807", NULL},
1576
    {"ftp-size", "213 -1", "none"},
1577
    {"ftp-size", "213 +1", "none"},
1578
    {"ftp-size", "213 99999999999999999999", "none"},
1579
    {"date", "Sun, 06 Nov 1994 08:49:37 GMT", NULL},
1580
    {"date", "Sunday, 06-Nov-94 08:49:37 GMT", NULL},
1581
    {"date", "Sun Nov  6 08:49:37 1994", NULL},
1582
    {"date", "Sun, 06 Nov 1994 08:49:37 +0100", NULL},
1583
    {"date", "\t06 Nov 1994 08:49:37", NULL},
1584
    {"date", "garbage", NULL},
1585
    {"date", "06 Nov 94 08:49 +0000", NULL},
1586
    {"date", "Sun, 06 Nov 1994 08:49 +0100", NULL},
1587
    {"date", "Sun, 06 Nov 1994 08:49:37 GMT+1", NULL},
1588
    {"date", "Sun, 06 Nov 1994 08:49:37 \t+1", NULL},
1589
    {"date", "Sun, 06 Nov 1994 08:49:37 ++1", NULL},
1590
    {"date", "4294967302 Nov 1994 08:49:37", "none"},
1591
    {"server-length", " 50", NULL},
1592
    {"server-length", " abc", NULL},
1593
    {"server-length", " -5", NULL},
1594
    {"server-length", " +5", "read=0"},
1595
    {"server-length", " 9223372036854775808", "read=0"},
1596
    {"proxytrack-length", " 50", NULL},
1597
    {"proxytrack-length", " abc", NULL},
1598
    {"proxytrack-length", " -5", "error"},
1599
    {"proxytrack-length", " +5", "error"},
1600
    {"proxytrack-length", " 2147483648", "error"},
1601
    {"url-port", "80", NULL},
1602
    {"url-port", "00080", NULL},
1603
    {"url-port", "65536", NULL},
1604
    {"url-port", "+80", NULL},
1605
    {"url-port", " 80", NULL},
1606
    {"url-port", "80x", NULL},
1607
    {"url-port", "", NULL},
1608
    {"url-port", "99999999999999999999", NULL},
1609
};
1610
1611
/* Copy pattern into out with its one "%s" replaced by value. */
1612
static void st_int_fill(char *out, size_t size, const char *pattern,
1613
0
                        const char *value) {
1614
0
  const char *hole = strstr(pattern, "%s");
1615
1616
0
  out[0] = '\0';
1617
0
  strlncatbuff(out, pattern, size, (size_t) (hole - pattern));
1618
0
  strlncatbuff(out, value, size, size - 1);
1619
0
  strlncatbuff(out, hole + 2, size, size - 1);
1620
0
}
1621
1622
/* Is the digit run at s larger than max? */
1623
0
static hts_boolean st_int_oversized(const char *s, LLint max) {
1624
0
  LLint v;
1625
1626
0
  return *s != '\0' && !hts_parse_llint(s, NULL, 0, max, &v);
1627
0
}
1628
1629
/* Every converted site against its legacy copy: the hand cases, then each
1630
   template filled with every space x sign x digits x suffix combination. Only
1631
   a signed or out-of-range number may differ, and the hand cases pin how. */
1632
0
static int st_intparsediff(httrackp *opt, int argc, char **argv) {
1633
0
  static const char *const spaces[] = {"", " ", "\t", "  "};
1634
0
  static const char *const signs[] = {"", "+", "-"};
1635
0
  static const char *const digits[] = {"",
1636
0
                                       "0",
1637
0
                                       "00",
1638
0
                                       "7",
1639
0
                                       "42",
1640
0
                                       "0042",
1641
0
                                       "65535",
1642
0
                                       "65536",
1643
0
                                       "2147483647",
1644
0
                                       "2147483648",
1645
0
                                       "4294967295",
1646
0
                                       "4294967311",
1647
0
                                       "9223372036854775807",
1648
0
                                       "9223372036854775808",
1649
0
                                       "99999999999999999999"};
1650
0
  static const char *const suffixes[] = {"", "x", " 7", ".5", "/3"};
1651
0
  size_t i, s, t, w, g, d, x;
1652
0
  int compared = 0, changed = 0, failures = 0;
1653
0
  char in[256], old[256], cur[256], gen[128];
1654
1655
0
  (void) opt;
1656
0
  if (argc == 2) { // One input through one site, as it parses now.
1657
0
    for (s = 0; s < sizeof(st_int_sites) / sizeof(st_int_sites[0]); s++)
1658
0
      if (strcmp(st_int_sites[s].name, argv[0]) == 0) {
1659
0
        st_int_sites[s].current(argv[1], cur, sizeof(cur));
1660
0
        printf("%s\n", cur);
1661
0
        return 0;
1662
0
      }
1663
0
    fprintf(stderr, "int-parse-diff: no site \"%s\"\n", argv[0]);
1664
0
    return 1;
1665
0
  }
1666
0
  for (i = 0; i < sizeof(st_int_cases) / sizeof(st_int_cases[0]); i++) {
1667
0
    for (s = 0; strcmp(st_int_sites[s].name, st_int_cases[i].site) != 0; s++)
1668
0
      ;
1669
0
    st_int_sites[s].legacy(st_int_cases[i].in, old, sizeof(old));
1670
0
    st_int_sites[s].current(st_int_cases[i].in, cur, sizeof(cur));
1671
    // An overflow's legacy result is up to the libc, so a want pins only ours.
1672
0
    if (strcmp(cur, st_int_cases[i].want != NULL ? st_int_cases[i].want
1673
0
                                                 : old) != 0) {
1674
0
      printf("FAIL %s \"%s\": legacy \"%s\" now \"%s\" want \"%s\"\n",
1675
0
             st_int_sites[s].name, st_int_cases[i].in, old, cur,
1676
0
             st_int_cases[i].want != NULL ? st_int_cases[i].want : old);
1677
0
      failures++;
1678
0
    }
1679
0
    compared++;
1680
0
    changed += st_int_cases[i].want != NULL;
1681
0
  }
1682
0
  for (s = 0; s < sizeof(st_int_sites) / sizeof(st_int_sites[0]); s++) {
1683
0
    const struct st_int_site *site = &st_int_sites[s];
1684
1685
0
    for (t = 0; site->templates[t] != NULL; t++)
1686
0
      for (w = 0; w < sizeof(spaces) / sizeof(spaces[0]); w++)
1687
0
        for (g = 0; g < sizeof(signs) / sizeof(signs[0]); g++)
1688
0
          for (d = 0; d < sizeof(digits) / sizeof(digits[0]); d++)
1689
0
            for (x = 0; x < sizeof(suffixes) / sizeof(suffixes[0]); x++) {
1690
0
              const hts_boolean is_signed =
1691
0
                  *signs[g] != '\0' && *digits[d] != '\0' && !site->sign_read;
1692
1693
0
              snprintf(gen, sizeof(gen), "%s%s%s%s", spaces[w], signs[g],
1694
0
                       digits[d], suffixes[x]);
1695
0
              st_int_fill(in, sizeof(in), site->templates[t], gen);
1696
0
              if (!site->exact &&
1697
0
                  (is_signed || st_int_oversized(digits[d], site->max))) {
1698
0
                changed++;
1699
0
                continue;
1700
0
              }
1701
0
              site->legacy(in, old, sizeof(old));
1702
0
              site->current(in, cur, sizeof(cur));
1703
0
              compared++;
1704
0
              if (strcmp(old, cur) != 0) {
1705
0
                printf("FAIL %s \"%s\": legacy \"%s\" now \"%s\"\n", site->name,
1706
0
                       in, old, cur);
1707
0
                failures++;
1708
0
              }
1709
0
            }
1710
0
  }
1711
0
  printf("int-parse-diff: %d compared, %d intended changes, %d failures\n",
1712
0
         compared, changed, failures);
1713
0
  return failures != 0;
1714
0
}
1715
1716
/* hts_header_param() on one value: found or not, and what it copied. */
1717
0
static int st_headerparam(httrackp *opt, int argc, char **argv) {
1718
0
  char out[256];
1719
0
  int size;
1720
0
  hts_boolean found;
1721
1722
0
  (void) opt;
1723
0
  if (argc != 4) {
1724
0
    fprintf(stderr, "header-param: needs <value> <seps> <name> <size>\n");
1725
0
    return 1;
1726
0
  }
1727
0
  size = atoi(argv[3]);
1728
0
  if (size < 0 || (size_t) size > sizeof(out)) {
1729
0
    fprintf(stderr, "header-param: size out of probe range\n");
1730
0
    return 1;
1731
0
  }
1732
0
  memset(out, 'X', sizeof(out));
1733
0
  found = hts_header_param(argv[0], argv[1], argv[2], out, (size_t) size);
1734
0
  printf("%s [%s]\n", found ? "yes" : "no", size != 0 ? out : "-");
1735
0
  return 0;
1736
0
}
1737
1738
/* Each converted treathead site has a frozen legacy parse and a current one. */
1739
0
static void legacy_ctype(const char *in, char *out, size_t size) {
1740
0
  char line[1024], tempo[1100];
1741
0
  char contenttype[HTS_MIMETYPE_SIZE] = "", charset[HTS_MIMETYPE_SIZE] = "";
1742
0
  char *a;
1743
1744
0
  line[0] = '\0';
1745
0
  strlncatbuff(line, in, sizeof(line), sizeof(line) - 1);
1746
0
  a = strchr(line, ';');
1747
0
  if (a) {
1748
0
    *a = '\0';
1749
0
    a++;
1750
0
    while (is_space(*a))
1751
0
      a++;
1752
0
    if (strfield(a, "charset")) {
1753
0
      a += 7;
1754
0
      while (is_space(*a))
1755
0
        a++;
1756
0
      if (*a == '=') {
1757
0
        a++;
1758
0
        while (is_space(*a))
1759
0
          a++;
1760
0
        if (*a == '\"')
1761
0
          a++;
1762
0
        while (is_space(*a))
1763
0
          a++;
1764
0
        if (*a) {
1765
0
          char *chs = a;
1766
1767
0
          while (*a && !is_space(*a) && *a != '\"' && *a != ';')
1768
0
            a++;
1769
0
          *a = '\0';
1770
0
          if (*chs && strlen(chs) < sizeof(charset) - 2)
1771
0
            strcpybuff(charset, chs);
1772
0
        }
1773
0
      }
1774
0
    }
1775
0
  }
1776
0
  if (sscanf(line, "%1099s", tempo) == 1 &&
1777
0
      strlen(tempo) < sizeof(contenttype) - 2)
1778
0
    strcpybuff(contenttype, tempo);
1779
0
  snprintf(out, size, "contenttype=%s charset=%s", contenttype, charset);
1780
0
}
1781
1782
0
static void current_ctype(const char *in, char *out, size_t size) {
1783
0
  htsblk r;
1784
1785
0
  st_int_treathead("Content-Type:", in, &r);
1786
0
  snprintf(out, size, "contenttype=%s charset=%s", r.contenttype, r.charset);
1787
0
}
1788
1789
0
static void legacy_cdispo(const char *in, char *out, size_t size) {
1790
0
  char cdispo[256] = "";
1791
0
  const char *v = in;
1792
1793
0
  while (is_realspace(*v))
1794
0
    v++;
1795
0
  if (strlen(v) < 250) {
1796
0
    char tmp[256];
1797
0
    char *a;
1798
1799
0
    strcpybuff(tmp, v);
1800
0
    a = strstr(tmp, "filename=");
1801
0
    if (a) {
1802
0
      char *c;
1803
1804
0
      a += strlen("filename=");
1805
0
      while (is_space(*a))
1806
0
        a++;
1807
0
      while ((c = strchr(a, '/')))
1808
0
        a = c + 1;
1809
0
      hts_rtrim(a, HTS_SPACES);
1810
0
      if (strlen(a) < 200)
1811
0
        strcpybuff(cdispo, a);
1812
0
    }
1813
0
  }
1814
0
  snprintf(out, size, "cdispo=%s", cdispo);
1815
0
}
1816
1817
0
static void current_cdispo(const char *in, char *out, size_t size) {
1818
0
  htsblk r;
1819
1820
0
  st_int_treathead("Content-Disposition:", in, &r);
1821
0
  snprintf(out, size, "cdispo=%s", r.cdispo);
1822
0
}
1823
1824
0
static void legacy_ka_param(const char *s, int *value) {
1825
0
  LLint v;
1826
0
  const hts_scan_result got = hts_scan_llint(&s, 0, INT_MAX, &v);
1827
1828
0
  if (got != HTS_SCAN_NONE)
1829
0
    *value = got == HTS_SCAN_OK ? (int) v : 0;
1830
0
}
1831
1832
0
static void legacy_keepalive_scan(const char *in, char *out, size_t size) {
1833
0
  int keep_alive = 0, keep_alive_t = 0, keep_alive_max = 0;
1834
0
  const char *a = in;
1835
1836
0
  while (is_space(*a))
1837
0
    a++;
1838
0
  if (*a) {
1839
0
    const char *p;
1840
1841
0
    keep_alive = 1;
1842
0
    keep_alive_max = 10;
1843
0
    keep_alive_t = 15;
1844
0
    if ((p = strstr(a, "timeout=")))
1845
0
      legacy_ka_param(p + strlen("timeout="), &keep_alive_t);
1846
0
    if ((p = strstr(a, "max=")))
1847
0
      legacy_ka_param(p + strlen("max="), &keep_alive_max);
1848
0
    if (keep_alive_max <= 1 || keep_alive_t < 1)
1849
0
      keep_alive = 0;
1850
0
  }
1851
0
  snprintf(out, size, "keep_alive=%d timeout=%d max=%d", keep_alive,
1852
0
           keep_alive_t, keep_alive_max);
1853
0
}
1854
1855
/* Why a generated input may parse differently from before. */
1856
enum {
1857
  HP_LATER = 1,  // the parameter is not the first one
1858
  HP_TRAIL = 2,  // more parameters follow the file name
1859
  HP_SUFFIX = 4, // a longer name ends with the wanted one
1860
  HP_QUOTE = 8,  // a quoted value holds a ';' or a '\' escape
1861
  HP_SPACE = 16, // white space around the '='
1862
  HP_CASE = 32,  // the name is not in lower case
1863
  HP_COUNT = 6
1864
};
1865
1866
static const char *const st_hp_reasons[HP_COUNT] = {"later", "trail", "suffix",
1867
                                                    "quote", "space", "case"};
1868
1869
/* One parameter of the generated corpus. Key is the name it must match as, and
1870
   want what the site then reports, or NULL for a parameter it must skip. */
1871
struct st_hp_item {
1872
  const char *text, *key, *want;
1873
  int why;
1874
};
1875
1876
/* Prefix is what the site prints before the value, or NULL for Keep-Alive. */
1877
struct st_hp_site {
1878
  const char *name, *head, *param, *prefix;
1879
  st_int_site_fn legacy, current;
1880
  const char *seps[4];
1881
  struct st_hp_item items[10];
1882
};
1883
1884
static const struct st_hp_site st_hp_sites[] = {
1885
    {"content-type",
1886
     " text/html",
1887
     "charset",
1888
     "contenttype=text/html charset=",
1889
     legacy_ctype,
1890
     current_ctype,
1891
     {";", "; ", " ; ", NULL},
1892
     {{"charset=utf-8", "charset", "utf-8", 0},
1893
      {"charset=\"iso-8859-1\"", "charset", "iso-8859-1", 0},
1894
      {"CharSet = koi8-r", "charset", "koi8-r", 0},
1895
      {"charset=", "charset", "", 0},
1896
      {"xcharset=bad", NULL, NULL, 0},
1897
      {"q=\"a;charset=bad\"", NULL, NULL, HP_QUOTE},
1898
      {"format=flowed", NULL, NULL, 0},
1899
      {NULL, NULL, NULL, 0}}},
1900
    {"content-disposition",
1901
     " attachment",
1902
     "filename",
1903
     "cdispo=",
1904
     legacy_cdispo,
1905
     current_cdispo,
1906
     {";", "; ", " ; ", NULL},
1907
     {{"filename=a.txt", "filename", "a.txt", 0},
1908
      {"filename=\"b c.txt\"", "filename", "b c.txt", 0},
1909
      {"filename=\"dir/d.txt\"", "filename", "d.txt", 0},
1910
      {"FILENAME=e.txt", "filename", "e.txt", HP_CASE},
1911
      {"filename=\"g\\\"h.txt\"", "filename", "g\"h.txt", HP_QUOTE},
1912
      {"filename*=UTF-8''f.txt", NULL, NULL, 0},
1913
      {"xfilename=x.txt", NULL, NULL, HP_SUFFIX},
1914
      {"name=\"q;filename=bad\"", NULL, NULL, HP_QUOTE},
1915
      {"size=3", NULL, NULL, 0},
1916
      {NULL, NULL, NULL, 0}}},
1917
    {"keep-alive",
1918
     "",
1919
     NULL,
1920
     NULL,
1921
     legacy_keepalive_scan,
1922
     current_keepalive,
1923
     {",", ", ", ";", NULL},
1924
     {{"timeout=5", "timeout", "5", 0},
1925
      {"max=100", "max", "100", 0},
1926
      {"max=1", "max", "1", 0},
1927
      {"timeout = 7", "timeout", "7", HP_SPACE},
1928
      {"max=\"20\"", "max", "20", HP_QUOTE},
1929
      {"xmax=50", NULL, NULL, HP_SUFFIX},
1930
      {"xtimeout=9", NULL, NULL, HP_SUFFIX},
1931
      {"foo=bar", NULL, NULL, 0},
1932
      {NULL, NULL, NULL, 0}}},
1933
};
1934
1935
/* First item of seq whose key is key, or NULL. */
1936
static const struct st_hp_item *st_hp_first(const struct st_hp_item *const *seq,
1937
                                            size_t n, const char *key,
1938
0
                                            size_t *at) {
1939
0
  size_t i;
1940
1941
0
  for (i = 0; i < n; i++)
1942
0
    if (seq[i]->key != NULL && strcmp(seq[i]->key, key) == 0) {
1943
0
      *at = i;
1944
0
      return seq[i];
1945
0
    }
1946
0
  return NULL;
1947
0
}
1948
1949
/* What the site must report now for the parameters in seq. */
1950
static void st_hp_expect(const struct st_hp_site *site,
1951
                         const struct st_hp_item *const *seq, size_t n,
1952
0
                         char *out, size_t size, int *why) {
1953
0
  size_t at = 0, i;
1954
0
  const struct st_hp_item *hit;
1955
1956
0
  *why = 0;
1957
0
  for (i = 0; i < n; i++)
1958
0
    *why |= seq[i]->why;
1959
0
  if (site->prefix == NULL) {
1960
0
    const struct st_hp_item *t = st_hp_first(seq, n, "timeout", &at);
1961
0
    const struct st_hp_item *m = st_hp_first(seq, n, "max", &at);
1962
0
    const int timeout = t != NULL ? atoi(t->want) : 15;
1963
0
    const int max = m != NULL ? atoi(m->want) : 10;
1964
1965
0
    snprintf(out, size, "keep_alive=%d timeout=%d max=%d",
1966
0
             !(max <= 1 || timeout < 1), timeout, max);
1967
0
    return;
1968
0
  }
1969
0
  hit = st_hp_first(seq, n, site->param, &at);
1970
0
  if (hit != NULL && at != 0)
1971
0
    *why |= HP_LATER;
1972
0
  if (hit != NULL && at + 1 < n)
1973
0
    *why |= HP_TRAIL;
1974
0
  snprintf(out, size, "%s%s", site->prefix, hit != NULL ? hit->want : "");
1975
0
}
1976
1977
/* Hand cases: a NULL want means the input parses as before. */
1978
static const struct {
1979
  const char *site, *in, *want;
1980
} st_hp_cases[] = {
1981
    {"content-type", " text/html", NULL},
1982
    {"content-type", " text/html;charset=utf-8", NULL},
1983
    {"content-type", " text/html; charset='utf-8'", NULL},
1984
    {"content-type", " text/html; charset=\" utf-8 \"", NULL},
1985
    {"content-type", " text/html; charset=utf-8 junk", NULL},
1986
    {"content-type", " text/html; charset=utf-8\"", NULL},
1987
    {"content-type", " text/html; charset", NULL},
1988
    {"content-type", " text/html; charset=\"utf-8", NULL},
1989
    {"content-type", " charset=utf-8",
1990
     "contenttype=charset=utf-8 charset=utf-8"},
1991
    {"content-disposition", " attachment", NULL},
1992
    {"content-disposition", " attachment; filename=a.txt", NULL},
1993
    {"content-disposition", " attachment; filename='a.txt'", NULL},
1994
    {"content-disposition", " attachment; filename= a.txt ", NULL},
1995
    {"content-disposition", " attachment; filename=\"a.txt", NULL},
1996
    {"content-disposition", " attachment; filename=../../etc/passwd", NULL},
1997
    {"content-disposition", " attachment; filename=", NULL},
1998
    {"content-disposition", " filename=a.txt", NULL},
1999
    {"content-disposition", " attachment; filename=\"a.txt\"; size=3",
2000
     "cdispo=a.txt"},
2001
    {"content-disposition", " attachment; filename=\"a;b.txt\"", NULL},
2002
    {"content-disposition", " attachment; filename=\"C:\\dir\\a.txt\"", NULL},
2003
    {"content-disposition", " attachment; filename=\"C:\\\\dir\\\\a.txt\"",
2004
     "cdispo=C:\\dir\\a.txt"},
2005
    {"content-disposition", " attachment; filename = a.txt", "cdispo=a.txt"},
2006
    {"keep-alive", " timeout=5, max=100", NULL},
2007
    {"keep-alive", " timeout=5; max=100", NULL},
2008
    {"keep-alive", " timeout=5,max=100", NULL},
2009
    {"keep-alive", " timeout= 7", NULL},
2010
    {"keep-alive", " timeout=007x", NULL},
2011
    {"keep-alive", " timeout=-1", NULL},
2012
    {"keep-alive", " max", NULL},
2013
    {"keep-alive", " xmax=50", "keep_alive=1 timeout=15 max=10"},
2014
    {"keep-alive", " max=\"50\"", "keep_alive=1 timeout=15 max=50"},
2015
};
2016
2017
/* Each hand case below takes HEAD, then N copies of FILL, as input. */
2018
static const struct {
2019
  const char *site, *head;
2020
  char fill;
2021
  size_t n;
2022
  const char *want;
2023
} st_hp_runs[] = {
2024
    {"content-type", " text/html; charset=\"utf-8 ", 'x', 300,
2025
     "contenttype=text/html charset="},
2026
    {"content-type", " text/html; charset=", 'x', 100, NULL},
2027
    {"content-type", " text/html; charset=", 'x', 125, NULL},
2028
    {"content-type", " text/html; charset=", 'x', 126, NULL},
2029
    {"content-disposition", " attachment; filename=", 'x', 150, NULL},
2030
    {"content-disposition", " attachment; filename=", 'x', 199, NULL},
2031
    {"content-disposition", " attachment; filename=", 'x', 200, NULL},
2032
    {"content-disposition", " attachment; filename=a.txt; filename*=UTF-8''",
2033
     'x', 240, "cdispo=a.txt"},
2034
    {"keep-alive", " timeout=5, max=", '0', 63, NULL},
2035
    {"keep-alive", " timeout=5, max=", '0', 64,
2036
     "keep_alive=1 timeout=5 max=10"},
2037
};
2038
2039
/* Compare one hand case against the legacy copy of its site. */
2040
0
static int st_hp_case(const char *name, const char *in, const char *want) {
2041
0
  const struct st_hp_site *site = st_hp_sites;
2042
0
  char old[512], cur[512];
2043
2044
0
  while (strcmp(site->name, name) != 0)
2045
0
    site++;
2046
0
  site->legacy(in, old, sizeof(old));
2047
0
  site->current(in, cur, sizeof(cur));
2048
0
  if (strcmp(cur, want != NULL ? want : old) == 0)
2049
0
    return 0;
2050
0
  printf("FAIL %s \"%s\": legacy \"%s\" now \"%s\"\n", name, in, old, cur);
2051
0
  return 1;
2052
0
}
2053
2054
/* Build generated input I from N items under SEP, and record them in seq. */
2055
static void st_hp_build(const struct st_hp_site *site, size_t items,
2056
                        const char *sep, size_t n, size_t i,
2057
0
                        const struct st_hp_item **seq, char *in, size_t size) {
2058
0
  size_t k;
2059
2060
0
  snprintf(in, size, "%s", site->head);
2061
0
  for (k = 0; k < n; k++, i /= items) {
2062
0
    seq[k] = &site->items[i % items];
2063
0
    strlncatbuff(in, k != 0 || *site->head != '\0' ? sep : " ", size, size - 1);
2064
0
    strlncatbuff(in, seq[k]->text, size, size - 1);
2065
0
  }
2066
0
}
2067
2068
/* Run hand cases and all 1-3 item runs, and require a reason per change. */
2069
0
static int st_headerparamdiff(httrackp *opt, int argc, char **argv) {
2070
0
  size_t i, s, k, n;
2071
0
  int compared = 0, failures = 0, by[HP_COUNT] = {0};
2072
0
  char in[512], old[512], cur[512], want[512];
2073
2074
0
  (void) opt;
2075
0
  (void) argc;
2076
0
  (void) argv;
2077
0
  for (i = 0; i < sizeof(st_hp_cases) / sizeof(st_hp_cases[0]); i++, compared++)
2078
0
    failures +=
2079
0
        st_hp_case(st_hp_cases[i].site, st_hp_cases[i].in, st_hp_cases[i].want);
2080
0
  for (i = 0; i < sizeof(st_hp_runs) / sizeof(st_hp_runs[0]); i++, compared++) {
2081
0
    const size_t head = strlen(st_hp_runs[i].head);
2082
2083
0
    assertf(head + st_hp_runs[i].n < sizeof(in));
2084
0
    memcpy(in, st_hp_runs[i].head, head);
2085
0
    memset(in + head, st_hp_runs[i].fill, st_hp_runs[i].n);
2086
0
    in[head + st_hp_runs[i].n] = '\0';
2087
0
    failures += st_hp_case(st_hp_runs[i].site, in, st_hp_runs[i].want);
2088
0
  }
2089
0
  for (s = 0; s < sizeof(st_hp_sites) / sizeof(st_hp_sites[0]); s++) {
2090
0
    const struct st_hp_site *site = &st_hp_sites[s];
2091
0
    size_t items, sep;
2092
2093
0
    for (items = 0; site->items[items].text != NULL; items++)
2094
0
      ;
2095
0
    for (sep = 0; site->seps[sep] != NULL; sep++)
2096
0
      for (n = 1; n <= 3; n++) {
2097
0
        size_t total = 1;
2098
2099
0
        for (k = 0; k < n; k++)
2100
0
          total *= items;
2101
0
        for (i = 0; i < total; i++) {
2102
0
          const struct st_hp_item *seq[3];
2103
0
          int why, r;
2104
2105
0
          st_hp_build(site, items, site->seps[sep], n, i, seq, in, sizeof(in));
2106
0
          site->legacy(in, old, sizeof(old));
2107
0
          site->current(in, cur, sizeof(cur));
2108
0
          st_hp_expect(site, seq, n, want, sizeof(want), &why);
2109
0
          compared++;
2110
0
          if (strcmp(cur, want) != 0) {
2111
0
            printf("FAIL %s \"%s\": now \"%s\" want \"%s\"\n", site->name, in,
2112
0
                   cur, want);
2113
0
            failures++;
2114
0
          } else if (strcmp(cur, old) != 0) {
2115
0
            if (why == 0) {
2116
0
              printf("FAIL %s \"%s\": legacy \"%s\" now \"%s\", no reason\n",
2117
0
                     site->name, in, old, cur);
2118
0
              failures++;
2119
0
            }
2120
0
            for (r = 0; r < HP_COUNT; r++)
2121
0
              if ((why & (1 << r)) != 0) {
2122
0
                by[r]++;
2123
0
                break;
2124
0
              }
2125
0
          }
2126
0
        }
2127
0
      }
2128
0
  }
2129
0
  printf("header-param-diff: %d compared, %d failures, changed by", compared,
2130
0
         failures);
2131
0
  for (i = 0; i < HP_COUNT; i++)
2132
0
    printf(" %s=%d", st_hp_reasons[i], by[i]);
2133
  printf("\n");
2134
0
  return failures != 0;
2135
0
}
2136
2137
const struct selftest_entry selftests_header[] = {
2138
    {"pubheaders", "",
2139
     "layout of the installed structs configure's switches decide",
2140
     st_pubheaders},
2141
    {"retry-after", "", "Retry-After header parser self-test", st_retryafter},
2142
    {"header", "<raw-header-line> ...", "response header-line parsing",
2143
     st_header},
2144
    {"statusline", "<status-line>", "status code of an HTTP status line",
2145
     st_statusline},
2146
    {"headerfield", "<headers> <field>",
2147
     "is <field> already present in the custom request-header block",
2148
     st_headerfield},
2149
    {"headerdedup", "<request> <headers> [capacity]",
2150
     "what the custom header block adds to a request that has some (#1340)",
2151
     st_headerdedup},
2152
    {"headerlong", "[header-name:]",
2153
     "over-long header value must not overflow the parse scratch",
2154
     st_headerlong},
2155
    {"location", "<url-length>", "Location gate matches the buffer it protects",
2156
     st_location},
2157
    {"headerline", "<read-size>",
2158
     "a header line that does not fit is reported and skipped whole",
2159
     st_headerline},
2160
    {"binputline", "<read-size> [block]",
2161
     "binput() consumes a clipped line whole (#1294)", st_binputline},
2162
    {"crange", "<raw-content-range-line> ...",
2163
     "Content-Range parse integer safety", st_crange},
2164
    {"int-parse", "parse|scan <text> <min> <max>",
2165
     "bounded decimal parse: value and end offset", st_intparse},
2166
    {"int-parse-diff", "[<site> <input>]",
2167
     "integer header and reply fields parse as before, out-of-range refused",
2168
     st_intparsediff},
2169
    {"header-param", "<value> <seps> <name> <size>",
2170
     "one parameter of a header value, unquoted, refused if too long",
2171
     st_headerparam},
2172
    {"header-param-diff", "",
2173
     "header parameters parse as before, but for the named changes",
2174
     st_headerparamdiff},
2175
    {"xfread-limit", "[oversized-file small-file]",
2176
     "in-memory receive buffer size bound", st_xfread_limit},
2177
    {"useragent", "", "default User-Agent self-test", st_useragent},
2178
    {"codecerrno", "<dir> <doomed-out>",
2179
     "hts_codec_unpack() errno: kept on a failed write, cleared on a bad "
2180
     "stream",
2181
     st_codecerrno},
2182
    {"status", "", "HTTP status code -> reason phrase self-test", st_status},
2183
    {"nobody", "", "which statuses excuse a response that stored no body",
2184
     st_nobody},
2185
    {"acceptencoding", "[dir]",
2186
     "Accept-Encoding advertises gzip+deflate, both decode", st_acceptencoding},
2187
    {"contentcodings", "[dir]",
2188
     "brotli and zstd bodies decode; bombs and unknown codings are refused",
2189
     st_contentcodings},
2190
    {NULL, NULL, NULL, NULL},
2191
};