/src/wget2/libwget/http.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2012 Tim Ruehsen |
3 | | * Copyright (c) 2015-2026 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of libwget. |
6 | | * |
7 | | * Libwget is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation, either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * Libwget is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
19 | | * |
20 | | * |
21 | | * HTTP routines |
22 | | * |
23 | | * Changelog |
24 | | * 25.04.2012 Tim Ruehsen created |
25 | | * 26.10.2012 added Cookie support (RFC 6265) |
26 | | * |
27 | | * Resources: |
28 | | * RFC 2616 |
29 | | * RFC 6265 |
30 | | * |
31 | | */ |
32 | | |
33 | | #include <config.h> |
34 | | |
35 | | #include <stdio.h> |
36 | | #include <stdlib.h> |
37 | | #include <string.h> |
38 | | #include <c-ctype.h> |
39 | | #include <time.h> |
40 | | #include <errno.h> |
41 | | #include <xstrtol.h> |
42 | | #include <sys/socket.h> |
43 | | #include <netinet/in.h> |
44 | | #include <arpa/inet.h> |
45 | | #ifdef WITH_LIBNGHTTP2 |
46 | | #include <nghttp2/nghttp2.h> |
47 | | #endif |
48 | | #ifdef HAVE_LIBPROXY |
49 | | #include "proxy.h" |
50 | | #endif |
51 | | |
52 | | #include <wget.h> |
53 | | #include "private.h" |
54 | | #include "http.h" |
55 | | #include "net.h" |
56 | | |
57 | | static char |
58 | | abort_indicator; |
59 | | |
60 | | static wget_vector |
61 | | *http_proxies, |
62 | | *https_proxies, |
63 | | *no_proxies; |
64 | | |
65 | | // protect access to the above vectors |
66 | | static wget_thread_mutex |
67 | | proxy_mutex, |
68 | | hosts_mutex; |
69 | | static bool |
70 | | initialized; |
71 | | |
72 | | static void http_exit(void) |
73 | 0 | { |
74 | 0 | if (initialized) { |
75 | 0 | wget_thread_mutex_destroy(&proxy_mutex); |
76 | 0 | wget_thread_mutex_destroy(&hosts_mutex); |
77 | 0 | initialized = false; |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | INITIALIZER(http_init) |
82 | 4 | { |
83 | 4 | if (!initialized) { |
84 | 4 | wget_thread_mutex_init(&proxy_mutex); |
85 | 4 | wget_thread_mutex_init(&hosts_mutex); |
86 | 4 | initialized = true; |
87 | 4 | atexit(http_exit); |
88 | 4 | } |
89 | 4 | } |
90 | | |
91 | | |
92 | | /** |
93 | | * HTTP API initialization, allocating/preparing the internal resources. |
94 | | * |
95 | | * On systems with automatic library constructors, this function |
96 | | * doesn't have to be called explicitly. |
97 | | * |
98 | | * This function is not thread-safe. |
99 | | */ |
100 | | void wget_http_init(void) |
101 | 0 | { |
102 | 0 | http_init(); |
103 | 0 | } |
104 | | |
105 | | /** |
106 | | * HTTP API deinitialization, free'ing all internal resources. |
107 | | * |
108 | | * On systems with automatic library destructors, this function |
109 | | * doesn't have to be called explicitly. |
110 | | * |
111 | | * This function is not thread-safe. |
112 | | */ |
113 | | void wget_http_exit(void) |
114 | 0 | { |
115 | 0 | http_exit(); |
116 | 0 | } |
117 | | |
118 | | static wget_server_stats_callback |
119 | | *server_stats_callback; |
120 | | |
121 | | // This is the default function for collecting body data |
122 | | static wget_http_body_callback body_callback; |
123 | | static int body_callback(wget_http_response *resp, void *user_data WGET_GCC_UNUSED, const char *data, size_t length) |
124 | 2.47k | { |
125 | 2.47k | if (!resp->body) |
126 | 1.52k | resp->body = wget_buffer_alloc(102400); |
127 | | |
128 | 2.47k | wget_buffer_memcat(resp->body, data, length); |
129 | | |
130 | 2.47k | return 0; |
131 | 2.47k | } |
132 | | |
133 | | /* |
134 | | * https://tools.ietf.org/html/rfc7230#section-3: Message Format |
135 | | * https://tools.ietf.org/html/rfc7230#section-3.1: Start Line |
136 | | * https://tools.ietf.org/html/rfc7230#section-3.1.1: Request Line |
137 | | * https://tools.ietf.org/html/rfc7230#section-5.3: Request Target |
138 | | * https://tools.ietf.org/html/rfc7230#section-5.3.1: Origin Form |
139 | | * https://tools.ietf.org/html/rfc7230#section-2.7.3: http and https URI Normalization and Comparison |
140 | | * Characters other than those in the "reserved" set are equivalent to their |
141 | | * percent-encoded octets: the normal form is to not encode them (see Sections 2.1 and 2.2 of [RFC3986]). |
142 | | */ |
143 | | wget_http_request *wget_http_create_request(const wget_iri *iri, const char *method) |
144 | 3.72k | { |
145 | 3.72k | wget_http_request *req = wget_calloc(1, sizeof(wget_http_request)); |
146 | 3.72k | if (!req) |
147 | 0 | return NULL; |
148 | | |
149 | 3.72k | wget_buffer_init(&req->esc_resource, req->esc_resource_buf, sizeof(req->esc_resource_buf)); |
150 | 3.72k | wget_buffer_init(&req->esc_host, req->esc_host_buf, sizeof(req->esc_host_buf)); |
151 | | |
152 | 3.72k | req->scheme = iri->scheme; |
153 | 3.72k | wget_strscpy(req->method, method, sizeof(req->method)); |
154 | 3.72k | wget_iri_get_escaped_resource(iri, &req->esc_resource); |
155 | 3.72k | if (wget_ip_is_family(iri->host, WGET_NET_FAMILY_IPV6)) |
156 | 0 | wget_buffer_printf(&req->esc_host, "[%s]", iri->host); |
157 | 3.72k | else |
158 | 3.72k | wget_iri_get_escaped_host(iri, &req->esc_host); |
159 | 3.72k | req->headers = wget_vector_create(8, NULL); |
160 | 3.72k | wget_vector_set_destructor(req->headers, (wget_vector_destructor *) wget_http_free_param); |
161 | | |
162 | 3.72k | wget_http_add_header(req, "Host", req->esc_host.data); |
163 | 3.72k | wget_http_request_set_body_cb(req, body_callback, NULL); |
164 | | |
165 | 3.72k | return req; |
166 | 3.72k | } |
167 | | |
168 | | void wget_http_request_set_header_cb(wget_http_request *req, wget_http_header_callback *callback, void *user_data) |
169 | 0 | { |
170 | 0 | req->header_callback = callback; |
171 | 0 | req->header_user_data = user_data; |
172 | 0 | } |
173 | | |
174 | | void wget_http_request_set_body_cb(wget_http_request *req, wget_http_body_callback *callback, void *user_data) |
175 | 3.72k | { |
176 | 3.72k | req->body_callback = callback; |
177 | 3.72k | req->body_user_data = user_data; |
178 | 3.72k | } |
179 | | |
180 | | void wget_http_request_set_free_user_data_cb(wget_http_request *req, wget_http_free_user_data_callback *callback) |
181 | 0 | { |
182 | 0 | req->user_data_callback = callback; |
183 | 0 | } |
184 | | |
185 | | void wget_http_request_set_int(wget_http_request *req, int key, int value) |
186 | 0 | { |
187 | 0 | switch (key) { |
188 | 0 | case WGET_HTTP_RESPONSE_KEEPHEADER: req->response_keepheader = value != 0; break; |
189 | 0 | case WGET_HTTP_RESPONSE_IGNORELENGTH: req->response_ignorelength = value != 0; break; |
190 | 0 | default: error_printf(_("%s: Unknown key %d (or value must not be an integer)\n"), __func__, key); |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | int wget_http_request_get_int(wget_http_request *req, int key) |
195 | 0 | { |
196 | 0 | switch (key) { |
197 | 0 | case WGET_HTTP_RESPONSE_KEEPHEADER: return req->response_keepheader; |
198 | 0 | case WGET_HTTP_RESPONSE_IGNORELENGTH: return req->response_ignorelength; |
199 | 0 | default: |
200 | 0 | error_printf(_("%s: Unknown key %d (or value must not be an integer)\n"), __func__, key); |
201 | 0 | return -1; |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | void wget_http_request_set_ptr(wget_http_request *req, int key, void *value) |
206 | 0 | { |
207 | 0 | switch (key) { |
208 | 0 | case WGET_HTTP_USER_DATA: req->user_data = value; break; |
209 | 0 | default: error_printf(_("%s: Unknown key %d (or value must not be an integer)\n"), __func__, key); |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | void *wget_http_request_get_ptr(wget_http_request *req, int key) |
214 | 0 | { |
215 | 0 | switch (key) { |
216 | 0 | case WGET_HTTP_USER_DATA: return req->user_data; |
217 | 0 | default: |
218 | 0 | error_printf(_("%s: Unknown key %d (or value must not be an integer)\n"), __func__, key); |
219 | 0 | return NULL; |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | | // See RFC 9110 |
224 | | bool wget_http_method_defines_content(const char *method) |
225 | 3.72k | { |
226 | 3.72k | return (!wget_strcasecmp_ascii(method, "POST") |
227 | 3.72k | || !wget_strcasecmp_ascii(method, "PUT") |
228 | 3.72k | || !wget_strcasecmp_ascii(method, "PATCH")); |
229 | 3.72k | } |
230 | | |
231 | | void wget_http_request_set_body(wget_http_request *req, const char *mimetype, char *body, size_t length) |
232 | 0 | { |
233 | 0 | if (mimetype) |
234 | 0 | wget_http_add_header(req, "Content-Type", mimetype); |
235 | |
|
236 | 0 | req->body = body; |
237 | 0 | req->body_length = length; |
238 | 0 | } |
239 | | |
240 | | static int http_add_header(wget_http_request *req, const char *name, const char *value) |
241 | 3.72k | { |
242 | 3.72k | wget_http_header_param *param = wget_malloc(sizeof(wget_http_header_param)); |
243 | | |
244 | 3.72k | if (!param || !name || !value) |
245 | 0 | goto err; |
246 | | |
247 | 3.72k | param->name = name; |
248 | 3.72k | param->value = value; |
249 | | |
250 | 3.72k | if (wget_vector_add(req->headers, param) >= 0) |
251 | 3.72k | return WGET_E_SUCCESS; |
252 | | |
253 | 0 | xfree(param); |
254 | |
|
255 | 0 | err: |
256 | 0 | xfree(value); |
257 | 0 | xfree(name); |
258 | 0 | return WGET_E_MEMORY; |
259 | 0 | } |
260 | | |
261 | | int wget_http_add_header_vprintf(wget_http_request *req, const char *name, const char *fmt, va_list args) |
262 | 0 | { |
263 | 0 | return http_add_header(req, wget_strdup(name), wget_vaprintf(fmt, args)); |
264 | 0 | } |
265 | | |
266 | | int wget_http_add_header_printf(wget_http_request *req, const char *name, const char *fmt, ...) |
267 | 0 | { |
268 | 0 | va_list args; |
269 | |
|
270 | 0 | va_start(args, fmt); |
271 | 0 | int rc = wget_http_add_header_vprintf(req, name, fmt, args); |
272 | 0 | va_end(args); |
273 | |
|
274 | 0 | return rc; |
275 | 0 | } |
276 | | |
277 | | int wget_http_add_header(wget_http_request *req, const char *name, const char *value) |
278 | 3.72k | { |
279 | 3.72k | return http_add_header(req, wget_strdup(name), wget_strdup(value)); |
280 | 3.72k | } |
281 | | |
282 | | int wget_http_add_header_param(wget_http_request *req, wget_http_header_param *param) |
283 | 0 | { |
284 | 0 | return http_add_header(req, wget_strdup(param->name), wget_strdup(param->value)); |
285 | 0 | } |
286 | | |
287 | | void wget_http_add_credentials(wget_http_request *req, wget_http_challenge *challenge, const char *username, const char *password, int proxied) |
288 | 0 | { |
289 | 0 | if (!challenge) |
290 | 0 | return; |
291 | | |
292 | 0 | if (!username) |
293 | 0 | username = ""; |
294 | |
|
295 | 0 | if (!password) |
296 | 0 | password = ""; |
297 | |
|
298 | 0 | if (!wget_strcasecmp_ascii(challenge->auth_scheme, "basic")) { |
299 | 0 | const char *encoded = wget_base64_encode_printf_alloc("%s:%s", username, password); |
300 | 0 | if (proxied) |
301 | 0 | wget_http_add_header_printf(req, "Proxy-Authorization", "Basic %s", encoded); |
302 | 0 | else |
303 | 0 | wget_http_add_header_printf(req, "Authorization", "Basic %s", encoded); |
304 | 0 | xfree(encoded); |
305 | 0 | } |
306 | 0 | else if (!wget_strcasecmp_ascii(challenge->auth_scheme, "digest")) { |
307 | 0 | const char *realm, *opaque, *nonce, *qop, *algorithm; |
308 | 0 | wget_buffer buf; |
309 | 0 | int hashtype, hashlen; |
310 | |
|
311 | 0 | if (!wget_stringmap_get(challenge->params, "realm", &realm)) |
312 | 0 | realm = NULL; |
313 | 0 | if (!wget_stringmap_get(challenge->params, "opaque", &opaque)) |
314 | 0 | opaque = NULL; |
315 | 0 | if (!wget_stringmap_get(challenge->params, "nonce", &nonce)) |
316 | 0 | nonce = NULL; |
317 | 0 | if (!wget_stringmap_get(challenge->params, "qop", &qop)) |
318 | 0 | qop = NULL; |
319 | 0 | if (!wget_stringmap_get(challenge->params, "algorithm", &algorithm)) |
320 | 0 | algorithm = NULL; |
321 | |
|
322 | 0 | if (qop && (wget_strcasecmp_ascii(qop, "auth") && wget_strcasecmp_ascii(qop, "auth-int"))) { |
323 | 0 | error_printf(_("Unsupported quality of protection '%s'.\n"), qop); |
324 | 0 | return; |
325 | 0 | } |
326 | | |
327 | 0 | if (!wget_strcasecmp_ascii(algorithm, "MD5") || !wget_strcasecmp_ascii(algorithm, "MD5-sess") || algorithm == NULL) { |
328 | | // RFC 2617 |
329 | 0 | hashtype = WGET_DIGTYPE_MD5; |
330 | 0 | } else if (!wget_strcasecmp_ascii(algorithm, "SHA-256") || !wget_strcasecmp_ascii(algorithm, "SHA-256-sess")) { |
331 | | // RFC 7616 |
332 | 0 | hashtype = WGET_DIGTYPE_SHA256; |
333 | 0 | } else { |
334 | 0 | error_printf(_("Unsupported algorithm '%s'.\n"), algorithm); |
335 | 0 | return; |
336 | 0 | } |
337 | | |
338 | 0 | if (!realm || !nonce) |
339 | 0 | return; |
340 | | |
341 | 0 | char a1buf[32 * 2 + 1], a2buf[32 * 2 + 1]; |
342 | 0 | char response_digest[32 * 2 + 1], cnonce[16] = ""; |
343 | |
|
344 | 0 | hashlen = wget_hash_get_len(hashtype); |
345 | 0 | size_t buflen = hashlen * 2 + 1; |
346 | 0 | if (buflen > sizeof(a1buf)) |
347 | 0 | return; |
348 | | |
349 | | // A1BUF = H(user ":" realm ":" password) |
350 | 0 | wget_hash_printf_hex(hashtype, a1buf, buflen, "%s:%s:%s", username, realm, password); |
351 | |
|
352 | 0 | if (!wget_strcasecmp_ascii(algorithm, "MD5-sess") || !wget_strcasecmp_ascii(algorithm, "SHA-256-sess")) { |
353 | | // A1BUF = H( H(user ":" realm ":" password) ":" nonce ":" cnonce ) |
354 | 0 | wget_snprintf(cnonce, sizeof(cnonce), "%08x", (unsigned) wget_random()); // create random hex string |
355 | 0 | wget_hash_printf_hex(hashtype, a1buf, buflen, "%s:%s:%s", a1buf, nonce, cnonce); |
356 | 0 | } |
357 | | |
358 | | // A2BUF = H(method ":" path) |
359 | 0 | wget_hash_printf_hex(hashtype, a2buf, buflen, "%s:/%s", req->method, req->esc_resource.data); |
360 | |
|
361 | 0 | if (!qop) { |
362 | | // RFC 2069 Digest Access Authentication |
363 | | |
364 | | // RESPONSE_DIGEST = H(A1BUF ":" nonce ":" A2BUF) |
365 | 0 | wget_hash_printf_hex(hashtype, response_digest, buflen, "%s:%s:%s", a1buf, nonce, a2buf); |
366 | 0 | } else { // if (!wget_strcasecmp_ascii(qop, "auth") || !wget_strcasecmp_ascii(qop, "auth-int")) { |
367 | | // RFC 2617 Digest Access Authentication |
368 | 0 | if (!*cnonce) |
369 | 0 | wget_snprintf(cnonce, sizeof(cnonce), "%08x", (unsigned) wget_random()); // create random hex string |
370 | | |
371 | | // RESPONSE_DIGEST = H(A1BUF ":" nonce ":" nc ":" cnonce ":" qop ": " A2BUF) |
372 | 0 | wget_hash_printf_hex(hashtype, response_digest, buflen, |
373 | 0 | "%s:%s:00000001:%s:%s:%s", a1buf, nonce, /* nc, */ cnonce, qop, a2buf); |
374 | 0 | } |
375 | |
|
376 | 0 | wget_buffer_init(&buf, NULL, 256); |
377 | |
|
378 | 0 | wget_buffer_printf(&buf, |
379 | 0 | "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"/%s\", response=\"%s\"", |
380 | 0 | username, realm, nonce, req->esc_resource.data, response_digest); |
381 | |
|
382 | 0 | if (!wget_strcasecmp_ascii(qop,"auth")) |
383 | 0 | wget_buffer_printf_append(&buf, ", qop=auth, nc=00000001, cnonce=\"%s\"", cnonce); |
384 | |
|
385 | 0 | if (opaque) |
386 | 0 | wget_buffer_printf_append(&buf, ", opaque=\"%s\"", opaque); |
387 | |
|
388 | 0 | if (algorithm) |
389 | 0 | wget_buffer_printf_append(&buf, ", algorithm=%s", algorithm); |
390 | |
|
391 | 0 | if (proxied) |
392 | 0 | wget_http_add_header(req, "Proxy-Authorization", buf.data); |
393 | 0 | else |
394 | 0 | wget_http_add_header(req, "Authorization", buf.data); |
395 | |
|
396 | 0 | wget_buffer_deinit(&buf); |
397 | 0 | } |
398 | 0 | } |
399 | | |
400 | | /* |
401 | | static struct config { |
402 | | int |
403 | | read_timeout; |
404 | | unsigned int |
405 | | dns_caching : 1; |
406 | | } config = { |
407 | | .read_timeout = -1, |
408 | | .dns_caching = 1 |
409 | | }; |
410 | | |
411 | | void http_set_config_int(int key, int value) |
412 | | { |
413 | | switch (key) { |
414 | | case HTTP_READ_TIMEOUT: config.read_timeout = value; |
415 | | case HTTP_DNS: config.read_timeout = value; |
416 | | default: error_printf(_("Unknown config key %d (or value must not be an integer)\n"), key); |
417 | | } |
418 | | } |
419 | | */ |
420 | | |
421 | | int http_decompress_error_handler_cb(wget_decompressor *dc, int err WGET_GCC_UNUSED) |
422 | 0 | { |
423 | 0 | wget_http_response *resp = (wget_http_response *) wget_decompress_get_context(dc); |
424 | |
|
425 | 0 | if (resp && resp->req) |
426 | 0 | error_printf(_("Decompress failed [host: %s - resource: %s]\n"), |
427 | 0 | resp->req->esc_host.data, resp->req->esc_resource.data); |
428 | |
|
429 | 0 | return 0; |
430 | 0 | } |
431 | | |
432 | | int http_get_body_cb(void *userdata, const char *data, size_t length) |
433 | 2.47k | { |
434 | 2.47k | wget_http_response *resp = (wget_http_response *) userdata; |
435 | | |
436 | 2.47k | return resp->req->body_callback(resp, resp->req->body_user_data, data, length); |
437 | 2.47k | } |
438 | | |
439 | | void http_fix_broken_server_encoding(wget_http_response *resp) |
440 | 1.86k | { |
441 | | // a workaround for broken server configurations |
442 | | // see https://mail-archives.apache.org/mod_mbox/httpd-dev/200207.mbox/<3D2D4E76.4010502@talex.com.pl> |
443 | 1.86k | if (resp->content_encoding == wget_content_encoding_gzip) { |
444 | 0 | const char *ext; |
445 | 0 | if (!wget_strcasecmp_ascii(resp->content_type, "application/x-gzip") |
446 | 0 | || !wget_strcasecmp_ascii(resp->content_type, "application/gzip") |
447 | 0 | || !wget_strcasecmp_ascii(resp->content_type, "application/gunzip") |
448 | 0 | || ((ext = strrchr(resp->req->esc_resource.data, '.')) |
449 | 0 | && (!wget_strcasecmp_ascii(ext, ".gz") || !wget_strcasecmp_ascii(ext, ".tgz")))) |
450 | 0 | { |
451 | 0 | debug_printf("Broken server configuration gzip workaround triggered\n"); |
452 | 0 | resp->content_encoding = wget_content_encoding_identity; |
453 | 0 | } |
454 | 0 | } |
455 | 1.86k | } |
456 | | |
457 | | #ifdef WITH_LIBNGHTTP2 |
458 | | #endif |
459 | | |
460 | | static int establish_proxy_connect(wget_tcp *tcp, const char *host, uint16_t port, const char *proxy_auth_header) |
461 | 0 | { |
462 | 0 | char sbuf[1024]; |
463 | 0 | wget_buffer buf; |
464 | |
|
465 | 0 | wget_buffer_init(&buf, sbuf, sizeof(sbuf)); |
466 | | |
467 | | // The use of Proxy-Connection has been discouraged in RFC 7230 A.1.2. |
468 | | // wget_buffer_sprintf(buf, "CONNECT %s:%hu HTTP/1.1\r\nHost: %s\r\nProxy-Connection: keep-alive\r\n\r\n", |
469 | | |
470 | | // Build CONNECT request with optional Proxy-Authorization header |
471 | 0 | const char *host_left = wget_ip_is_family(host, WGET_NET_FAMILY_IPV6) ? "[" : ""; |
472 | 0 | const char *host_right = *host_left == '[' ? "]" : ""; |
473 | |
|
474 | 0 | wget_buffer_printf(&buf, "CONNECT %s%s%s:%hu HTTP/1.1\r\nHost: %s%s%s:%hu\r\n", |
475 | 0 | host_left, host, host_right, port, host_left, host, host_right, port); |
476 | |
|
477 | 0 | if (proxy_auth_header) { |
478 | 0 | debug_printf("# Sending CONNECT request (with Proxy-Authorization)\n%sProxy-Authorization: Basic *\r\n\r\n", buf.data); |
479 | 0 | wget_buffer_printf_append(&buf, "Proxy-Authorization: Basic %s\r\n\r\n", proxy_auth_header); |
480 | 0 | } else { |
481 | 0 | wget_buffer_strcat(&buf, "\r\n"); |
482 | 0 | debug_printf("%s", buf.data); |
483 | 0 | } |
484 | |
|
485 | 0 | if (wget_tcp_write(tcp, buf.data, buf.length) != (ssize_t) buf.length) { |
486 | 0 | wget_buffer_deinit(&buf); |
487 | 0 | return WGET_E_CONNECT; |
488 | 0 | } |
489 | | |
490 | 0 | wget_buffer_deinit(&buf); |
491 | |
|
492 | 0 | ssize_t nbytes; |
493 | 0 | if ((nbytes = wget_tcp_read(tcp, sbuf, sizeof(sbuf) - 1)) < 0) { |
494 | 0 | return WGET_E_CONNECT; |
495 | 0 | } |
496 | 0 | sbuf[nbytes] = 0; |
497 | | |
498 | | // strip trailing whitespace |
499 | 0 | while (nbytes > 0 && c_isspace(sbuf[--nbytes])) |
500 | 0 | sbuf[nbytes] = 0; |
501 | | |
502 | | // Additionally accepting HTTP/1.0 solves at least some compatibility issues. |
503 | | // See https://gitlab.com/gnuwget/wget2/-/issues/666#note_2002037243 |
504 | 0 | if (wget_strncasecmp_ascii(sbuf, "HTTP/1.1 200", 12) && wget_strncasecmp_ascii(sbuf, "HTTP/1.0 200", 12)) { |
505 | 0 | error_printf(_("Proxy connection failed with: %s\n"), sbuf); |
506 | 0 | return WGET_E_CONNECT; |
507 | 0 | } |
508 | | |
509 | 0 | debug_printf("Proxy connection established: %s\n", sbuf); |
510 | |
|
511 | 0 | return WGET_E_SUCCESS; |
512 | 0 | } |
513 | | |
514 | | int wget_http_open(wget_http_connection **_conn, const wget_iri *iri) |
515 | 3.72k | { |
516 | 3.72k | static int next_http_proxy = -1; |
517 | 3.72k | static int next_https_proxy = -1; |
518 | | |
519 | 3.72k | wget_http_connection |
520 | 3.72k | *conn; |
521 | 3.72k | const char |
522 | 3.72k | *host; |
523 | 3.72k | uint16_t |
524 | 3.72k | port; |
525 | 3.72k | int |
526 | 3.72k | rc, |
527 | 3.72k | ssl = iri->scheme == WGET_IRI_SCHEME_HTTPS; |
528 | 3.72k | bool |
529 | 3.72k | need_connect = false; |
530 | | |
531 | 3.72k | if (!_conn) |
532 | 0 | return WGET_E_INVALID; |
533 | | |
534 | 3.72k | conn = wget_calloc(1, sizeof(wget_http_connection)); // convenience assignment |
535 | 3.72k | if (!conn) |
536 | 0 | return WGET_E_MEMORY; |
537 | | |
538 | 3.72k | *_conn = conn; |
539 | 3.72k | host = iri->host; |
540 | 3.72k | port = iri->port; |
541 | 3.72k | conn->tcp = wget_tcp_init(); |
542 | | |
543 | | #ifdef HAVE_LIBPROXY |
544 | | { |
545 | | pxProxyFactory *pf = px_proxy_factory_new(); |
546 | | if (pf) { |
547 | | char **proxies = px_proxy_factory_get_proxies(pf, iri->uri); |
548 | | |
549 | | if (proxies) { |
550 | | if (proxies[0]) { |
551 | | if (strcmp (proxies[0], "direct://") != 0) { |
552 | | wget_iri *proxy_iri = wget_iri_parse(proxies[0], "utf-8"); |
553 | | host = strdup(proxy_iri->host); |
554 | | port = proxy_iri->port; |
555 | | |
556 | | if (proxy_iri->scheme == WGET_IRI_SCHEME_HTTP) { |
557 | | ssl = false; |
558 | | conn->proxied = 1; |
559 | | } else { |
560 | | ssl = true; |
561 | | need_connect = true; |
562 | | |
563 | | // Compute Proxy-Authorization header for CONNECT tunnel if credentials are present |
564 | | if (proxy_iri->userinfo && *proxy_iri->userinfo) { |
565 | | conn->proxy_auth_header = (char *) wget_base64_encode_printf_alloc("%s:%s", proxy_iri->userinfo, proxy_iri->password ? proxy_iri->password : ""); |
566 | | debug_printf("HTTPS Proxy (libproxy): using credentials (userinfo=%s, password=*)\n", proxy_iri->userinfo); |
567 | | } |
568 | | } |
569 | | wget_iri_free(&proxy_iri); |
570 | | } |
571 | | } |
572 | | |
573 | | px_proxy_factory_free_proxies(proxies); |
574 | | } |
575 | | |
576 | | px_proxy_factory_free (pf); |
577 | | } |
578 | | } |
579 | | #else |
580 | 3.72k | if (!wget_http_match_no_proxy(no_proxies, iri->host)) { |
581 | 3.72k | if (!ssl && http_proxies) { |
582 | 0 | wget_thread_mutex_lock(proxy_mutex); |
583 | 0 | wget_iri *proxy = wget_vector_get(http_proxies, (++next_http_proxy) % wget_vector_size(http_proxies)); |
584 | 0 | wget_thread_mutex_unlock(proxy_mutex); |
585 | |
|
586 | 0 | if (!proxy) { |
587 | | // this practically can't happen |
588 | 0 | xfree(conn); |
589 | 0 | *_conn = NULL; |
590 | 0 | return WGET_E_UNKNOWN; |
591 | 0 | } |
592 | | |
593 | 0 | host = proxy->host; |
594 | 0 | port = proxy->port; |
595 | 0 | ssl = proxy->scheme == WGET_IRI_SCHEME_HTTPS; |
596 | 0 | conn->proxied = 1; |
597 | 3.72k | } else if (ssl && https_proxies) { |
598 | 0 | wget_thread_mutex_lock(proxy_mutex); |
599 | 0 | wget_iri *proxy = wget_vector_get(https_proxies, (++next_https_proxy) % wget_vector_size(https_proxies)); |
600 | 0 | wget_thread_mutex_unlock(proxy_mutex); |
601 | |
|
602 | 0 | if (!proxy) { |
603 | | // this practically can't happen |
604 | 0 | xfree(conn); |
605 | 0 | *_conn = NULL; |
606 | 0 | return WGET_E_UNKNOWN; |
607 | 0 | } |
608 | | |
609 | 0 | host = proxy->host; |
610 | 0 | port = proxy->port; |
611 | 0 | ssl = proxy->scheme == WGET_IRI_SCHEME_HTTPS; |
612 | | // conn->proxied = 1; |
613 | |
|
614 | 0 | need_connect = true; |
615 | | |
616 | | // Compute Proxy-Authorization header for CONNECT tunnel if credentials are present |
617 | 0 | if (proxy->userinfo && *proxy->userinfo) { |
618 | 0 | conn->proxy_auth_header = (char *) wget_base64_encode_printf_alloc("%s:%s", proxy->userinfo, proxy->password ? proxy->password : ""); |
619 | 0 | debug_printf("HTTPS Proxy: using credentials (userinfo=%s, password=*)\n", proxy->userinfo); |
620 | 0 | } |
621 | 0 | } |
622 | 3.72k | } |
623 | 3.72k | #endif |
624 | | |
625 | 3.72k | if (ssl) { |
626 | 0 | wget_tcp_set_ssl(conn->tcp, 1); // switch SSL on |
627 | 0 | wget_tcp_set_ssl_hostname(conn->tcp, host); // enable host name checking |
628 | 0 | } |
629 | | |
630 | 3.72k | if ((rc = wget_tcp_connect(conn->tcp, host, port)) != WGET_E_SUCCESS) { |
631 | 0 | if (server_stats_callback && (rc == WGET_E_CERTIFICATE)) |
632 | 0 | server_stats_callback(conn, NULL); |
633 | |
|
634 | 0 | wget_http_close(_conn); |
635 | 0 | return rc; |
636 | 0 | } |
637 | | |
638 | 3.72k | if (need_connect) { |
639 | 0 | if ((rc = establish_proxy_connect(conn->tcp, iri->host, iri->port, conn->proxy_auth_header)) != WGET_E_SUCCESS) { |
640 | 0 | wget_http_close(_conn); |
641 | 0 | return rc; |
642 | 0 | } |
643 | | |
644 | 0 | if (iri->scheme == WGET_IRI_SCHEME_HTTPS) { |
645 | 0 | wget_tcp_set_ssl(conn->tcp, 1); // switch SSL on |
646 | 0 | wget_tcp_set_ssl_hostname(conn->tcp, iri->host); // enable host name checking |
647 | |
|
648 | 0 | if ((rc = wget_tcp_tls_start(conn->tcp)) != WGET_E_SUCCESS) { |
649 | 0 | if (server_stats_callback && rc == WGET_E_CERTIFICATE) |
650 | 0 | server_stats_callback(conn, NULL); |
651 | 0 | wget_http_close(_conn); |
652 | 0 | return rc; |
653 | 0 | } |
654 | 0 | } |
655 | 0 | } |
656 | | |
657 | 3.72k | conn->esc_host = iri->host ? wget_strdup(iri->host) : NULL; |
658 | 3.72k | conn->port = iri->port; |
659 | 3.72k | conn->scheme = iri->scheme; |
660 | 3.72k | conn->buf = wget_buffer_alloc(102400); // reusable buffer, large enough for most requests and responses |
661 | | #ifdef WITH_LIBNGHTTP2 |
662 | | if ((conn->protocol = (char) wget_tcp_get_protocol(conn->tcp)) == WGET_PROTOCOL_HTTP_2_0) { |
663 | | if ((rc = wget_http2_open(conn)) < 0) { |
664 | | wget_http_close(_conn); |
665 | | return rc; |
666 | | } |
667 | | } else { |
668 | | conn->pending_requests = wget_vector_create(16, NULL); |
669 | | } |
670 | | |
671 | | #else |
672 | 3.72k | conn->pending_requests = wget_vector_create(16, NULL); |
673 | 3.72k | #endif |
674 | | |
675 | 3.72k | return rc; |
676 | 3.72k | } |
677 | | |
678 | | void wget_http_close(wget_http_connection **conn) |
679 | 3.72k | { |
680 | 3.72k | if (*conn) { |
681 | 3.72k | debug_printf("closing connection\n"); |
682 | | #ifdef WITH_LIBNGHTTP2 |
683 | | wget_http2_close(conn); |
684 | | #endif |
685 | 3.72k | wget_tcp_deinit(&(*conn)->tcp); |
686 | | // if (!wget_tcp_get_dns_caching()) |
687 | | // freeaddrinfo((*conn)->addrinfo); |
688 | 3.72k | xfree((*conn)->esc_host); |
689 | 3.72k | xfree((*conn)->proxy_auth_header); |
690 | | // xfree((*conn)->scheme); |
691 | 3.72k | wget_buffer_free(&(*conn)->buf); |
692 | 3.72k | wget_vector_clear_nofree((*conn)->pending_requests); |
693 | 3.72k | wget_vector_free(&(*conn)->pending_requests); |
694 | 3.72k | xfree(*conn); |
695 | 3.72k | } |
696 | 3.72k | } |
697 | | |
698 | | // Takes ownership of req on success, means the caller's req will be set to NULL to avoid |
699 | | // double-free and free-after-use failures. |
700 | | int wget_http_send_request(wget_http_connection *conn, wget_http_request **req) |
701 | 3.72k | { |
702 | 3.72k | ssize_t nbytes; |
703 | | |
704 | | #ifdef WITH_LIBNGHTTP2 |
705 | | if (wget_tcp_get_protocol(conn->tcp) == WGET_PROTOCOL_HTTP_2_0) { |
706 | | return wget_http2_send_request(conn, req); |
707 | | } |
708 | | #endif |
709 | | |
710 | 3.72k | if ((nbytes = wget_http_request_to_buffer(*req, conn->buf, conn->proxied, conn->port)) < 0) { |
711 | 0 | error_printf(_("Failed to create request buffer\n")); |
712 | 0 | return WGET_E_UNKNOWN; |
713 | 0 | } |
714 | | |
715 | 3.72k | (*req)->request_start = wget_get_timemillis(); |
716 | | |
717 | 3.72k | if (wget_tcp_write(conn->tcp, conn->buf->data, nbytes) != nbytes) { |
718 | | // An error will be written by the wget_tcp_write function. |
719 | | // error_printf(_("Failed to send %zd bytes (%d)\n"), nbytes, errno); |
720 | 0 | return WGET_E_UNKNOWN; |
721 | 0 | } |
722 | | |
723 | 3.72k | if ((*req)->debug_skip_body) |
724 | 0 | debug_printf("# sent %zd bytes:\n%.*s<body skipped>", nbytes, (int) (conn->buf->length - (*req)->body_length), conn->buf->data); |
725 | 3.72k | else |
726 | 3.72k | debug_printf("# sent %zd bytes:\n%.*s", nbytes, (int) conn->buf->length, conn->buf->data); |
727 | | |
728 | 3.72k | wget_vector_add(conn->pending_requests, *req); |
729 | 3.72k | *req = NULL; |
730 | | |
731 | 3.72k | return WGET_E_SUCCESS; |
732 | 3.72k | } |
733 | | |
734 | | ssize_t wget_http_request_to_buffer(wget_http_request *req, wget_buffer *buf, int proxied, int port) |
735 | 3.72k | { |
736 | 3.72k | bool add_content_length = (req->body && req->body_length) |
737 | 3.72k | || wget_http_method_defines_content(req->method); |
738 | | |
739 | | // wget_buffer_sprintf(buf, "%s /%s HTTP/1.1\r\nHost: %s", req->method, req->esc_resource.data ? req->esc_resource.data : "",); |
740 | | |
741 | 3.72k | wget_buffer_strcpy(buf, req->method); |
742 | 3.72k | wget_buffer_memcat(buf, " ", 1); |
743 | 3.72k | if (proxied) { |
744 | 0 | wget_buffer_strcat(buf, wget_iri_scheme_get_name(req->scheme)); |
745 | 0 | wget_buffer_memcat(buf, "://", 3); |
746 | 0 | wget_buffer_bufcat(buf, &req->esc_host); |
747 | 0 | wget_buffer_printf_append(buf, ":%d", port); |
748 | 0 | } |
749 | 3.72k | wget_buffer_memcat(buf, "/", 1); |
750 | 3.72k | wget_buffer_bufcat(buf, &req->esc_resource); |
751 | 3.72k | wget_buffer_memcat(buf, " HTTP/1.1\r\n", 11); |
752 | | |
753 | 7.45k | for (int it = 0; it < wget_vector_size(req->headers); it++) { |
754 | 3.72k | wget_http_header_param *param = wget_vector_get(req->headers, it); |
755 | 3.72k | if (!param) |
756 | 0 | continue; |
757 | | |
758 | 3.72k | wget_buffer_strcat(buf, param->name); |
759 | 3.72k | wget_buffer_memcat(buf, ": ", 2); |
760 | 3.72k | wget_buffer_strcat(buf, param->value); |
761 | | |
762 | 3.72k | if (buf->data[buf->length - 1] != '\n') { |
763 | 3.72k | wget_buffer_memcat(buf, "\r\n", 2); |
764 | 3.72k | } |
765 | | |
766 | 3.72k | if (!wget_strcasecmp_ascii(param->name, "Content-Length")) |
767 | 0 | add_content_length = false; // User supplied Content-Length header, keep it unchecked |
768 | 3.72k | } |
769 | | |
770 | | /* The use of Proxy-Connection has been discouraged in RFC 7230 A.1.2. |
771 | | if (proxied) |
772 | | wget_buffer_strcat(buf, "Proxy-Connection: keep-alive\r\n"); |
773 | | */ |
774 | | |
775 | 3.72k | if (add_content_length) |
776 | 0 | wget_buffer_printf_append(buf, "Content-Length: %zu\r\n", req->body_length); |
777 | | |
778 | 3.72k | wget_buffer_memcat(buf, "\r\n", 2); // end-of-header |
779 | | |
780 | 3.72k | if (req->body && req->body_length) |
781 | 0 | wget_buffer_memcat(buf, req->body, req->body_length); |
782 | | |
783 | 3.72k | return buf->length; |
784 | 3.72k | } |
785 | | |
786 | | static char *get_page(wget_http_request *req) |
787 | 846 | { |
788 | 846 | return wget_aprintf("%s://%s/%s", |
789 | 846 | wget_iri_scheme_get_name(req->scheme), req->esc_host.data, req->esc_resource.data); |
790 | 846 | } |
791 | | |
792 | | wget_http_response *wget_http_get_response_cb(wget_http_connection *conn) |
793 | 3.72k | { |
794 | 3.72k | size_t bufsize, body_len = 0, body_size = 0; |
795 | 3.72k | ssize_t nbytes, nread = 0; |
796 | 3.72k | char *buf, *p = NULL; |
797 | 3.72k | wget_http_response *resp = NULL; |
798 | | |
799 | | #ifdef WITH_LIBNGHTTP2 |
800 | | if (conn->protocol == WGET_PROTOCOL_HTTP_2_0) { |
801 | | return wget_http2_get_response_cb(conn, server_stats_callback); |
802 | | } |
803 | | #endif |
804 | | |
805 | 3.72k | wget_decompressor *dc = NULL; |
806 | 3.72k | wget_http_request *req = wget_vector_get(conn->pending_requests, 0); // TODO: should use double linked lists here |
807 | | |
808 | 3.72k | debug_printf("### req %p pending requests = %d\n", (void *) req, wget_vector_size(conn->pending_requests)); |
809 | 3.72k | if (!req) |
810 | 0 | goto cleanup; |
811 | | |
812 | 3.72k | wget_vector_remove_nofree(conn->pending_requests, 0); |
813 | | |
814 | | // reuse generic connection buffer |
815 | 3.72k | buf = conn->buf->data; |
816 | 3.72k | bufsize = conn->buf->size; |
817 | | |
818 | 165k | while ((nbytes = wget_tcp_read(conn->tcp, buf + nread, bufsize - nread)) > 0) { |
819 | 163k | req->first_response_start = wget_get_timemillis(); |
820 | | // debug_printf("nbytes %zd nread %zd %zu\n", nbytes, nread, bufsize); |
821 | 163k | nread += nbytes; |
822 | 163k | buf[nread] = 0; // 0-terminate to allow string functions |
823 | 163k | skip_1xx: |
824 | 163k | if (nread < 4) continue; |
825 | | |
826 | 158k | if (nread - nbytes <= 4) |
827 | 5.58k | p = buf; |
828 | 152k | else |
829 | 152k | p = buf + nread - nbytes - 3; |
830 | | |
831 | 158k | if ((p = strstr(p, "\r\n\r\n"))) { |
832 | | // found end-of-header |
833 | 1.86k | *p = 0; |
834 | | |
835 | 1.86k | debug_printf("# got header %zd bytes:\n%s\n\n", p - buf, buf); |
836 | | |
837 | 1.86k | wget_buffer *header = NULL; |
838 | 1.86k | if (req->response_keepheader) { |
839 | 0 | header = wget_buffer_alloc(p - buf + 4); |
840 | 0 | wget_buffer_memcpy(header, buf, p - buf); |
841 | 0 | wget_buffer_memcat(header, "\r\n", 2); |
842 | 0 | } |
843 | | |
844 | 1.86k | if (!(resp = wget_http_parse_response_header(buf))) { |
845 | 0 | debug_printf("failed to parse response header"); |
846 | 0 | goto cleanup; // something is wrong with the header |
847 | 0 | } |
848 | | |
849 | 1.86k | if (H_10X(resp->code)) { |
850 | 0 | wget_http_free_response(&resp); |
851 | 0 | p += 4; |
852 | | // calculate number of bytes so far read |
853 | 0 | nbytes = nread -= (p - buf); |
854 | | // move remaining data to begin of buf |
855 | 0 | memmove(buf, p, nread + 1); |
856 | 0 | goto skip_1xx; // ignore intermediate response, no body expected |
857 | 0 | } |
858 | | |
859 | 1.86k | if (req->response_keepheader) { |
860 | 0 | resp->header = header; |
861 | 1.86k | } else { |
862 | 1.86k | xfree(header); |
863 | 1.86k | } |
864 | | |
865 | 1.86k | resp->req = req; |
866 | | |
867 | 1.86k | if (server_stats_callback) |
868 | 0 | server_stats_callback(conn, resp); |
869 | | |
870 | 1.86k | if (req->header_callback) { |
871 | 0 | req->header_callback(resp, req->header_user_data); |
872 | 0 | } |
873 | | |
874 | 1.86k | if (!wget_strcasecmp_ascii(req->method, "HEAD")) { |
875 | 0 | debug_printf("HEAD response with a body"); |
876 | 0 | goto cleanup; // a HEAD response won't have a body |
877 | 0 | } |
878 | | |
879 | 1.86k | http_fix_broken_server_encoding(resp); |
880 | | |
881 | 1.86k | p += 4; // skip \r\n\r\n to point to body |
882 | 1.86k | break; |
883 | 1.86k | } |
884 | | |
885 | 156k | if ((size_t)nread + 1024 > bufsize) { |
886 | 0 | if (wget_buffer_ensure_capacity(conn->buf, bufsize + 1024) != WGET_E_SUCCESS) { |
887 | 0 | error_printf(_("Failed to allocate %zu bytes\n"), bufsize + 1024); |
888 | 0 | goto cleanup; |
889 | 0 | } |
890 | 0 | buf = conn->buf->data; |
891 | 0 | bufsize = conn->buf->size; |
892 | 0 | } |
893 | 156k | } |
894 | 3.72k | if (!nread) { |
895 | 0 | debug_printf("incomplete response data"); |
896 | 0 | goto cleanup; |
897 | 0 | } |
898 | 3.72k | if (!p) { |
899 | 1.86k | debug_printf("missing end-of-header"); |
900 | 1.86k | goto cleanup; |
901 | 1.86k | } |
902 | | |
903 | 1.86k | if (resp && resp->code == HTTP_STATUS_RANGE_NOT_SATISFIABLE) { |
904 | | /* |
905 | | RFC7233: |
906 | | 4.4. 416 Range Not Satisfiable |
907 | | |
908 | | The 416 (Range Not Satisfiable) status code indicates that none of |
909 | | the ranges in the request's Range header field (Section 3.1) overlap |
910 | | the current extent of the selected resource or that the set of ranges |
911 | | requested has been rejected due to invalid ranges or an excessive |
912 | | request of small or overlapping ranges. |
913 | | */ |
914 | 0 | debug_printf("range request is not satisfiable"); |
915 | 0 | goto cleanup; |
916 | 0 | } |
917 | 1.86k | if (!resp |
918 | 1.86k | || resp->code == HTTP_STATUS_NO_CONTENT |
919 | 1.86k | || resp->code == HTTP_STATUS_NOT_MODIFIED |
920 | 1.86k | || (resp->transfer_encoding == wget_transfer_encoding_identity && resp->content_length == 0 && resp->content_length_valid)) { |
921 | | // - body not included, see RFC 2616 4.3 |
922 | | // - body empty, see RFC 2616 4.4 |
923 | 0 | debug_printf("response with no body"); |
924 | 0 | goto cleanup; |
925 | 0 | } |
926 | | |
927 | 1.86k | dc = wget_decompress_open(resp->content_encoding, http_get_body_cb, resp); |
928 | 1.86k | wget_decompress_set_error_handler(dc, http_decompress_error_handler_cb); |
929 | | |
930 | | // calculate number of body bytes so far read |
931 | 1.86k | body_len = nread - (p - buf); |
932 | | // move already read body data to buf |
933 | 1.86k | memmove(buf, p, body_len); |
934 | 1.86k | buf[body_len] = 0; |
935 | 1.86k | resp->cur_downloaded = body_len; |
936 | | |
937 | 1.86k | if (resp->transfer_encoding == wget_transfer_encoding_chunked) { |
938 | 621 | size_t chunk_size = 0; |
939 | 621 | char *end; |
940 | | |
941 | 621 | debug_printf("method 1 %zu %zu:\n", body_len, body_size); |
942 | | // RFC 2616 3.6.1 |
943 | | // Chunked-Body = *chunk last-chunk trailer CRLF |
944 | | // chunk = chunk-size [ chunk-extension ] CRLF chunk-data CRLF |
945 | | // chunk-size = 1*HEX |
946 | | // last-chunk = 1*("0") [ chunk-extension ] CRLF |
947 | | // chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) |
948 | | // chunk-ext-name = token |
949 | | // chunk-ext-val = token | quoted-string |
950 | | // chunk-data = chunk-size(OCTET) |
951 | | // trailer = *(entity-header CRLF) |
952 | | // entity-header = extension-header = message-header |
953 | | // message-header = field-name ":" [ field-value ] |
954 | | // field-name = token |
955 | | // field-value = *( field-content | LWS ) |
956 | | // field-content = <the OCTETs making up the field-value |
957 | | // and consisting of either *TEXT or combinations |
958 | | // of token, separators, and quoted-string> |
959 | | |
960 | | /* |
961 | | length := 0 |
962 | | read chunk-size, chunk-extension (if any) and CRLF |
963 | | while (chunk-size > 0) { |
964 | | read chunk-data and CRLF |
965 | | append chunk-data to entity-body |
966 | | length := length + chunk-size |
967 | | read chunk-size and CRLF |
968 | | } |
969 | | read entity-header |
970 | | while (entity-header not empty) { |
971 | | append entity-header to existing header fields |
972 | | read entity-header |
973 | | } |
974 | | Content-Length := length |
975 | | Remove "chunked" from Transfer-Encoding |
976 | | */ |
977 | | |
978 | | // read each chunk, stripping the chunk info |
979 | 621 | p = buf; |
980 | 1.63k | for (;;) { |
981 | | // read: chunk-size [ chunk-extension ] CRLF |
982 | 1.63k | while ((!(end = strchr(p, '\r')) || end[1] != '\n')) { |
983 | 43 | if (http_connection_is_aborted(conn)) |
984 | 0 | goto cleanup; |
985 | | |
986 | 43 | if (body_len + 1024 > bufsize) { |
987 | 0 | if (wget_buffer_ensure_capacity(conn->buf, bufsize + 1024) != WGET_E_SUCCESS) { |
988 | 0 | char *page = get_page(req); |
989 | 0 | error_printf(_("Failed to allocate %zu bytes (%s)\n"), bufsize + 1024, page); |
990 | 0 | xfree(page); |
991 | 0 | goto cleanup; |
992 | 0 | } |
993 | 0 | p = conn->buf->data + (p - buf); |
994 | 0 | buf = conn->buf->data; |
995 | 0 | bufsize = conn->buf->size; |
996 | 0 | } |
997 | | |
998 | 43 | if ((nbytes = wget_tcp_read(conn->tcp, buf + body_len, bufsize - body_len)) <= 0) { |
999 | 43 | debug_printf("failed to read data (%zd)", nbytes); |
1000 | 43 | goto cleanup; |
1001 | 43 | } |
1002 | | |
1003 | 0 | body_len += nbytes; |
1004 | 0 | buf[body_len] = 0; |
1005 | | // debug_printf("a nbytes %zd body_len %zu\n", nbytes, body_len); |
1006 | 0 | } |
1007 | 1.58k | end += 2; |
1008 | | |
1009 | | // now p points to chunk-size (hex) |
1010 | 1.58k | errno = 0; |
1011 | 1.58k | chunk_size = (size_t) strtoll(p, NULL, 16); |
1012 | 1.58k | if (errno) { |
1013 | 148 | char *page = get_page(req); |
1014 | 148 | error_printf(_("Failed to convert chunk size '%.31s' (%s)\n"), p, page); |
1015 | 148 | xfree(page); |
1016 | 148 | goto cleanup; |
1017 | 148 | } |
1018 | | |
1019 | | // debug_printf("chunk size is %zu\n", chunk_size); |
1020 | 1.44k | if (chunk_size == 0) { |
1021 | | // now read 'trailer CRLF' which is '*(entity-header CRLF) CRLF' |
1022 | 112 | if (*end == '\r' && end[1] == '\n') { // shortcut for the most likely case (empty trailer) |
1023 | 6 | debug_printf("missing CRLF chunk trailer"); |
1024 | 6 | goto cleanup; |
1025 | 6 | } |
1026 | | |
1027 | 106 | debug_printf("reading trailer\n"); |
1028 | 106 | while (!strstr(end, "\r\n\r\n")) { |
1029 | 101 | if (body_len > 3) { |
1030 | | // just need to keep the last 3 bytes to avoid buffer resizing |
1031 | 67 | memmove(buf, buf + body_len - 3, 4); // plus 0 terminator, just in case |
1032 | 67 | body_len = 3; |
1033 | 67 | } |
1034 | | |
1035 | 101 | if (http_connection_is_aborted(conn)) { |
1036 | 0 | debug_printf("connection was aborted"); |
1037 | 0 | goto cleanup; |
1038 | 0 | } |
1039 | | |
1040 | 101 | if ((nbytes = wget_tcp_read(conn->tcp, buf + body_len, bufsize - body_len)) <= 0) { |
1041 | 101 | debug_printf("failed to read data (%zd)", nbytes); |
1042 | 101 | goto cleanup; |
1043 | 101 | } |
1044 | | |
1045 | 0 | body_len += nbytes; |
1046 | 0 | buf[body_len] = 0; |
1047 | 0 | end = buf; |
1048 | | // debug_printf("a nbytes %zd\n", nbytes); |
1049 | 0 | } |
1050 | 5 | debug_printf("end of trailer\n"); |
1051 | 5 | goto cleanup; |
1052 | 106 | } |
1053 | | |
1054 | | // check for pointer overflow |
1055 | 1.32k | if (chunk_size > SIZE_MAX/2 - 2 || end >= end + chunk_size + 2) { |
1056 | | // if (end > end + chunk_size || end >= end + chunk_size + 2) { |
1057 | 96 | char *page = get_page(req); |
1058 | 96 | error_printf(_("Chunk size overflow: %zX (%s)\n"), chunk_size, page); |
1059 | 96 | xfree(page); |
1060 | 96 | goto cleanup; |
1061 | 96 | } |
1062 | | |
1063 | 1.23k | p = end + chunk_size + 2; |
1064 | 1.23k | if (p <= buf + body_len) { |
1065 | | // debug_printf("write full chunk, %zu bytes\n", chunk_size); |
1066 | 1.01k | resp->cur_downloaded += chunk_size; |
1067 | 1.01k | if (wget_decompress(dc, end, chunk_size)) { |
1068 | 0 | debug_printf("failed to decompress chunk data"); |
1069 | 0 | goto cleanup; |
1070 | 0 | } |
1071 | 1.01k | continue; |
1072 | 1.01k | } |
1073 | | |
1074 | | // resp->cur_downloaded += (buf + body_len) - end; |
1075 | | // wget_decompress(dc, end, (buf + body_len) - end); |
1076 | | |
1077 | 222 | if ((uintptr_t)((buf + body_len) - end) > chunk_size) { |
1078 | 1 | resp->cur_downloaded += chunk_size; |
1079 | 1 | if (wget_decompress(dc, end, chunk_size)) { |
1080 | 0 | debug_printf("failed to decompress chunk data"); |
1081 | 0 | goto cleanup; |
1082 | 0 | } |
1083 | 221 | } else { |
1084 | 221 | resp->cur_downloaded += (buf + body_len) - end; |
1085 | 221 | if (wget_decompress(dc, end, (buf + body_len) - end)) { |
1086 | 0 | debug_printf("failed to decompress chunk data"); |
1087 | 0 | goto cleanup; |
1088 | 0 | } |
1089 | 221 | } |
1090 | | |
1091 | 222 | chunk_size = (((uintptr_t) p) - ((uintptr_t) (buf + body_len))); // in fact needed bytes to have chunk_size+2 in buf |
1092 | | |
1093 | 222 | debug_printf("need at least %zu more bytes\n", chunk_size); |
1094 | | |
1095 | 222 | while (chunk_size > 0) { |
1096 | 222 | if (http_connection_is_aborted(conn)) { |
1097 | 0 | debug_printf("connection was aborted"); |
1098 | 0 | goto cleanup; |
1099 | 0 | } |
1100 | | |
1101 | 222 | if ((nbytes = wget_tcp_read(conn->tcp, buf, bufsize)) <= 0) { |
1102 | 222 | debug_printf("failed to read data (%zd)", nbytes); |
1103 | 222 | goto cleanup; |
1104 | 222 | } |
1105 | | // debug_printf("a nbytes=%zd chunk_size=%zu\n", nread, chunk_size); |
1106 | | |
1107 | 0 | if (chunk_size <= (size_t)nbytes) { |
1108 | 0 | if (chunk_size == 1 || !strncmp(buf + chunk_size - 2, "\r\n", 2)) { |
1109 | 0 | debug_printf("chunk completed\n"); |
1110 | | // p=end+chunk_size+2; |
1111 | 0 | } else { |
1112 | 0 | char *page = get_page(req); |
1113 | 0 | error_printf(_("Expected end-of-chunk not found (%s)\n"), page); |
1114 | 0 | xfree(page); |
1115 | 0 | goto cleanup; |
1116 | 0 | } |
1117 | 0 | if (chunk_size > 2) { |
1118 | 0 | resp->cur_downloaded += chunk_size - 2; |
1119 | 0 | if (wget_decompress(dc, buf, chunk_size - 2)) { |
1120 | 0 | debug_printf("failed to decompress chunk data"); |
1121 | 0 | goto cleanup; |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | body_len = nbytes - chunk_size; |
1125 | 0 | if (body_len) |
1126 | 0 | memmove(buf, buf + chunk_size, body_len); |
1127 | 0 | buf[body_len] = 0; |
1128 | 0 | p = buf; |
1129 | 0 | break; |
1130 | 0 | } else { |
1131 | 0 | chunk_size -= nbytes; |
1132 | 0 | if (chunk_size >= 2) { |
1133 | 0 | resp->cur_downloaded += nbytes; |
1134 | 0 | if (wget_decompress(dc, buf, nbytes)) { |
1135 | 0 | debug_printf("failed to decompress chunk data"); |
1136 | 0 | goto cleanup; |
1137 | 0 | } |
1138 | 0 | } else { |
1139 | | // special case: we got a partial end-of-chunk |
1140 | 0 | resp->cur_downloaded += nbytes - 1; |
1141 | 0 | if (wget_decompress(dc, buf, nbytes - 1)) { |
1142 | 0 | debug_printf("failed to decompress chunk data"); |
1143 | 0 | goto cleanup; |
1144 | 0 | } |
1145 | 0 | } |
1146 | 0 | } |
1147 | 0 | } |
1148 | 222 | } |
1149 | 1.24k | } else if (resp->content_length_valid && !resp->req->response_ignorelength) { |
1150 | | // read content_length bytes |
1151 | 621 | debug_printf("method 2\n"); |
1152 | | |
1153 | 621 | if (body_len) { |
1154 | 621 | if (wget_decompress(dc, buf, body_len)) { |
1155 | 0 | debug_printf("failed to decompress data"); |
1156 | 0 | goto cleanup; |
1157 | 0 | } |
1158 | 621 | } |
1159 | | |
1160 | 621 | while (body_len < resp->content_length) { |
1161 | 191 | if (http_connection_is_aborted(conn)) |
1162 | 0 | break; |
1163 | | |
1164 | 191 | if (((nbytes = wget_tcp_read(conn->tcp, buf, bufsize)) <= 0)) |
1165 | 191 | break; |
1166 | | |
1167 | 0 | body_len += nbytes; |
1168 | | // debug_printf("nbytes %zd total %zu/%zu\n", nbytes, body_len, resp->content_length); |
1169 | 0 | resp->cur_downloaded += nbytes; |
1170 | 0 | if (wget_decompress(dc, buf, nbytes)) { |
1171 | 0 | debug_printf("failed to decompress data"); |
1172 | 0 | goto cleanup; |
1173 | 0 | } |
1174 | 0 | } |
1175 | 621 | if (nbytes < 0) { |
1176 | 0 | char *page = get_page(req); |
1177 | 0 | error_printf(_("Failed to read %zd bytes (%d) (%s)\n"), nbytes, errno, page); |
1178 | 0 | xfree(page); |
1179 | 0 | } |
1180 | 621 | if (body_len < resp->content_length) { |
1181 | 191 | resp->length_inconsistent = true; |
1182 | 191 | char *page = get_page(req); |
1183 | 191 | error_printf(_("Just got %zu of %zu bytes (%s)\n"), body_len, resp->content_length, page); |
1184 | 191 | xfree(page); |
1185 | 430 | } else if (body_len > resp->content_length) { |
1186 | 411 | resp->length_inconsistent = true; |
1187 | 411 | char *page = get_page(req); |
1188 | 411 | error_printf(_("Body too large: %zu instead of %zu bytes (%s)\n"), body_len, resp->content_length, page); |
1189 | 411 | xfree(page); |
1190 | 411 | } |
1191 | 621 | resp->content_length = body_len; |
1192 | 621 | } else { |
1193 | | // read as long as we can |
1194 | 621 | debug_printf("method 3\n"); |
1195 | | |
1196 | 621 | if (body_len) { |
1197 | 621 | if (wget_decompress(dc, buf, body_len)) { |
1198 | 0 | debug_printf("failed to decompress data"); |
1199 | 0 | goto cleanup; |
1200 | 0 | } |
1201 | 621 | } |
1202 | | |
1203 | 621 | while (!http_connection_is_aborted(conn) && (nbytes = wget_tcp_read(conn->tcp, buf, bufsize)) > 0) { |
1204 | 0 | body_len += nbytes; |
1205 | | // debug_printf("nbytes %zd total %zu\n", nbytes, body_len); |
1206 | 0 | resp->cur_downloaded += nbytes; |
1207 | 0 | if (wget_decompress(dc, buf, nbytes)) { |
1208 | 0 | debug_printf("failed to decompress data"); |
1209 | 0 | goto cleanup; |
1210 | 0 | } |
1211 | 0 | } |
1212 | 621 | resp->content_length = body_len; |
1213 | 621 | } |
1214 | | |
1215 | 3.72k | cleanup: |
1216 | | |
1217 | 3.72k | if (resp) |
1218 | 1.86k | resp->response_end = wget_get_timemillis(); |
1219 | 1.86k | else |
1220 | 1.86k | wget_http_free_request(&req); |
1221 | | |
1222 | 3.72k | wget_decompress_close(dc); |
1223 | | |
1224 | 3.72k | return resp; |
1225 | 1.86k | } |
1226 | | |
1227 | | // get response, resp->body points to body in memory |
1228 | | |
1229 | | wget_http_response *wget_http_get_response(wget_http_connection *conn) |
1230 | 3.72k | { |
1231 | 3.72k | wget_http_response *resp; |
1232 | | |
1233 | 3.72k | resp = wget_http_get_response_cb(conn); |
1234 | | |
1235 | 3.72k | if (resp) { |
1236 | 1.86k | if (!wget_strcasecmp_ascii(resp->req->method, "GET")) |
1237 | 1.86k | if (resp->body) |
1238 | 1.52k | resp->content_length = resp->body->length; |
1239 | 1.86k | } |
1240 | | |
1241 | 3.72k | return resp; |
1242 | 3.72k | } |
1243 | | |
1244 | | static void iri_free(void *iri) |
1245 | 21.3k | { |
1246 | 21.3k | if (iri) |
1247 | 21.3k | wget_iri_free((wget_iri **) &iri); |
1248 | 21.3k | } |
1249 | | |
1250 | | static wget_vector *parse_proxies(const char *proxy, const char *encoding) |
1251 | 20.6k | { |
1252 | 20.6k | if (!proxy) |
1253 | 19.5k | return NULL; |
1254 | | |
1255 | 1.09k | wget_vector *proxies = NULL; |
1256 | 1.09k | const char *s, *p; |
1257 | | |
1258 | 25.3k | for (s = p = proxy; *p; s = p + 1) { |
1259 | 24.2k | if ((p = strchrnul(s, ',')) != s && p - s < 256) { |
1260 | 23.2k | wget_iri *iri; |
1261 | 23.2k | char host[256]; |
1262 | | |
1263 | 23.2k | wget_strmemcpy(host, sizeof(host), s, p - s); |
1264 | | |
1265 | 23.2k | iri = wget_iri_parse (host, encoding); |
1266 | 23.2k | if (iri) { |
1267 | 21.3k | if (!proxies) { |
1268 | 986 | proxies = wget_vector_create(8, NULL); |
1269 | 986 | wget_vector_set_destructor(proxies, iri_free); |
1270 | 986 | } |
1271 | 21.3k | wget_vector_add(proxies, iri); |
1272 | 21.3k | } |
1273 | 23.2k | } |
1274 | 24.2k | } |
1275 | | |
1276 | 1.09k | return proxies; |
1277 | 20.6k | } |
1278 | | |
1279 | | static wget_vector *parse_no_proxies(const char *no_proxy, const char *encoding) |
1280 | 9.89k | { |
1281 | 9.89k | if (!no_proxy) |
1282 | 9.79k | return NULL; |
1283 | | |
1284 | 102 | wget_vector *proxies; |
1285 | 102 | const char *s, *p; |
1286 | | |
1287 | 102 | proxies = wget_vector_create(8, NULL); |
1288 | | |
1289 | 1.48k | for (s = p = no_proxy; *p; s = p + 1) { |
1290 | 1.37k | while (c_isspace(*s) && s < p) s++; |
1291 | | |
1292 | 1.37k | if ((p = strchrnul(s, ',')) != s && p - s < 256) { |
1293 | 938 | char *host, *hostp; |
1294 | | |
1295 | 1.40k | while (c_isspace(*s) && s < p) s++; |
1296 | | |
1297 | 938 | if (s >= p || !(host = wget_strmemdup(s, p - s))) |
1298 | 253 | continue; |
1299 | | |
1300 | | // May be a hostname, domainname (optional with leading dot or wildcard), IP address. |
1301 | | // We do not support network address (CIDR) for now. |
1302 | | |
1303 | 685 | wget_strtolower(host); |
1304 | 685 | if (wget_str_needs_encoding(host)) { |
1305 | 462 | if ((hostp = wget_str_to_utf8(host, encoding))) { |
1306 | 194 | xfree(host); |
1307 | 194 | host = hostp; |
1308 | 194 | } |
1309 | 462 | } |
1310 | 685 | if ((hostp = (char *) wget_str_to_ascii(host)) != host) { |
1311 | 228 | xfree(host); |
1312 | 228 | host = hostp; |
1313 | 228 | } |
1314 | | |
1315 | 685 | wget_vector_add(proxies, host); |
1316 | 685 | } |
1317 | 1.37k | } |
1318 | | |
1319 | 102 | return proxies; |
1320 | 9.89k | } |
1321 | | |
1322 | | int wget_http_set_http_proxy(const char *proxy, const char *encoding) |
1323 | 10.8k | { |
1324 | 10.8k | if (http_proxies) |
1325 | 978 | wget_vector_free(&http_proxies); |
1326 | | |
1327 | 10.8k | http_proxies = parse_proxies(proxy, encoding); |
1328 | | |
1329 | 10.8k | return wget_vector_size(http_proxies); |
1330 | 10.8k | } |
1331 | | |
1332 | | int wget_http_set_https_proxy(const char *proxy, const char *encoding) |
1333 | 9.80k | { |
1334 | 9.80k | if (https_proxies) |
1335 | 8 | wget_vector_free(&https_proxies); |
1336 | | |
1337 | 9.80k | https_proxies = parse_proxies(proxy, encoding); |
1338 | | |
1339 | 9.80k | return wget_vector_size(https_proxies); |
1340 | 9.80k | } |
1341 | | |
1342 | | int wget_http_set_no_proxy(const char *no_proxy, const char *encoding) |
1343 | 9.89k | { |
1344 | 9.89k | if (no_proxies) |
1345 | 102 | wget_vector_free(&no_proxies); |
1346 | | |
1347 | 9.89k | no_proxies = parse_no_proxies(no_proxy, encoding); |
1348 | 9.89k | if (!no_proxies) |
1349 | 9.79k | return -1; |
1350 | | |
1351 | 102 | return 0; |
1352 | 9.89k | } |
1353 | | |
1354 | | const wget_vector *wget_http_get_no_proxy(void) |
1355 | 0 | { |
1356 | 0 | return no_proxies; |
1357 | 0 | } |
1358 | | |
1359 | | static bool cidr_v4_match(const char *cidr, struct in_addr *addr) |
1360 | 0 | { |
1361 | 0 | const char *slash_pos = strchr(cidr, '/'); |
1362 | 0 | if (slash_pos == NULL) { |
1363 | 0 | return false; // invalid CIDR range |
1364 | 0 | } |
1365 | 0 | long prefix_len; |
1366 | 0 | if (xstrtol(slash_pos + 1, NULL, 10, &prefix_len, NULL) != LONGINT_OK) |
1367 | 0 | return false; // invalid prefix length |
1368 | 0 | if (prefix_len < 0 || prefix_len > 32) { |
1369 | 0 | return false; // invalid prefix length |
1370 | 0 | } |
1371 | 0 | struct in_addr network_addr; |
1372 | 0 | const char *prefix = wget_strmemdup(cidr, slash_pos - cidr); |
1373 | 0 | if (inet_pton(AF_INET, prefix, &network_addr) != 1) { |
1374 | 0 | xfree(prefix); |
1375 | 0 | return false; // invalid network address |
1376 | 0 | } |
1377 | 0 | xfree(prefix); |
1378 | |
|
1379 | 0 | uint32_t mask = (uint32_t) ~(0xFFFFFFFFLLU >> prefix_len); |
1380 | 0 | uint32_t network = ntohl(network_addr.s_addr) & mask; |
1381 | 0 | uint32_t test_addr = ntohl(addr->s_addr); |
1382 | 0 | return (test_addr & mask) == network; |
1383 | 0 | } |
1384 | | |
1385 | | #include <netinet/in.h> |
1386 | | |
1387 | | #ifdef HAVE_IPV6 |
1388 | | static bool cidr_v6_match(const char *cidr, struct in6_addr *addr) |
1389 | 0 | { |
1390 | 0 | const char *slash_pos = strchr(cidr, '/'); |
1391 | 0 | if (slash_pos == NULL) { |
1392 | 0 | return false; // invalid CIDR range |
1393 | 0 | } |
1394 | 0 | long prefix_len; |
1395 | 0 | if (xstrtol(slash_pos + 1, NULL, 10, &prefix_len, NULL) != LONGINT_OK) |
1396 | 0 | return false; // invalid prefix length |
1397 | 0 | if (prefix_len < 0 || prefix_len > 128) { |
1398 | 0 | return false; // invalid prefix length |
1399 | 0 | } |
1400 | 0 | struct in6_addr network_addr; |
1401 | 0 | const char *prefix = wget_strmemdup(cidr, slash_pos - cidr); |
1402 | 0 | if (inet_pton(AF_INET6, prefix, &network_addr) != 1) { |
1403 | 0 | xfree(prefix); |
1404 | 0 | return false; // invalid network address |
1405 | 0 | } |
1406 | 0 | xfree(prefix); |
1407 | |
|
1408 | 0 | int bytes = prefix_len / 8; |
1409 | 0 | if (bytes && memcmp(network_addr.s6_addr, addr->s6_addr, bytes)) |
1410 | 0 | return false; |
1411 | | |
1412 | 0 | int bits = prefix_len & 7; |
1413 | 0 | if (!bits) |
1414 | 0 | return true; |
1415 | | |
1416 | 0 | uint8_t mask = (uint8_t) ~(0xFF >> bits); |
1417 | 0 | return ((network_addr.s6_addr[bytes] ^ addr->s6_addr[bytes]) & mask) == 0; |
1418 | 0 | } |
1419 | | #endif |
1420 | | |
1421 | | int wget_http_match_no_proxy(const wget_vector *no_proxies_vec, const char *host) |
1422 | 3.72k | { |
1423 | 3.72k | if (wget_vector_size(no_proxies_vec) < 1 || !host) |
1424 | 3.72k | return 0; |
1425 | | |
1426 | 0 | struct in_addr addr; |
1427 | 0 | #ifdef HAVE_IPV6 |
1428 | 0 | struct in6_addr addr6; |
1429 | 0 | #endif |
1430 | 0 | bool ipv4 = false, ipv6 = false; |
1431 | |
|
1432 | 0 | if (inet_pton(AF_INET, host, &addr) == 1) { |
1433 | 0 | ipv4 = true; |
1434 | 0 | #ifdef HAVE_IPV6 |
1435 | 0 | } else if (inet_pton(AF_INET6, host, &addr6) == 1) { |
1436 | 0 | ipv6 = true; |
1437 | 0 | #endif |
1438 | 0 | } |
1439 | | |
1440 | | // https://www.gnu.org/software/emacs/manual/html_node/url/Proxies.html |
1441 | 0 | for (int it = 0; it < wget_vector_size(no_proxies_vec); it++) { |
1442 | 0 | const char *no_proxy = wget_vector_get(no_proxies_vec, it); |
1443 | |
|
1444 | 0 | if (!no_proxy) |
1445 | 0 | continue; |
1446 | | |
1447 | 0 | if (!strcmp(no_proxy, host)) |
1448 | 0 | return 1; // exact match |
1449 | | |
1450 | 0 | if (ipv4) { |
1451 | 0 | if (cidr_v4_match(no_proxy, &addr)) { |
1452 | 0 | return 1; |
1453 | 0 | } |
1454 | 0 | #ifdef HAVE_IPV6 |
1455 | 0 | } else if (ipv6) { |
1456 | 0 | if (cidr_v6_match(no_proxy, &addr6)) { |
1457 | 0 | return 1; |
1458 | 0 | } |
1459 | 0 | #endif |
1460 | 0 | } |
1461 | | |
1462 | | // check for subdomain match |
1463 | 0 | if (*no_proxy == '.' && wget_match_tail(host, no_proxy)) |
1464 | 0 | return 1; |
1465 | 0 | } |
1466 | | |
1467 | 0 | return 0; |
1468 | 0 | } |
1469 | | |
1470 | | void wget_http_abort_connection(wget_http_connection *conn) |
1471 | 0 | { |
1472 | 0 | if (conn) |
1473 | 0 | conn->abort_indicator = 1; // stop single connection |
1474 | 0 | else |
1475 | 0 | abort_indicator = 1; // stop all connections |
1476 | 0 | } |
1477 | | |
1478 | | int http_connection_is_aborted(wget_http_connection *conn) |
1479 | 1.17k | { |
1480 | 1.17k | return conn->abort_indicator || abort_indicator; |
1481 | 1.17k | } |
1482 | | |
1483 | | /** |
1484 | | * \param[in] conn Pointer to a `wget_http_connection` instance. |
1485 | | * \return Returns true if the remote side no longer accepts requests. |
1486 | | * |
1487 | | * With HTTP/2 a server can indicate that no more requests are accepted. |
1488 | | * If that happens, pending responses are still delivered and should be retrieved. |
1489 | | * After the last response has been received, the connection should be closed. |
1490 | | */ |
1491 | | bool wget_http_connection_receive_only(wget_http_connection *conn) |
1492 | 0 | { |
1493 | 0 | return conn->goaway; |
1494 | 0 | } |
1495 | | |
1496 | | /** |
1497 | | * \param[in] fn A `wget_server_stats_callback` callback function to receive server statistics data |
1498 | | * \param[in] ctx Context data given to \p fn |
1499 | | * |
1500 | | * Set callback function to be called when server statistics are available |
1501 | | */ |
1502 | | void wget_server_set_stats_callback(wget_server_stats_callback *fn) |
1503 | 12 | { |
1504 | 12 | server_stats_callback = fn; |
1505 | 12 | } |