Coverage Report

Created: 2026-10-14 06:53

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