/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 | | }; |