Coverage Report

Created: 2026-09-14 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htsback.c
Line
Count
Source
1
/* ------------------------------------------------------------ */
2
/*
3
HTTrack Website Copier, Offline Browser for Windows and Unix
4
Copyright (C) 1998 Xavier Roche and other contributors
5
6
SPDX-License-Identifier: GPL-3.0-or-later
7
8
This program is free software: you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation, either version 3 of the License, or
11
(at your option) any later version.
12
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
GNU General Public License for more details.
17
18
You should have received a copy of the GNU General Public License
19
along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21
Ethical use: we kindly ask that you NOT use this software to harvest email
22
addresses or to collect any other private information about people. Doing so
23
would dishonor our work and waste the many hours we have spent on it.
24
25
Please visit our Website: http://www.httrack.com
26
*/
27
28
/* ------------------------------------------------------------ */
29
/* File: httrack.c subroutines:                                 */
30
/*       backing system (multiple socket download)              */
31
/* Author: Xavier Roche                                         */
32
/* ------------------------------------------------------------ */
33
34
/* Internal engine bytecode */
35
#define HTS_INTERNAL_BYTECODE
36
37
/* specific definitions */
38
#include "htsnet.h"
39
#include "htscore.h"
40
#include "htsio.h"
41
#include "htswarc.h"
42
#include "htschanges.h"
43
#include "htsthread.h"
44
#include <limits.h>
45
#include <stdint.h>
46
#include <time.h>
47
/* END specific definitions */
48
49
#include "htsback.h"
50
51
#include "htsftp.h"
52
#include "htscodec.h"
53
#include "htsproxy.h"
54
55
#ifdef _WIN32
56
#ifndef __cplusplus
57
// DOS
58
#include <process.h>            /* _beginthread, _endthread */
59
#endif
60
#include <io.h> /* _chsize_s */
61
#define HTS_FTRUNCATE(fp, sz) _chsize_s(_fileno(fp), (sz))
62
#else
63
0
#define HTS_FTRUNCATE(fp, sz) ftruncate(fileno(fp), (sz))
64
#endif
65
66
/* Subdirectory holding a mirrored file's temporaries, beside it. url_savename()
67
   maps '~' to '_', so no URL can ever be mirrored inside it (#774, #842). */
68
0
#define HTS_TMPDIR "~hts-tmp"
69
70
/* Slot operations */
71
static hts_boolean back_tmpname(char *dest, size_t size, const char *save,
72
                                const char *ext);
73
74
hts_boolean back_spoolname(httrackp *opt, const char *save, char *dest,
75
0
                           size_t size) {
76
  /* -p0 keeps no save name to derive from, so it counts instead. No separator:
77
     path_html_utf8 brings its own, and is "" with no -O, where an added one
78
     would make this absolute and spool into the filesystem root. */
79
0
  if (opt->getmode == 0) {
80
0
    if (!slprintfbuff(dest, size, "%s" HTS_TMPDIR "/tmpfile%d.tmp",
81
0
                      StringBuff(opt->path_html_utf8),
82
0
                      opt->state.tmpnameid++)) {
83
0
      dest[0] = '\0';
84
0
      return HTS_FALSE;
85
0
    }
86
0
    return HTS_TRUE;
87
0
  }
88
0
  return back_tmpname(dest, size, save, "tmp");
89
0
}
90
static int slot_can_be_cached_on_disk(const lien_back * back);
91
static int slot_can_be_cleaned(const lien_back * back);
92
static int slot_can_be_finalized(httrackp * opt, const lien_back * back);
93
94
/* Which hard quota, if any, is currently aborting the mirror. */
95
typedef enum {
96
  HTS_MIRROR_LIMIT_NONE = 0,
97
  HTS_MIRROR_LIMIT_SIZE,
98
  HTS_MIRROR_LIMIT_TIME,
99
} hts_mirror_limit;
100
101
static hts_mirror_limit back_mirror_limit(httrackp *opt);
102
static hts_boolean back_mirror_capped(const httrackp *opt);
103
static hts_boolean back_is_live(const int status);
104
105
/* NULL when the slot table cannot be allocated, which httpmirror() already
106
   answers by aborting the mirror with a message. Failing here rather than
107
   aborting the process: back_max grows with -cN, so the size is the user's. */
108
0
struct_back *back_new(httrackp *opt, int back_max) {
109
0
  int i;
110
0
  struct_back *sback = calloct(1, sizeof(struct_back));
111
112
0
  if (sback == NULL)
113
0
    return NULL;
114
0
  sback->count = back_max;
115
0
  sback->lnk = (lien_back *) calloct((back_max + 1), sizeof(lien_back));
116
0
  sback->connect_fallback = (hts_connect_fallback *) calloct(
117
0
      (back_max + 1), sizeof(hts_connect_fallback));
118
0
  sback->ready = coucal_new(0);
119
0
  if (sback->lnk == NULL || sback->connect_fallback == NULL ||
120
0
      sback->ready == NULL) {
121
0
    back_free(&sback);
122
0
    return NULL;
123
0
  }
124
0
  hts_set_hash_handler(sback->ready, opt);
125
0
  coucal_set_name(sback->ready, "back_new");
126
0
  sback->ready_size_bytes = 0;
127
0
  coucal_value_is_malloc(sback->ready, 1);
128
  // init
129
0
  for(i = 0; i < sback->count; i++) {
130
0
    sback->lnk[i].r.location = sback->lnk[i].location_buffer;
131
0
    sback->lnk[i].status = STATUS_FREE;
132
0
    sback->lnk[i].r.soc = INVALID_SOCKET;
133
0
    sback->connect_fallback[i].addr_count = -1; // not yet probed
134
0
  }
135
0
  return sback;
136
0
}
137
138
0
void back_free(struct_back ** sback) {
139
0
  if (sback != NULL && *sback != NULL) {
140
0
    if ((*sback)->lnk != NULL) {
141
0
      freet((*sback)->lnk);
142
0
      (*sback)->lnk = NULL;
143
0
    }
144
0
    freet((*sback)->connect_fallback);
145
0
    if ((*sback)->ready != NULL) {
146
0
      coucal_delete(&(*sback)->ready);
147
0
      (*sback)->ready_size_bytes = 0;
148
0
    }
149
0
    freet(*sback);
150
0
    *sback = NULL;
151
0
  }
152
0
}
153
154
/* Per-candidate connect deadline cap (seconds): a connecting slot with another
155
   address to try waits at most this long before falling back, instead of the
156
   full (default 120s) slot timeout. Caps the dead-IPv6 stall while staying well
157
   above a normal handshake. The last candidate still gets the full timeout. */
158
0
#define HTS_CONNECT_FALLBACK_TIMEOUT 10
159
160
0
void back_read_ftp_result(FILE *fp, htsblk *r) {
161
0
  size_t j = 0;
162
163
0
  if (fscanf(fp, "%d ", &r->statuscode) != 1)
164
0
    r->statuscode = STATUSCODE_INVALID;
165
  // an external helper writes this file: stop at capacity, not at EOF
166
0
  while (j + 1 < sizeof(r->msg)) {
167
0
    const int c = fgetc(fp);
168
169
0
    if (c == EOF)
170
0
      break;
171
0
    r->msg[j++] = (char) c;
172
0
  }
173
0
  r->msg[j] = '\0';
174
0
}
175
176
int back_connect_fallback_due(int addr_index, int addr_count, int elapsed,
177
0
                              int timeout) {
178
0
  int deadline;
179
180
0
  if (addr_index + 1 >= addr_count) // last (or only) candidate: no fallback
181
0
    return 0;
182
0
  if (timeout <= 0) // no timeout management: never force it
183
0
    return 0;
184
0
  deadline = (timeout < HTS_CONNECT_FALLBACK_TIMEOUT)
185
0
                 ? timeout
186
0
                 : HTS_CONNECT_FALLBACK_TIMEOUT;
187
0
  return elapsed >= deadline;
188
0
}
189
190
/* Retry a stuck/failed connecting slot against its next resolved address.
191
   Closes the current socket and starts a non-blocking connect to the next
192
   candidate, leaving the slot in STATUS_CONNECTING. Returns 1 if a new connect
193
   was started, 0 if no fallback address remains (caller fails the slot). */
194
0
static int back_connect_next(httrackp *opt, struct_back *sback, int i) {
195
0
  hts_connect_fallback *const cf = &sback->connect_fallback[i];
196
0
  lien_back *const back = sback->lnk;
197
0
  const int next = cf->addr_index + 1;
198
0
  T_SOC soc;
199
200
0
  if (next >= cf->addr_count)
201
0
    return 0;
202
203
0
  if (back[i].r.soc != INVALID_SOCKET) {
204
0
    deletehttp(&back[i].r);
205
0
    back[i].r.soc = INVALID_SOCKET;
206
0
  }
207
0
  soc = newhttp_addr(opt, back[i].url_adr, &back[i].r, -1, 0, next, NULL);
208
0
  if (soc == INVALID_SOCKET)
209
0
    return 0;
210
211
0
  back[i].r.soc = soc;
212
0
  cf->addr_index = next;
213
0
  cf->connect_start = time_local();
214
0
  if (back[i].timeout > 0)
215
0
    back[i].timeout_refresh = cf->connect_start;
216
0
  back[i].status = STATUS_CONNECTING;
217
0
  hts_log_print(opt, LOG_DEBUG,
218
0
                "connect failed, trying next address (%d/%d) for %s", next + 1,
219
0
                cf->addr_count, back[i].url_adr);
220
0
  return 1;
221
0
}
222
223
0
void back_delete_all(httrackp * opt, cache_back * cache, struct_back * sback) {
224
0
  if (sback != NULL) {
225
0
    int i;
226
227
    /* An FTP worker writes through its slot until it returns, so nothing here
228
       may wipe or free one under it. */
229
0
    ftp_stop_workers();
230
    /* A slot still writing when the mirror ends leaves its partial on disk, so
231
       hts-cache/ref must outlive the run (#1595). back_abort_slot() catches the
232
       ones a sweep took, and the link loop can end a capped mirror before that
233
       sweep ever runs (htscore.c, back_checkmirror). */
234
0
    for (i = 0; i < sback->count; i++) {
235
0
      const lien_back *const back = &sback->lnk[i];
236
237
0
      if (back_is_live(back->status) && back->r.is_write &&
238
0
          !IS_DELAYED_EXT(back->url_sav))
239
0
        opt->abort_left_partial = HTS_TRUE;
240
0
    }
241
    // delete live slots
242
0
    for(i = 0; i < sback->count; i++) {
243
0
      back_delete(opt, cache, sback, i);
244
0
    }
245
    // delete stored slots
246
0
    if (sback->ready != NULL) {
247
0
      struct_coucal_enum e = coucal_enum_new(sback->ready);
248
0
      coucal_item *item;
249
250
0
      while((item = coucal_enum_next(&e))) {
251
0
#ifndef HTS_NO_BACK_ON_DISK
252
0
        const char *filename = (char *) item->value.ptr;
253
254
0
        if (filename != NULL) {
255
0
          (void) UNLINK(filename);
256
0
          back_tmpdir_drop(filename);
257
0
        }
258
#else
259
        /* clear entry content (but not yet the entry) */
260
        lien_back *back = (lien_back *) item->value.ptr;
261
262
        back_clear_entry(back);
263
#endif
264
0
      }
265
      /* delete hashtable & content */
266
0
      coucal_delete(&sback->ready);
267
0
      sback->ready_size_bytes = 0;
268
0
    }
269
0
  }
270
0
}
271
272
// ---
273
// routines de backing
274
275
static int back_index_ready(httrackp * opt, struct_back * sback, const char *adr,
276
                            const char *fil, const char *sav, int getIndex);
277
static int back_index_fetch(httrackp * opt, struct_back * sback, const char *adr,
278
                            const char *fil, const char *sav, int getIndex);
279
280
// retourne l'index d'un lien dans un tableau de backing
281
int back_index(httrackp * opt, struct_back * sback, const char *adr, const char *fil,
282
0
               const char *sav) {
283
0
  return back_index_fetch(opt, sback, adr, fil, sav, 1);
284
0
}
285
286
static int back_index_fetch(httrackp * opt, struct_back * sback, const char *adr,
287
0
                            const char *fil, const char *sav, int getIndex) {
288
0
  lien_back *const back = sback->lnk;
289
0
  const int back_max = sback->count;
290
0
  int index = -1;
291
0
  int i;
292
293
0
  for(i = 0; i < back_max; i++) {
294
0
    if (back[i].status >= 0     /* not free or alive */
295
0
        && strfield2(back[i].url_adr, adr)
296
0
        && strcmp(back[i].url_fil, fil) == 0) {
297
0
      if (index == -1)          /* first time we meet, store it */
298
0
        index = i;
299
0
      else if (sav != NULL && strcmp(back[i].url_sav, sav) == 0) {      /* oops, check sav too */
300
0
        index = i;
301
0
        return index;
302
0
      }
303
0
    }
304
0
  }
305
  // not found in fast repository - search in the storage hashtable
306
0
  if (index == -1 && sav != NULL) {
307
0
    index = back_index_ready(opt, sback, adr, fil, sav, getIndex);
308
0
  }
309
0
  return index;
310
0
}
311
312
/* resurrect stored entry */
313
static int back_index_ready(httrackp * opt, struct_back * sback, const char *adr,
314
0
                            const char *fil, const char *sav, int getIndex) {
315
0
  lien_back *const back = sback->lnk;
316
0
  void *ptr = NULL;
317
318
0
  if (coucal_read_pvoid(sback->ready, sav, &ptr)) {
319
0
    if (!getIndex) {            /* don't "pagefault" the entry */
320
0
      if (ptr != NULL) {
321
0
        return sback->count;    /* (invalid but) positive result */
322
0
      } else {
323
0
        return -1;              /* not found */
324
0
      }
325
0
    } else if (ptr != NULL) {
326
0
      lien_back *itemback = NULL;
327
328
0
#ifndef HTS_NO_BACK_ON_DISK
329
0
      FILE *fp;
330
0
      const char *fileback = (char *) ptr;
331
0
      char catbuff[CATBUFF_SIZE];
332
333
0
      if ((fp = FOPEN(fconv(catbuff, sizeof(catbuff), fileback), "rb")) != NULL) {
334
0
        if (back_unserialize(fp, &itemback) != 0) {
335
0
          if (itemback != NULL) {
336
0
            back_clear_entry(itemback);
337
0
            freet(itemback);
338
0
            itemback = NULL;
339
0
          }
340
0
          hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
341
0
                        "engine: warning: unserialize error for %s%s (%s)", adr,
342
0
                        fil, sav);
343
0
        }
344
0
        fclose(fp);
345
0
      } else {
346
0
        hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
347
0
                      "engine: warning: unserialize error for %s%s (%s), file disappeared",
348
0
                      adr, fil, sav);
349
0
      }
350
0
      (void) UNLINK(fileback);
351
0
      back_tmpdir_drop(fileback);
352
#else
353
      itemback = (lien_back *) ptr;
354
#endif
355
0
      if (itemback != NULL) {
356
        // move from hashtable to fast repository
357
0
        int q = back_search(opt, sback);
358
359
0
        if (q != -1) {
360
0
          deletehttp(&back[q].r);       // security check
361
0
          back_move(itemback, &back[q]);
362
0
          back_clear_entry(itemback);   /* delete entry content */
363
0
          freet(itemback);      /* delete item */
364
0
          itemback = NULL;
365
0
          coucal_remove(sback->ready, sav);    // delete item
366
0
          sback->ready_size_bytes -= back[q].r.size;    /* substract for stats */
367
0
          back_set_locked(sback, q);    /* locked */
368
0
          return q;
369
0
        } else {
370
0
          hts_log_print(opt, LOG_WARNING,
371
0
                        "engine: warning: unserialize error for %s%s (%s): no more space to wakeup frozen slots",
372
0
                        adr, fil, sav);
373
0
        }
374
0
      }
375
0
    }
376
0
  }
377
0
  return -1;
378
0
}
379
380
0
static int slot_can_be_cached_on_disk(const lien_back * back) {
381
  /* A pending backup or spool means the slot is not finalized, and the swap
382
     would unlink it through back_clear_entry() (#771). */
383
0
  if (back->tmpfile != NULL && back->tmpfile[0] != '\0')
384
0
    return 0;
385
0
  return (back->status == STATUS_READY && back->locked == 0
386
0
          && back->url_sav[0] != '\0'
387
0
          && strcmp(back->url_sav, BACK_ADD_TEST) != 0);
388
  /* Note: not checking !IS_DELAYED_EXT(back->url_sav) or it will quickly cause the slots to be filled! */
389
0
}
390
391
0
int back_selftest_slot_swap(void) {
392
0
  lien_back back;
393
0
  int err = 0;
394
395
0
#define CHECK(want, why)                                                       \
396
0
  do {                                                                         \
397
0
    if (slot_can_be_cached_on_disk(&back) != (want)) {                         \
398
0
      fprintf(stderr, "backswap: expected %d for %s\n", (want), (why));        \
399
0
      err = 1;                                                                 \
400
0
    }                                                                          \
401
0
  } while (0)
402
403
0
  memset(&back, 0, sizeof(back));
404
0
  back.status = STATUS_READY;
405
0
  strcpybuff(back.url_sav, "/tmp/httrack-selftest.bin");
406
0
  CHECK(1, "a plain ready slot");
407
408
0
  back.tmpfile = back.tmpfile_buffer;
409
0
  strcpybuff(back.tmpfile_buffer, "/tmp/httrack-selftest.bin.bak");
410
0
  CHECK(0, "a slot still holding a re-fetch backup");
411
412
  /* Callers clear a spent temporary by emptying the name, not the pointer. */
413
0
  back.tmpfile_buffer[0] = '\0';
414
0
  CHECK(1, "a slot whose temporary was already dropped");
415
416
0
  back.tmpfile = NULL;
417
0
  back.locked = 1;
418
0
  CHECK(0, "a locked slot");
419
0
  back.locked = 0;
420
421
0
  back.status = STATUS_TRANSFER;
422
0
  CHECK(0, "a slot still transferring");
423
0
  back.status = STATUS_READY;
424
425
0
  back.url_sav[0] = '\0';
426
0
  CHECK(0, "a slot with no save name");
427
428
0
  strcpybuff(back.url_sav, BACK_ADD_TEST);
429
0
  CHECK(0, "the dummy test slot");
430
0
#undef CHECK
431
432
  /* The swap round-trip must not lose the size of a slot whose body is already
433
     at url_sav, or the link writer blanks the file (#797). */
434
0
  {
435
0
    static const char body[] = "swapped body";
436
0
    int c;
437
438
0
    for (c = 0; c < 2; c++) {
439
0
      const hts_boolean inmemory = c == 0 ? HTS_TRUE : HTS_FALSE;
440
0
      FILE *const fp = tmpfile();
441
0
      lien_back *copy = NULL;
442
443
0
      memset(&back, 0, sizeof(back));
444
0
      back.status = STATUS_READY;
445
0
      strcpybuff(back.url_sav, "/tmp/httrack-selftest.bin");
446
0
      back.r.size = (LLint) sizeof(body) - 1;
447
0
      if (inmemory) {
448
0
        back.r.adr = strdupt(body);
449
0
      }
450
0
      if (fp == NULL || back_serialize(fp, &back) != 0 ||
451
0
          fseek(fp, 0, SEEK_SET) != 0 || back_unserialize(fp, &copy) != 0) {
452
0
        fprintf(stderr, "backswap: round-trip failed for a %s slot\n",
453
0
                inmemory ? "buffered" : "direct-to-disk");
454
0
        err = 1;
455
0
      } else {
456
0
        if (copy->r.size != back.r.size) {
457
0
          fprintf(stderr,
458
0
                  "backswap: %s slot came back with size " LLintP
459
0
                  ", expected " LLintP "\n",
460
0
                  inmemory ? "buffered" : "direct-to-disk", copy->r.size,
461
0
                  back.r.size);
462
0
          err = 1;
463
0
        }
464
0
        if (inmemory && (copy->r.adr == NULL ||
465
0
                         memcmp(copy->r.adr, body, sizeof(body) - 1) != 0)) {
466
0
          fprintf(stderr, "backswap: buffered slot lost its body\n");
467
0
          err = 1;
468
0
        }
469
0
        if (!inmemory && copy->r.adr != NULL) {
470
0
          fprintf(stderr, "backswap: direct-to-disk slot gained a body\n");
471
0
          err = 1;
472
0
        }
473
0
        back_clear_entry(copy);
474
0
        freet(copy);
475
0
      }
476
0
      if (fp != NULL)
477
0
        fclose(fp);
478
0
      freet(back.r.adr);
479
0
    }
480
0
  }
481
482
  /* Each buffer travels through a void *, so a truncated file must still come
483
     back as a NULL slot with nothing leaked. */
484
0
  {
485
0
    static const char body[] = "truncated body";
486
0
    static const char hdrs[] = "HTTP/1.1 200 OK\r\nX: 1\r\n";
487
0
    const size_t head = sizeof(size_t);
488
0
    char *whole = NULL;
489
0
    long whole_len = 0;
490
0
    FILE *fp = tmpfile();
491
492
0
    memset(&back, 0, sizeof(back));
493
0
    back.status = STATUS_READY;
494
0
    strcpybuff(back.url_sav, "/tmp/httrack-selftest.bin");
495
0
    back.r.size = (LLint) sizeof(body) - 1;
496
0
    back.r.adr = strdupt(body);
497
0
    back.r.headers = strdupt(hdrs);
498
0
    if (fp == NULL || back_serialize(fp, &back) != 0 ||
499
0
        (whole_len = ftell(fp)) <= 0 || fseek(fp, 0, SEEK_SET) != 0) {
500
0
      fprintf(stderr, "backswap: could not serialize a slot with headers\n");
501
0
      err = 1;
502
0
    } else {
503
0
      whole = malloct((size_t) whole_len);
504
0
      if (whole == NULL ||
505
0
          fread(whole, 1, (size_t) whole_len, fp) != (size_t) whole_len) {
506
0
        fprintf(stderr, "backswap: could not read the serialized slot back\n");
507
0
        err = 1;
508
0
        whole_len = 0;
509
0
      }
510
0
    }
511
0
    if (fp != NULL)
512
0
      fclose(fp);
513
514
0
    if (whole_len > 0) {
515
      /* Cuts at each boundary the reader stops on: the leading size, the
516
         struct, the body's size and bytes, then the headers. */
517
0
      const long cuts[] = {1,
518
0
                           (long) head,
519
0
                           (long) (head + sizeof(lien_back)) - 1,
520
0
                           (long) (head + sizeof(lien_back)),
521
0
                           (long) (head + sizeof(lien_back) + head) + 1,
522
0
                           whole_len - 1,
523
0
                           whole_len};
524
0
      size_t c;
525
526
0
      for (c = 0; c < sizeof(cuts) / sizeof(cuts[0]); c++) {
527
0
        const long cut = cuts[c];
528
0
        const hts_boolean complete = cut == whole_len;
529
0
        lien_back *copy = NULL;
530
0
        FILE *cfp;
531
532
0
        if (cut <= 0 || cut > whole_len)
533
0
          continue;
534
0
        cfp = tmpfile();
535
0
        if (cfp == NULL ||
536
0
            fwrite(whole, 1, (size_t) cut, cfp) != (size_t) cut ||
537
0
            fseek(cfp, 0, SEEK_SET) != 0) {
538
0
          fprintf(stderr, "backswap: could not stage a %ld-byte slot\n", cut);
539
0
          err = 1;
540
0
        } else if (back_unserialize(cfp, &copy) == 0) {
541
0
          if (!complete) {
542
0
            fprintf(stderr, "backswap: %ld of %ld bytes unserialized anyway\n",
543
0
                    cut, whole_len);
544
0
            err = 1;
545
0
          } else if (copy->r.headers == NULL ||
546
0
                     strcmp(copy->r.headers, hdrs) != 0) {
547
0
            fprintf(stderr,
548
0
                    "backswap: the slot came back without its headers\n");
549
0
            err = 1;
550
0
          }
551
0
          back_clear_entry(copy);
552
0
          freet(copy);
553
0
        } else {
554
0
          if (complete) {
555
0
            fprintf(stderr, "backswap: the whole slot failed to unserialize\n");
556
0
            err = 1;
557
0
          }
558
0
          if (copy != NULL) {
559
0
            fprintf(stderr, "backswap: a failed unserialize kept a slot\n");
560
0
            err = 1;
561
0
          }
562
0
        }
563
0
        if (cfp != NULL)
564
0
          fclose(cfp);
565
0
      }
566
0
    }
567
0
    if (whole != NULL)
568
0
      freet(whole);
569
0
    freet(back.r.adr);
570
0
    freet(back.r.headers);
571
0
  }
572
573
  /* A ready table is a file, so its size headers are hostile input. */
574
0
  {
575
    /* SIZE_MAX wrapped the guard byte's allocation to zero, and 16 is a
576
       well-formed header of the wrong struct size, whose slot must be freed.
577
       A merely huge size is left out, since ASan aborts on it. */
578
0
    const size_t bad[] = {(size_t) -1, 16};
579
0
    size_t c;
580
581
0
    for (c = 0; c < sizeof(bad) / sizeof(bad[0]); c++) {
582
0
      FILE *const cfp = tmpfile();
583
0
      lien_back *copy = NULL;
584
0
      char pad[16];
585
586
0
      memset(pad, 0, sizeof(pad));
587
0
      if (cfp == NULL || fwrite(&bad[c], sizeof(bad[c]), 1, cfp) != 1 ||
588
0
          (bad[c] == sizeof(pad) &&
589
0
           fwrite(pad, 1, sizeof(pad), cfp) != sizeof(pad)) ||
590
0
          fseek(cfp, 0, SEEK_SET) != 0) {
591
0
        fprintf(stderr, "backswap: could not stage a bad size header\n");
592
0
        err = 1;
593
0
      } else if (back_unserialize(cfp, &copy) == 0) {
594
0
        fprintf(stderr, "backswap: a %lu-byte size header unserialized\n",
595
0
                (unsigned long) bad[c]);
596
0
        err = 1;
597
0
        back_clear_entry(copy);
598
0
        freet(copy);
599
0
      } else if (copy != NULL) {
600
0
        fprintf(stderr, "backswap: a rejected size header kept a slot\n");
601
0
        err = 1;
602
0
      }
603
0
      if (cfp != NULL)
604
0
        fclose(cfp);
605
0
    }
606
0
  }
607
608
0
  printf("backswap self-test: %s\n", err ? "FAIL" : "OK");
609
0
  return err;
610
0
}
611
612
/* Put all backing entries that are ready in the storage hashtable to spare space and CPU */
613
int back_cleanup_background(httrackp * opt, cache_back * cache,
614
0
                            struct_back * sback) {
615
0
  lien_back *const back = sback->lnk;
616
0
  const int back_max = sback->count;
617
0
  int nclean = 0;
618
0
  int i;
619
620
0
  for(i = 0; i < back_max; i++) {
621
    // ready, not locked and suitable
622
0
    if (slot_can_be_cached_on_disk(&back[i])) {
623
#ifdef HTS_NO_BACK_ON_DISK
624
      lien_back *itemback;
625
#endif
626
      /* Security check */
627
0
      int checkIndex =
628
0
        back_index_ready(opt, sback, back[i].url_adr, back[i].url_fil,
629
0
                         back[i].url_sav, 1);
630
0
      if (checkIndex != -1) {
631
0
        hts_log_print(opt, LOG_WARNING,
632
0
                      "engine: unexpected duplicate file entry: %s%s -> %s (%d '%s') / %s%s -> %s (%d '%s')",
633
0
                      back[checkIndex].url_adr, back[checkIndex].url_fil,
634
0
                      back[checkIndex].url_sav, back[checkIndex].r.statuscode,
635
0
                      back[checkIndex].r.msg, back[i].url_adr, back[i].url_fil,
636
0
                      back[i].url_sav, back[i].r.statuscode, back[i].r.msg);
637
0
        back_delete(NULL, NULL, sback, checkIndex);
638
#ifdef _DEBUG
639
        /* This should NOT happend! */
640
        {
641
          int duplicateEntryInBacklog = 1;
642
643
          assertf(!duplicateEntryInBacklog);
644
        }
645
#endif
646
0
      }
647
0
#ifndef HTS_NO_BACK_ON_DISK
648
      /* temporarily serialize the entry on disk */
649
0
      {
650
        /* +32: room for the directory and extension back_spoolname() inserts */
651
0
        char BIGSTK tmpname[HTS_URLMAXSIZE * 2 + 32];
652
0
        char *filename;
653
0
        const hts_boolean named =
654
0
            back_spoolname(opt, back[i].url_sav, tmpname, sizeof(tmpname));
655
0
        filename = named ? strdupt(tmpname) : NULL;
656
657
0
        if (filename != NULL) {
658
0
          FILE *fp;
659
660
          /* Security check */
661
0
          if (fexist_utf8(filename)) {
662
0
            hts_log_print(opt, LOG_WARNING,
663
0
                          "engine: warning: temporary file %s already exists",
664
0
                          filename);
665
0
          }
666
          /* Create file and serialize slot */
667
0
          if ((fp = filecreate(NULL, filename)) != NULL) {
668
0
            if (back_serialize(fp, &back[i]) == 0) {
669
0
              coucal_add_pvoid(sback->ready, back[i].url_sav, filename);
670
0
              filename = NULL;
671
0
              sback->ready_size_bytes += back[i].r.size;        /* add for stats */
672
0
              nclean++;
673
0
              back_clear_entry(&back[i]);       /* entry is now recycled */
674
0
            } else {
675
0
              hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
676
0
                            "engine: warning: serialize error for %s%s to %s: write error",
677
0
                            back[i].url_adr, back[i].url_fil, filename);
678
0
            }
679
0
            fclose(fp);
680
0
          } else {
681
0
            hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
682
0
                          "engine: warning: serialize error for %s%s to %s: open error (%s, %s)",
683
0
                          back[i].url_adr, back[i].url_fil, filename,
684
0
                          dir_exists(filename) ? "directory exists" :
685
0
                          "directory does NOT exist!",
686
0
                          fexist_utf8(filename) ? "file already exists!" :
687
0
                          "file does not exist");
688
0
          }
689
0
          if (filename != NULL)
690
0
            freet(filename);
691
0
        } else {
692
0
          hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
693
0
                        "engine: warning: serialize error for %s%s: %s",
694
0
                        back[i].url_adr, back[i].url_fil,
695
0
                        named ? "memory full" : "temporary filename too long");
696
0
        }
697
0
      }
698
#else
699
      itemback = calloct(1, sizeof(lien_back));
700
      back_move(&back[i], itemback);
701
      coucal_add_pvoid(sback->ready, itemback->url_sav, itemback);
702
      nclean++;
703
#endif
704
0
    }
705
0
  }
706
0
  return nclean;
707
0
}
708
709
// nombre d'entrées libres dans le backing
710
0
int back_available(const struct_back * sback) {
711
0
  const lien_back *const back = sback->lnk;
712
0
  const int back_max = sback->count;
713
0
  int i;
714
0
  int nb = 0;
715
716
0
  for(i = 0; i < back_max; i++)
717
0
    if (back[i].status == STATUS_FREE)  /* libre */
718
0
      nb++;
719
0
  return nb;
720
0
}
721
722
// retourne estimation de la taille des html et fichiers stockés en mémoire
723
0
LLint back_incache(const struct_back * sback) {
724
0
  const lien_back *const back = sback->lnk;
725
0
  const int back_max = sback->count;
726
0
  int i;
727
0
  LLint sum = 0;
728
729
0
  for(i = 0; i < back_max; i++)
730
0
    if (back[i].status != -1)
731
0
      if (back[i].r.adr)        // ne comptabilier que les blocs en mémoire
732
0
        sum += max(back[i].r.size, back[i].r.totalsize);
733
  // stored (ready) slots
734
#ifdef HTS_NO_BACK_ON_DISK
735
  if (sback->ready != NULL) {
736
    struct_coucal_enum e = coucal_enum_new(sback->ready);
737
    coucal_item *item;
738
739
    while((item = coucal_enum_next(&e))) {
740
      lien_back *ritem = (lien_back *) item->value.ptr;
741
742
      if (ritem->status != -1)
743
        if (ritem->r.adr)       // ne comptabilier que les blocs en mémoire
744
          sum += max(ritem->r.size, ritem->r.totalsize);
745
    }
746
  }
747
#endif
748
0
  return sum;
749
0
}
750
751
// retourne estimation de la taille des html et fichiers stockés en mémoire
752
0
int back_done_incache(const struct_back * sback) {
753
0
  const lien_back *const back = sback->lnk;
754
0
  const int back_max = sback->count;
755
0
  int i;
756
0
  int n = 0;
757
758
0
  for(i = 0; i < back_max; i++)
759
0
    if (back[i].status == STATUS_READY)
760
0
      n++;
761
  // stored (ready) slots
762
0
  if (sback->ready != NULL) {
763
0
#ifndef HTS_NO_BACK_ON_DISK
764
0
    n += (int) coucal_nitems(sback->ready);
765
#else
766
    struct_coucal_enum e = coucal_enum_new(sback->ready);
767
    coucal_item *item;
768
769
    while((item = coucal_enum_next(&e))) {
770
      lien_back *ritem = (lien_back *) item->value.ptr;
771
772
      if (ritem->status == STATUS_READY)
773
        n++;
774
    }
775
#endif
776
0
  }
777
0
  return n;
778
0
}
779
780
// le lien a-t-il été mis en backing?
781
HTS_INLINE int back_exist(struct_back * sback, httrackp * opt, const char *adr,
782
0
                          const char *fil, const char *sav) {
783
0
  return (back_index_fetch(opt, sback, adr, fil, sav, /*don't fetch */ 0) >= 0);
784
0
}
785
786
// nombre de sockets en tâche de fond
787
0
int back_nsoc(const struct_back * sback) {
788
0
  const lien_back *const back = sback->lnk;
789
0
  const int back_max = sback->count;
790
0
  int n = 0;
791
0
  int i;
792
793
0
  for(i = 0; i < back_max; i++)
794
0
    if (back[i].status > 0)     // only receive
795
0
      n++;
796
797
0
  return n;
798
0
}
799
0
int back_nsoc_overall(const struct_back * sback) {
800
0
  const lien_back *const back = sback->lnk;
801
0
  const int back_max = sback->count;
802
0
  int n = 0;
803
0
  int i;
804
805
0
  for(i = 0; i < back_max; i++)
806
0
    if (back[i].status > 0 || back[i].status == STATUS_ALIVE)
807
0
      n++;
808
809
0
  return n;
810
0
}
811
812
/* Build save's temporary as <dir>/<HTS_TMPDIR>/<name>.<ext>. Appending the
813
   extension to save instead put it in the mirror namespace, so a site serving
814
   <path>.bak had its copy taken as the backup and then unlinked (#774).
815
   HTS_FALSE (dest emptied) if it would not fit. Note: utf-8. */
816
static hts_boolean back_tmpname(char *dest, size_t size, const char *save,
817
0
                                const char *ext) {
818
0
  const char *const slash = strrchr(save, '/');
819
0
  const int dirlen = slash != NULL ? (int) (slash - save) + 1 : 0;
820
821
0
  if (!slprintfbuff(dest, size, "%.*s" HTS_TMPDIR "/%s.%s", dirlen, save,
822
0
                    slash != NULL ? slash + 1 : save, ext)) {
823
0
    dest[0] = '\0';
824
0
    return HTS_FALSE;
825
0
  }
826
0
  return HTS_TRUE;
827
0
}
828
829
/* Note: utf-8 */
830
0
void back_tmpdir_drop(const char *tmp) {
831
0
  char BIGSTK dir[HTS_URLMAXSIZE * 2];
832
0
  const char *slash;
833
834
0
  if (tmp == NULL || (slash = strrchr(tmp, '/')) == NULL)
835
0
    return;
836
0
  if (!strclipbuff(dir, sizeof(dir), tmp))
837
0
    return;
838
0
  dir[slash - tmp] = '\0';
839
0
  slash = strrchr(dir, '/');
840
0
  if (strcmp(slash != NULL ? slash + 1 : dir, HTS_TMPDIR) == 0)
841
0
    (void) RMDIR(dir);
842
0
}
843
844
/* generate temporary file on lien_back */
845
/* Note: utf-8 */
846
static int create_back_tmpfile(httrackp *opt, lien_back *const back,
847
0
                               const char *ext) {
848
  // do not use tempnam() but a regular filename
849
0
  back->tmpfile_buffer[0] = '\0';
850
0
  if (back->url_sav[0] != '\0') {
851
0
    if (!back_tmpname(back->tmpfile_buffer, sizeof(back->tmpfile_buffer),
852
0
                      back->url_sav, ext)) {
853
0
      hts_log_print(opt, LOG_WARNING, "temporary filename too long for %s",
854
0
                    back->url_sav);
855
0
      return -1;
856
0
    }
857
0
    back->tmpfile = back->tmpfile_buffer;
858
0
    if (structcheck(back->tmpfile) != 0) {
859
0
      hts_log_print(opt, LOG_WARNING, "can not create directory to %s",
860
0
                    back->tmpfile);
861
0
      back->tmpfile_buffer[0] = '\0';
862
0
      back->tmpfile = NULL;
863
0
      return -1;
864
0
    }
865
0
  } else {
866
    /* same directory as the named case, so back_tmpdir_drop() only removes one
867
       the engine made (#842) */
868
    /* truncation here would collide distinct tmpnameid's onto one name */
869
0
    if (!sprintfbuff(back->tmpfile_buffer, "%s" HTS_TMPDIR "/tmp%d.%s",
870
0
                     StringBuff(opt->path_html_utf8), opt->state.tmpnameid++,
871
0
                     ext)) {
872
0
      hts_log_print(opt, LOG_WARNING, "temporary filename too long in %s",
873
0
                    StringBuff(opt->path_html_utf8));
874
0
      back->tmpfile_buffer[0] = '\0';
875
0
      return -1;
876
0
    }
877
0
    back->tmpfile = back->tmpfile_buffer;
878
0
    if (structcheck(back->tmpfile) != 0) {
879
0
      hts_log_print(opt, LOG_WARNING, "can not create directory to %s",
880
0
                    back->tmpfile);
881
0
      back->tmpfile_buffer[0] = '\0';
882
0
      back->tmpfile = NULL;
883
0
      return -1;
884
0
    }
885
0
  }
886
  /* OK */
887
0
  hts_log_print(opt, LOG_TRACE, "produced temporary name %s", back->tmpfile);
888
0
  return 0;
889
0
}
890
891
/* Note: utf-8 */
892
0
void back_refetch_backup(httrackp *opt, lien_back *const back) {
893
0
  back->tmpfile = NULL;
894
0
  if (fexist_utf8(back->url_sav)) {
895
0
    hts_boolean saved = HTS_FALSE;
896
897
0
    if (create_back_tmpfile(opt, back, "bak") == 0) {
898
      /* clobber a .bak a killed run left behind, or the guard stays off for
899
         good (#758) */
900
0
      if (fexist_utf8(back->tmpfile))
901
0
        hts_log_print(opt, LOG_WARNING, "replacing leftover backup %s",
902
0
                      back->tmpfile);
903
0
      saved = hts_rename_over(opt, back->url_sav, back->tmpfile);
904
      /* Another slot sharing the directory may have removed it between the
905
         structcheck above and the rename: recreate it and try once more. */
906
0
      if (!saved && structcheck(back->tmpfile) == 0)
907
0
        saved = hts_rename_over(opt, back->url_sav, back->tmpfile);
908
0
    }
909
0
    if (!saved) {
910
0
      hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
911
0
                    "could not back up %s; an aborted re-fetch will lose it",
912
0
                    back->url_sav);
913
0
      back->tmpfile = NULL;
914
0
    }
915
0
  }
916
0
}
917
918
0
hts_boolean back_transfer_failed(const int statuscode) {
919
0
  switch (statuscode) {
920
0
  case STATUSCODE_TOO_BIG:
921
0
  case STATUSCODE_EXCLUDED:
922
0
  case STATUSCODE_TEST_OK:
923
0
    return HTS_FALSE;
924
0
  default:
925
0
    return statuscode <= 0 ? HTS_TRUE : HTS_FALSE;
926
0
  }
927
0
}
928
929
hts_boolean back_finalize_backup(httrackp *opt, lien_back *const back,
930
0
                                 hts_boolean commit) {
931
0
  const hts_boolean wanted = commit;
932
933
0
  if (back->tmpfile == NULL || back->r.compressed)
934
0
    return HTS_TRUE;
935
  /* Nothing to commit to: filecreate() can fail after the backup was taken,
936
     and dropping it then loses both copies (#775). */
937
0
  if (commit && !fexist_utf8(back->url_sav)) {
938
0
    hts_log_print(opt, LOG_WARNING, "%s was never created; restoring %s",
939
0
                  back->url_sav, back->tmpfile);
940
0
    commit = HTS_FALSE;
941
0
  }
942
0
  if (commit) {
943
0
    (void) UNLINK(back->tmpfile); /* new copy is good; drop the backup */
944
0
    back_tmpdir_drop(back->tmpfile);
945
0
  } else {
946
0
    if (back->r.out != NULL) {
947
0
      fclose(back->r.out);
948
0
      back->r.out = NULL;
949
0
    }
950
    /* On failure keep the backup: an orphaned temp beats losing the good copy.
951
     */
952
0
    if (!hts_rename_over(opt, back->tmpfile, back->url_sav)) {
953
0
      hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
954
0
                    "could not restore %s; previous copy kept as %s",
955
0
                    back->url_sav, back->tmpfile);
956
0
    } else {
957
0
      back_tmpdir_drop(back->tmpfile);
958
      /* The restore replaced the partial, so its byte ranges now describe a
959
         file that is gone (#1595). */
960
0
      url_savename_refname_remove(opt, back->url_adr, back->url_fil);
961
0
    }
962
0
  }
963
0
  back->tmpfile = NULL;
964
0
  return commit == wanted ? HTS_TRUE : HTS_FALSE;
965
0
}
966
967
/* A chunked body is framed by its terminating zero-length chunk (#840);
968
   chunk_blocksize is reset per response and reaches -1 only once it is seen. */
969
0
static hts_boolean back_chunked_unterminated(const lien_back *const back) {
970
0
  return back->is_chunk && back->chunk_blocksize != -1 ? HTS_TRUE : HTS_FALSE;
971
0
}
972
973
/* Past the terminating chunk, the line still owed is the optional trailer
974
   section (RFC 9112 7.1.2), read and discarded like a header block. */
975
0
static hts_boolean back_in_chunk_trailers(const lien_back *const back) {
976
0
  return back->status == STATUS_CHUNK_CR && back->chunk_blocksize == -1
977
0
             ? HTS_TRUE
978
0
             : HTS_FALSE;
979
0
}
980
981
/* Name a write we could not complete -- a failing close, or a decode that could
982
   not write its output -- and give up the mirror on the fatal class. */
983
0
static void back_report_write_failure(httrackp *opt, lien_back *const back) {
984
0
  const hts_boolean fatal = check_fatal_io_errno() ? HTS_TRUE : HTS_FALSE;
985
986
  /* the read path already named and classed a write error it saw itself */
987
0
  if (!statuscode_is_write_error(back->r.statuscode)) {
988
0
    hts_log_print(opt, LOG_ERROR | LOG_ERRNO, "Unable to write file %s",
989
0
                  back->url_sav);
990
    /* a slot still claiming success would be cached as mirrored; a
991
       STATUSCODE_INVALID must survive, the decode site's purge rests on it */
992
0
    if (back->r.statuscode > 0) {
993
0
      back->r.statuscode = fatal ? STATUSCODE_IO_FATAL : STATUSCODE_IO_ERROR;
994
0
      strcpybuff(back->r.msg, "Write error on disk");
995
0
    }
996
0
  }
997
0
  if (fatal && opt->state.exit_xh == 0) {
998
0
    hts_log_print(opt, LOG_ERROR,
999
0
                  "Mirror aborted: disk full or filesystem problems");
1000
0
    opt->state.exit_xh = -1;
1001
0
  }
1002
0
}
1003
1004
0
hts_boolean back_set_decoded_size(htsblk *r, LLint size) {
1005
0
  if (!r->is_write && !hts_inmem_size_fits(size)) {
1006
0
    r->statuscode = STATUSCODE_INVALID;
1007
0
    strcpybuff(r->msg, "Decompressed content too large");
1008
0
    deleteaddr(r);
1009
0
    return HTS_FALSE;
1010
0
  }
1011
0
  r->size = r->totalsize = size;
1012
0
  return HTS_TRUE;
1013
0
}
1014
1015
// objet (lien) téléchargé ou transféré depuis le cache
1016
//
1017
// fermer les paramètres de transfert,
1018
// et notamment vérifier les fichiers compressés (décompresser), callback etc.
1019
int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
1020
0
                  const int p) {
1021
0
  char catbuff[CATBUFF_SIZE];
1022
0
  lien_back *const back = sback->lnk;
1023
0
  const int back_max = sback->count;
1024
1025
0
  assertf(p >= 0 && p < back_max);
1026
1027
  /* Store ? */
1028
0
  if (!back[p].finalized) {
1029
0
    back[p].finalized = 1;
1030
1031
    /* Don't store broken files. Note: check is done before compression.
1032
       If the file is partial, the next run will attempt to continue it with compression too.
1033
     */
1034
0
    const hts_boolean cut_chunked = back_chunked_unterminated(&back[p]);
1035
0
    const hts_boolean short_body =
1036
0
        back[p].r.totalsize >= 0 && back[p].r.size != back[p].r.totalsize
1037
0
            ? HTS_TRUE
1038
0
            : HTS_FALSE;
1039
1040
0
    if ((short_body || cut_chunked) && back[p].r.statuscode > 0 &&
1041
0
        !opt->tolerant) {
1042
0
      if (cut_chunked) {
1043
0
        hts_log_print(
1044
0
            opt, LOG_WARNING,
1045
0
            "truncated chunked transfer (terminating chunk missing, got " LLintP
1046
0
            " bytes): file not cached, will be retried on the next"
1047
0
            " update (use -%%B to cache anyway): %s%s",
1048
0
            back[p].r.size, back[p].url_adr, back[p].url_fil);
1049
0
      } else if (back[p].status == STATUS_READY) {
1050
0
        hts_log_print(opt, LOG_WARNING,
1051
0
                      "incomplete transfer (expected " LLintP
1052
0
                      " bytes, got " LLintP
1053
0
                      "): file not cached, will be retried on the next update"
1054
0
                      " (use -%%B to cache anyway): %s%s",
1055
0
                      back[p].r.totalsize, back[p].r.size, back[p].url_adr,
1056
0
                      back[p].url_fil);
1057
0
      } else {
1058
0
        hts_log_print(opt, LOG_INFO,
1059
0
                      "incomplete file not yet stored in cache (expected "
1060
0
                      LLintP " got " LLintP "): %s%s", back[p].r.totalsize,
1061
0
                      back[p].r.size, back[p].url_adr, back[p].url_fil);
1062
0
      }
1063
0
      back_finalize_backup(opt, &back[p], HTS_FALSE);
1064
      /* Keep the surviving copy in new.lst, else the update purge drops the
1065
         file we refused to overwrite with the partial body (#562). */
1066
0
      if (fexist_utf8(back[p].url_sav))
1067
0
        filenote(&opt->state.strc, back[p].url_sav, NULL);
1068
0
      return -1;
1069
0
    }
1070
1071
0
    if ((back[p].status == STATUS_READY)        // ready
1072
0
        && (back[p].r.statuscode > 0)   // not internal error
1073
0
      ) {
1074
0
      if (!back[p].testmode) {  // not test mode
1075
0
        const char *state = "unknown";
1076
1077
        /* Undo the content coding */
1078
0
        if (back[p].r.compressed) {
1079
0
          if (back[p].r.size > 0) {
1080
            // stats
1081
0
            back[p].compressed_size = back[p].r.size;
1082
            // en mémoire -> passage sur disque
1083
0
            if (!back[p].r.is_write) {
1084
              // do not use tempnam() but a regular filename
1085
0
              if (create_back_tmpfile(opt, &back[p], "z") == 0) {
1086
0
                assertf(back[p].tmpfile != NULL);
1087
                /* note: tmpfile is utf-8 */
1088
0
                back[p].r.out = FOPEN(back[p].tmpfile, "wb");
1089
0
                if (back[p].r.out) {
1090
0
                  if ((back[p].r.adr) && (back[p].r.size > 0)) {
1091
0
                    if (!hts_fwrite_exact(back[p].r.adr,
1092
0
                                          (size_t) back[p].r.size,
1093
0
                                          back[p].r.out)) {
1094
0
                      back[p].r.statuscode = STATUSCODE_INVALID;
1095
0
                      strcpybuff(back[p].r.msg,
1096
0
                                 "Write error when decompressing");
1097
0
                    }
1098
0
                  } else {
1099
0
                    back[p].tmpfile[0] = '\0';
1100
0
                    back[p].r.statuscode = STATUSCODE_INVALID;
1101
0
                    strcpybuff(back[p].r.msg, "Empty compressed file");
1102
0
                  }
1103
0
                } else {
1104
0
                  snprintf(back[p].r.msg, sizeof(back[p].r.msg),
1105
0
                           "Open error when decompressing (can not create temporary file %s)",
1106
0
                           back[p].tmpfile);
1107
0
                  back[p].tmpfile[0] = '\0';
1108
0
                  back[p].r.statuscode = STATUSCODE_INVALID;
1109
0
                }
1110
0
              } else {
1111
0
                snprintf(back[p].r.msg, sizeof(back[p].r.msg),
1112
0
                         "Open error when decompressing (can not generate a temporary file)");
1113
0
              }
1114
0
            }
1115
            // fermer fichier sortie
1116
0
            if (back[p].r.out != NULL) {
1117
0
              fclose(back[p].r.out);
1118
0
              back[p].r.out = NULL;
1119
0
            }
1120
            // décompression
1121
0
            if (back[p].tmpfile != NULL) {
1122
0
              if (back[p].url_sav[0]) {
1123
0
                const hts_codec codec =
1124
0
                    hts_codec_parse(back[p].r.contentencoding);
1125
                /* Never decode over url_sav: a failed decode would destroy the
1126
                   copy an --update re-fetch is supposed to refresh (#557). */
1127
0
                char BIGSTK unpacked[HTS_URLMAXSIZE * 2];
1128
0
                LLint size;
1129
1130
                /* fits whenever the .z temp it decodes from did */
1131
0
                if (!back_tmpname(unpacked, sizeof(unpacked), back[p].url_sav,
1132
0
                                  "u")) {
1133
0
                  back[p].r.statuscode = STATUSCODE_INVALID;
1134
0
                  strcpybuff(back[p].r.msg, "Error when decompressing (the "
1135
0
                                            "temporary filename is too long)");
1136
                  /* as the decode-failure branch below: never let the coded
1137
                     bytes be committed as the page */
1138
0
                  if (!back[p].r.is_write)
1139
0
                    deleteaddr(&back[p].r);
1140
0
                } else if ((size = hts_codec_unpack(codec, back[p].tmpfile,
1141
0
                                                    unpacked)) >= 0) {
1142
0
                  const hts_boolean sized =
1143
0
                      back_set_decoded_size(&back[p].r, size);
1144
1145
0
                  if (sized && back[p].r.is_write) {
1146
                    /* Sample the previous copy now: the rename below replaces
1147
                       it, and file_notify() only fires once it is gone. */
1148
0
                    hts_changes_notify(
1149
0
                        opt, back[p].url_adr, back[p].url_fil, back[p].url_sav,
1150
0
                        HTS_TRUE, back[p].r.notmodified ? HTS_TRUE : HTS_FALSE);
1151
0
                  }
1152
0
                  if (!sized) {
1153
0
                    UNLINK(unpacked);
1154
0
                  } else if (!back[p].r.is_write) {
1155
                    // fichier -> mémoire ; le fichier est écrit plus tard
1156
0
                    deleteaddr(&back[p].r);
1157
0
                    back[p].r.adr = readfile_utf8(unpacked);
1158
0
                    if (!back[p].r.adr) {
1159
0
                      back[p].r.statuscode = STATUSCODE_INVALID;
1160
0
                      strcpybuff(back[p].r.msg,
1161
0
                                 "Read error when decompressing");
1162
0
                    }
1163
0
                    UNLINK(unpacked);
1164
0
                  } else if (hts_rename_over(opt, unpacked, back[p].url_sav)) {
1165
                    /* The temp bypassed filecreate(), which is what chmods. */
1166
0
#ifndef _WIN32
1167
0
                    chmod(back[p].url_sav, HTS_ACCESS_FILE);
1168
0
#endif
1169
0
                    file_notify(opt, back[p].url_adr, back[p].url_fil,
1170
0
                                back[p].url_sav, 1, 1, back[p].r.notmodified);
1171
0
                    filenote(&opt->state.strc, back[p].url_sav, NULL);
1172
0
                  } else {
1173
0
                    back[p].r.statuscode = STATUSCODE_INVALID;
1174
0
                    strcpybuff(back[p].r.msg,
1175
0
                               "Write error when decompressing (can not rename "
1176
0
                               "the temporary file)");
1177
                    /* Keep the decoded body: the failed replace may have
1178
                       removed the previous copy, leaving this as the only one.
1179
                     */
1180
0
                    hts_log_print(
1181
0
                        opt, LOG_WARNING | LOG_ERRNO,
1182
0
                        "could not replace %s; decoded copy kept as %s",
1183
0
                        back[p].url_sav, unpacked);
1184
0
                  }
1185
0
                } else {
1186
0
                  back[p].r.statuscode = STATUSCODE_INVALID;
1187
                  /* Our own disk, not the coded body: hts_codec_unpack() leaves
1188
                     a local write's errno behind, and 0 for a bad stream. */
1189
0
                  if (errno != 0)
1190
0
                    back_report_write_failure(opt, &back[p]);
1191
0
                  snprintf(back[p].r.msg, sizeof(back[p].r.msg),
1192
0
                           codec == HTS_CODEC_UNSUPPORTED
1193
0
                               ? "Unsupported Content-Encoding (%s)"
1194
0
                               : "Error when decompressing (%s)",
1195
0
                           back[p].r.contentencoding);
1196
                  /* Drop the undecoded body so the writer can't commit the
1197
                     coded bytes as the page; url_sav is left untouched. */
1198
0
                  if (!back[p].r.is_write)
1199
0
                    deleteaddr(&back[p].r);
1200
0
                  UNLINK(unpacked);
1201
0
                }
1202
                /* A failed decode keeps the previously-mirrored copy: note it,
1203
                   or the update purge (in old.lst, absent from new.lst) would
1204
                   delete what we just took care not to overwrite. */
1205
0
                if (back[p].r.statuscode == STATUSCODE_INVALID &&
1206
0
                    fexist_utf8(back[p].url_sav))
1207
0
                  filenote(&opt->state.strc, back[p].url_sav, NULL);
1208
0
              }
1209
              /* Keep the compressed spool so the WARC record stores the body
1210
                 verbatim (Content-Encoding preserved) instead of unlinking it.
1211
               */
1212
0
              if (StringNotEmpty(opt->warc_file)) {
1213
0
                warc_adopt_rawspool(&back[p].r, back[p].tmpfile);
1214
0
                if (back[p].r.warc_rawpath != NULL)
1215
0
                  back[p].tmpfile =
1216
0
                      NULL; /* adopted: freed via warc_free_request */
1217
0
              }
1218
              /* ensure that no remaining temporary file exists */
1219
0
              if (back[p].tmpfile != NULL) {
1220
0
                unlink(back[p].tmpfile);
1221
0
                back_tmpdir_drop(back[p].tmpfile); /* the .u went with it */
1222
0
                back[p].tmpfile = NULL;
1223
0
              }
1224
0
            }
1225
            // stats
1226
0
            HTS_STAT.total_packed += back[p].compressed_size;
1227
0
            HTS_STAT.total_unpacked += back[p].r.size;
1228
0
            HTS_STAT.total_packedfiles++;
1229
            // unflag
1230
0
          }
1231
0
        }
1232
        /* Body fully received: keep the freshly written url_sav, drop the
1233
           backup of the previous copy. */
1234
0
        if (!back_finalize_backup(opt, &back[p], HTS_TRUE)) {
1235
          /* The previous copy is back because the new one was never created;
1236
             caching this response's validators against it would pin the stale
1237
             body on every later --update. */
1238
0
          if (fexist_utf8(back[p].url_sav))
1239
0
            filenote(&opt->state.strc, back[p].url_sav, NULL);
1240
0
          return -1;
1241
0
        }
1242
        /* Write mode to disk */
1243
0
        if (back[p].r.is_write && back[p].r.adr != NULL) {
1244
0
          freet(back[p].r.adr);
1245
0
          back[p].r.adr = NULL;
1246
0
        }
1247
1248
        /* remove reference file, if any */
1249
0
        if (back[p].r.is_write) {
1250
0
          url_savename_refname_remove(opt, back[p].url_adr, back[p].url_fil);
1251
0
        }
1252
1253
        /* ************************************************************************
1254
           REAL MEDIA HACK
1255
           Check if we have to load locally the file
1256
           ************************************************************************ */
1257
0
        if (back[p].r.statuscode == HTTP_OK) {  // OK (ou 304 en backing)
1258
0
          if (back[p].r.is_write) {     // Written file
1259
0
            if (may_be_hypertext_mime(opt, back[p].r.contenttype, back[p].url_fil)) {   // to parse!
1260
0
              LLint sz;
1261
1262
0
              sz = fsize_utf8(back[p].url_sav);
1263
0
              if (sz > 0) {     // ok, exists!
1264
0
                if (sz < 8192) {        // ok, small file --> to parse!
1265
0
                  FILE *fp = FOPEN(back[p].url_sav, "rb");
1266
1267
0
                  if (fp) {
1268
0
                    back[p].r.adr = malloct((size_t) sz + 1);
1269
0
                    if (back[p].r.adr) {
1270
0
                      if (hts_fread_exact(back[p].r.adr, (size_t) sz, fp)) {
1271
0
                        back[p].r.size = sz;
1272
0
                        back[p].r.adr[sz] = '\0';
1273
0
                        back[p].r.is_write = 0; /* not anymore a direct-to-disk file */
1274
0
                      } else {
1275
0
                        freet(back[p].r.adr);
1276
0
                        back[p].r.size = 0;
1277
0
                        back[p].r.adr = NULL;
1278
0
                        back[p].r.statuscode = STATUSCODE_INVALID;
1279
0
                        strcpybuff(back[p].r.msg, ".RAM read error");
1280
0
                      }
1281
0
                      fclose(fp);
1282
0
                      fp = NULL;
1283
                      // remove (temporary) file!
1284
0
                      UNLINK(fconv(catbuff, sizeof(catbuff), back[p].url_sav));
1285
0
                    }
1286
0
                    if (fp)
1287
0
                      fclose(fp);
1288
0
                  }
1289
0
                }
1290
0
              }
1291
0
            }
1292
0
          }
1293
0
        }
1294
        /* EN OF REAL MEDIA HACK */
1295
1296
        /* Stats */
1297
0
        if (cache->txt) {
1298
0
          char flags[32];
1299
0
          char s[256];
1300
0
          time_t tt;
1301
0
          struct tm tmv;
1302
1303
0
          tt = time(NULL);
1304
0
          if (!hts_localtime(tt, &tmv)) {
1305
0
            int localtime_returned_null = 0;
1306
1307
0
            assertf(localtime_returned_null);
1308
0
          }
1309
0
          strftime(s, 250, "%H:%M:%S", &tmv);
1310
1311
0
          flags[0] = '\0';
1312
          /* input flags */
1313
0
          if (back[p].is_update)
1314
0
            strcatbuff(flags, "U");     // update request
1315
0
          else
1316
0
            strcatbuff(flags, "-");
1317
0
          if (back[p].range_req_size)
1318
0
            strcatbuff(flags, "R");     // range request
1319
0
          else
1320
0
            strcatbuff(flags, "-");
1321
          /* state flags */
1322
0
          if (back[p].r.is_file)        // direct to disk
1323
0
            strcatbuff(flags, "F");
1324
0
          else
1325
0
            strcatbuff(flags, "-");
1326
          /* output flags */
1327
0
          if (!back[p].r.notmodified)
1328
0
            strcatbuff(flags, "M");     // modified
1329
0
          else
1330
0
            strcatbuff(flags, "-");
1331
0
          if (back[p].r.is_chunk)       // chunked
1332
0
            strcatbuff(flags, "C");
1333
0
          else
1334
0
            strcatbuff(flags, "-");
1335
0
          if (back[p].r.compressed)
1336
0
            strcatbuff(flags, "Z"); // content coding
1337
0
          else
1338
0
            strcatbuff(flags, "-");
1339
          /* Err I had to split these.. */
1340
0
          fprintf(cache->txt, "%s\t", s);
1341
0
          fprintf(cache->txt, LLintP "/", (LLint) back[p].r.size);
1342
0
          fprintf(cache->txt, LLintP, (LLint) back[p].r.totalsize);
1343
0
          fprintf(cache->txt, "\t%s\t", flags);
1344
0
        }
1345
0
        back[p].r.compressed = 0;
1346
1347
0
        if (back[p].r.statuscode == HTTP_OK) {
1348
0
          if (back[p].r.size >= 0) {
1349
0
            if (strcmp(back[p].url_fil, "/robots.txt") != 0) {
1350
0
              HTS_STAT.stat_bytes += back[p].r.size;
1351
0
              HTS_STAT.stat_files++;
1352
0
              hts_log_print(opt, LOG_TRACE, "added file %s%s => %s",
1353
0
                            back[p].url_adr, back[p].url_fil, back[p].url_sav);
1354
0
            }
1355
0
            if ((!back[p].r.notmodified) && (opt->is_update)) {
1356
0
              HTS_STAT.stat_updated_files++;    // page modifiée
1357
0
              if (back[p].is_update) {
1358
0
                hts_log_print(opt, LOG_INFO,
1359
0
                              "engine: transfer-status: link updated: %s%s -> %s",
1360
0
                              back[p].url_adr, back[p].url_fil,
1361
0
                              back[p].url_sav);
1362
0
              } else {
1363
0
                hts_log_print(opt, LOG_INFO,
1364
0
                              "engine: transfer-status: link added: %s%s -> %s",
1365
0
                              back[p].url_adr, back[p].url_fil,
1366
0
                              back[p].url_sav);
1367
0
              }
1368
0
              if (cache->txt) {
1369
0
                if (back[p].is_update) {
1370
0
                  state = "updated";
1371
0
                } else {
1372
0
                  state = "added";
1373
0
                }
1374
0
              }
1375
0
            } else {
1376
0
              hts_log_print(opt, LOG_INFO,
1377
0
                            "engine: transfer-status: link recorded: %s%s -> %s",
1378
0
                            back[p].url_adr, back[p].url_fil, back[p].url_sav);
1379
0
              if (cache->txt) {
1380
0
                if (opt->is_update)
1381
0
                  state = "untouched";
1382
0
                else
1383
0
                  state = "added";
1384
0
              }
1385
0
            }
1386
0
          } else {
1387
0
            hts_log_print(opt, LOG_INFO,
1388
0
                          "engine: transfer-status: empty file? (%d, '%s'): %s%s",
1389
0
                          back[p].r.statuscode, back[p].r.msg, back[p].url_adr,
1390
0
                          back[p].url_fil);
1391
0
            if (cache->txt) {
1392
0
              state = "empty";
1393
0
            }
1394
0
          }
1395
0
        } else {
1396
0
          hts_log_print(opt, LOG_INFO,
1397
0
                        "engine: transfer-status: link error (%d, '%s'): %s%s",
1398
0
                        back[p].r.statuscode, back[p].r.msg, back[p].url_adr,
1399
0
                        back[p].url_fil);
1400
0
          if (cache->txt) {
1401
0
            state = "error";
1402
0
          }
1403
0
        }
1404
0
        if (cache->txt) {
1405
0
#undef ESC_URL
1406
0
#define ESC_URL(S) escape_check_url_addr(S, OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt))
1407
0
          fprintf(cache->txt,
1408
0
                  "%d\t" "%s ('%s')\t" "%s\t" "%s%s\t" "%s%s%s\t%s\t"
1409
0
                  "(from %s%s%s)" LF, back[p].r.statuscode, state,
1410
0
                  ESC_URL(back[p].r.msg),
1411
0
                  ESC_URL(back[p].r.contenttype),
1412
0
                  ((back[p].r.etag[0]) ? "etag:" : ((back[p].r.
1413
0
                                           lastmodified[0]) ? "date:" : "")),
1414
0
                  ESC_URL((back[p].r.etag[0]) ? back[p].r.
1415
0
                                        etag : (back[p].r.lastmodified)),
1416
0
                  (link_has_authority(back[p].url_adr) ? "" : "http://"),
1417
0
                  ESC_URL(back[p].url_adr),
1418
0
                  ESC_URL(back[p].url_fil),
1419
0
                  ESC_URL(back[p].url_sav),
1420
0
                  (link_has_authority(back[p].referer_adr)
1421
0
                   || !back[p].referer_adr[0]) ? "" : "http://",
1422
0
                  ESC_URL(back[p].referer_adr),
1423
0
                  ESC_URL(back[p].referer_fil)
1424
0
            );
1425
0
#undef ESC_URL
1426
0
          if (opt->flush)
1427
0
            fflush(cache->txt);
1428
0
        }
1429
1430
        /* Cache */
1431
0
        if (!IS_DELAYED_EXT(back[p].url_sav)) {
1432
0
          cache_mayadd(opt, cache, &back[p].r, back[p].url_adr, back[p].url_fil,
1433
0
                       back[p].url_sav);
1434
0
        } else {
1435
          /* error */
1436
0
          if (!HTTP_IS_OK(back[p].r.statuscode)) {
1437
0
            hts_log_print(opt, LOG_DEBUG, "redirect to %s%s", back[p].url_adr,
1438
0
                          back[p].url_fil);
1439
            /* Store only header reference */
1440
0
            cache_mayadd(opt, cache, &back[p].r, back[p].url_adr,
1441
0
                         back[p].url_fil, NULL);
1442
0
          } else {
1443
            /* Partial file, but marked as "ok" ? */
1444
0
            hts_log_print(
1445
0
                opt, LOG_WARNING,
1446
0
                "file with unresolved type not cached (%s (%d), size " LLintP
1447
0
                "): %s%s",
1448
0
                back[p].r.msg, back[p].r.statuscode, (LLint) back[p].r.size,
1449
0
                back[p].url_adr, back[p].url_fil);
1450
0
          }
1451
0
        }
1452
1453
        // status finished callback
1454
0
        RUN_CALLBACK1(opt, xfrstatus, &back[p]);
1455
1456
        // WARC archive of the transaction (request + response/revisit)
1457
0
        if (StringNotEmpty(opt->warc_file))
1458
0
          warc_write_backtransaction(opt, &back[p]);
1459
1460
0
        return 0;
1461
0
      } else {                  // testmode
1462
0
        if (back[p].r.statuscode / 100 >= 3) {  /* Store 3XX, 4XX, 5XX test response codes, but NOT 2XX */
1463
          /* Cache */
1464
0
          cache_mayadd(opt, cache, &back[p].r, back[p].url_adr, back[p].url_fil,
1465
0
                       NULL);
1466
0
        }
1467
0
      }
1468
0
    }
1469
0
  }
1470
  /* Aborted, error, or not ready: url_sav (if written) is broken; restore the
1471
     previous copy from the backup. */
1472
0
  back_finalize_backup(opt, &back[p], HTS_FALSE);
1473
  /* Note the surviving copy, or the end-of-update purge drops what this run
1474
     never managed to replace (#746). */
1475
0
  if (!back[p].testmode && back_transfer_failed(back[p].r.statuscode) &&
1476
0
      back[p].url_sav[0] != '\0' && fexist_utf8(back[p].url_sav)) {
1477
0
    filenote(&opt->state.strc, back[p].url_sav, NULL);
1478
0
    file_notify(opt, back[p].url_adr, back[p].url_fil, back[p].url_sav, 0, 0,
1479
0
                back[p].r.notmodified);
1480
    /* This run stored no entry, and the entry is what names the copy on the
1481
       next run: keep the previous one (#1421). */
1482
0
    cache_keep_previous(opt, cache, back[p].url_adr, back[p].url_fil,
1483
0
                        back[p].url_sav);
1484
0
  }
1485
0
  return -1;
1486
0
}
1487
1488
/* try to keep the connection alive */
1489
int back_letlive(httrackp * opt, cache_back * cache, struct_back * sback,
1490
0
                 const int p) {
1491
0
  lien_back *const back = sback->lnk;
1492
0
  const int back_max = sback->count;
1493
0
  int checkerror;
1494
0
  htsblk *src = &back[p].r;
1495
1496
0
  assertf(p >= 0 && p < back_max);
1497
0
  if (src && !src->is_file && src->soc != INVALID_SOCKET && src->statuscode >= 0        /* no timeout errors & co */
1498
0
      && src->keep_alive_trailers == 0  /* not yet supported (chunk trailers) */
1499
0
      && !(checkerror = check_sockerror(src->soc))
1500
      /*&& !check_sockdata(src->soc) *//* no unexpected data */
1501
0
    ) {
1502
0
    htsblk tmp;
1503
1504
0
    memset(&tmp, 0, sizeof(tmp));
1505
    /* clear everything but connection: switch, close, and reswitch */
1506
0
    back_connxfr(src, &tmp);
1507
0
    back_delete(opt, cache, sback, p);
1508
0
    back_connxfr(&tmp, src);
1509
0
    src->req.flush_garbage = 1; /* ignore CRLF garbage */
1510
0
    return 1;
1511
0
  }
1512
0
  return 0;
1513
0
}
1514
1515
0
void back_connxfr(htsblk * src, htsblk * dst) {
1516
0
  dst->soc = src->soc;
1517
0
  src->soc = INVALID_SOCKET;
1518
#if HTS_USEOPENSSL
1519
  dst->ssl = src->ssl;
1520
  src->ssl = 0;
1521
  dst->ssl_con = src->ssl_con;
1522
  src->ssl_con = NULL;
1523
#endif
1524
0
  dst->keep_alive = src->keep_alive;
1525
0
  src->keep_alive = 0;
1526
0
  dst->keep_alive_max = src->keep_alive_max;
1527
0
  src->keep_alive_max = 0;
1528
0
  dst->keep_alive_t = src->keep_alive_t;
1529
0
  src->keep_alive_t = 0;
1530
0
  dst->debugid = src->debugid;
1531
0
  src->debugid = 0;
1532
0
  dst->address = src->address; // peer IP survives the cache-entry swap (#838)
1533
0
}
1534
1535
/* Release the buffers a response owns. The connection members are left alone:
1536
   back_connxfr() moves those, and the file handles are closed elsewhere. */
1537
0
static void back_free_response(htsblk *r) {
1538
0
  deleteaddr(r);
1539
0
  warc_free_request(r);
1540
0
}
1541
1542
0
void back_move(lien_back * src, lien_back * dst) {
1543
0
  memcpy(dst, src, sizeof(lien_back));
1544
0
  memset(src, 0, sizeof(lien_back));
1545
0
  src->r.soc = INVALID_SOCKET;
1546
0
  src->status = STATUS_FREE;
1547
0
  src->r.location = src->location_buffer;
1548
0
  dst->r.location = dst->location_buffer;
1549
0
}
1550
1551
0
void back_copy_static(const lien_back * src, lien_back * dst) {
1552
0
  memcpy(dst, src, sizeof(lien_back));
1553
0
  dst->r.soc = INVALID_SOCKET;
1554
0
  dst->r.adr = NULL;
1555
0
  dst->r.headers = NULL;
1556
0
  dst->r.warc_reqhdr = NULL;
1557
0
  dst->r.warc_resphdr = NULL;
1558
0
  dst->r.warc_rawpath =
1559
0
      NULL; /* the spool stays owned by src (no double-unlink) */
1560
0
  dst->r.warc_truncated = 0;
1561
0
  dst->r.out = NULL;
1562
0
  dst->r.location = dst->location_buffer;
1563
0
  dst->r.fp = NULL;
1564
#if HTS_USEOPENSSL
1565
  dst->r.ssl_con = NULL;
1566
#endif
1567
0
}
1568
1569
0
static int back_data_serialize(FILE * fp, const void *data, size_t size) {
1570
0
  if (hts_fwrite_exact(&size, sizeof(size), fp) &&
1571
0
      (size == 0 || hts_fwrite_exact(data, size, fp)))
1572
0
    return 0;
1573
0
  return 1;                     /* error */
1574
0
}
1575
1576
0
static int back_string_serialize(FILE * fp, const char *str) {
1577
0
  size_t size = (str != NULL) ? (strlen(str) + 1) : 0;
1578
1579
0
  return back_data_serialize(fp, str, size);
1580
0
}
1581
1582
/* Stores the buffer through a void *, never the caller's own pointer type,
1583
   and sets it on the error paths too so the caller can free it. */
1584
0
static int back_data_unserialize(FILE * fp, void **str, size_t * size) {
1585
0
  *str = NULL;
1586
0
  if (hts_fread_exact(size, sizeof(*size), fp)) {
1587
0
    if (*size == 0)             /* serialized NULL ptr */
1588
0
      return 0;
1589
    /* Untrusted, and the guard byte's extra byte must not wrap the size. */
1590
0
    if (*size > SIZE_MAX - 1)
1591
0
      return 1; /* error */
1592
0
    *str = malloct(*size + 1);
1593
0
    if (*str == NULL)
1594
0
      return 1;                 /* error */
1595
0
    ((char *) *str)[*size] = 0; /* guard byte */
1596
0
    if (hts_fread_exact(*str, *size, fp))
1597
0
      return 0;
1598
0
  }
1599
0
  return 1;                     /* error */
1600
0
}
1601
1602
0
static int back_string_unserialize(FILE * fp, char **str) {
1603
0
  size_t dummy;
1604
0
  void *data;
1605
0
  const int err = back_data_unserialize(fp, &data, &dummy);
1606
1607
0
  *str = (char *) data;
1608
0
  return err;
1609
0
}
1610
1611
/* Spools one slot for back_cleanup_background() to pick up later in the same
1612
   run, so it may hold pointers and the host's own layout. The resume reference
1613
   outlives the process and cannot: see back_serialize_ref() below. */
1614
0
int back_serialize(FILE * fp, const lien_back * src) {
1615
0
  if (back_data_serialize(fp, src, sizeof(lien_back)) == 0
1616
0
      && back_data_serialize(fp, src->r.adr,
1617
0
                             src->r.adr ? (size_t) src->r.size : 0) == 0
1618
0
      && back_string_serialize(fp, src->r.headers) == 0 && fflush(fp) == 0)
1619
0
    return 0;
1620
0
  return 1;
1621
0
}
1622
1623
0
int back_unserialize(FILE * fp, lien_back ** dst) {
1624
0
  size_t size;
1625
0
  void *data;
1626
0
  int err;
1627
1628
0
  *dst = NULL;
1629
0
  errno = 0;
1630
0
  err = back_data_unserialize(fp, &data, &size);
1631
0
  *dst = (lien_back *) data;
1632
0
  if (err == 0 && size == sizeof(lien_back)) {
1633
0
    (*dst)->tmpfile = NULL;
1634
0
    (*dst)->chunk_adr = NULL;
1635
0
    (*dst)->r.adr = NULL;
1636
0
    (*dst)->r.out = NULL;
1637
0
    (*dst)->r.warc_reqhdr = NULL;
1638
0
    (*dst)->r.warc_resphdr = NULL;
1639
0
    (*dst)->r.warc_rawpath = NULL;
1640
0
    (*dst)->r.warc_truncated = 0;
1641
0
    (*dst)->r.location = (*dst)->location_buffer;
1642
0
    (*dst)->r.fp = NULL;
1643
0
    (*dst)->r.soc = INVALID_SOCKET;
1644
#if HTS_USEOPENSSL
1645
    (*dst)->r.ssl_con = NULL;
1646
#endif
1647
0
    {
1648
0
      void *adr;
1649
0
      const int adr_err = back_data_unserialize(fp, &adr, &size);
1650
1651
0
      (*dst)->r.adr = (char *) adr;
1652
0
      if (adr_err == 0) {
1653
        /* A bodyless slot already wrote its bytes to url_sav (FTP, direct to
1654
           disk); zeroing r.size makes the writer blank that file (#797). */
1655
0
        if ((*dst)->r.adr != NULL)
1656
0
          (*dst)->r.size = size;
1657
0
        (*dst)->r.headers = NULL;
1658
0
        if (back_string_unserialize(fp, &(*dst)->r.headers) == 0)
1659
0
          return 0; /* ok */
1660
0
        if ((*dst)->r.headers != NULL)
1661
0
          freet((*dst)->r.headers);
1662
0
      }
1663
0
    }
1664
0
    if ((*dst)->r.adr != NULL)
1665
0
      freet((*dst)->r.adr);
1666
0
  }
1667
0
  if (dst != NULL) {
1668
0
    freet(*dst);
1669
0
    *dst = NULL;
1670
0
  }
1671
0
  return 1;                     /* error */
1672
0
}
1673
1674
/* --- the .ref resume state -------------------------------------------------
1675
1676
   What an interrupted transfer resumes from: where its partial bytes landed,
1677
   and the validators the Range request must carry. Fields go out one by one in
1678
   little-endian fixed widths, because the build that resumes a mirror need not
1679
   be the one, nor on the machine, that interrupted it.
1680
1681
   Up to 3.50.1 the record was a raw lien_back blit, so the magic refuses such a
1682
   file rather than misreading it and the transfer restarts from zero.
1683
1684
   Only what a reader consumes goes out: sockets, file handles, request options
1685
   and scheduling state mean nothing in another process, and come back zeroed.
1686
 */
1687
1688
/* No pre-3.50.2 file can be taken for one of these: it opened with a
1689
   host-native size_t holding sizeof(lien_back), which leaves a zero byte among
1690
   the first eight for every word size and byte order, and the magic has none.
1691
 */
1692
HTS_STATIC_ASSERT(sizeof(lien_back) < 0x10000, ref_magic_unambiguous);
1693
1694
0
static hts_boolean ref_put_u32(FILE *fp, uint32_t v) {
1695
0
  unsigned char b[4];
1696
1697
0
  b[0] = (unsigned char) (v & 0xff);
1698
0
  b[1] = (unsigned char) ((v >> 8) & 0xff);
1699
0
  b[2] = (unsigned char) ((v >> 16) & 0xff);
1700
0
  b[3] = (unsigned char) ((v >> 24) & 0xff);
1701
0
  return hts_fwrite_exact(b, sizeof(b), fp);
1702
0
}
1703
1704
0
static hts_boolean ref_put_u64(FILE *fp, uint64_t v) {
1705
0
  unsigned char b[8];
1706
0
  int i;
1707
1708
0
  for (i = 0; i < 8; i++)
1709
0
    b[i] = (unsigned char) ((v >> (8 * i)) & 0xff);
1710
0
  return hts_fwrite_exact(b, sizeof(b), fp);
1711
0
}
1712
1713
0
static hts_boolean ref_get_u32(FILE *fp, uint32_t *v) {
1714
0
  unsigned char b[4];
1715
1716
0
  if (!hts_fread_exact(b, sizeof(b), fp))
1717
0
    return HTS_FALSE;
1718
0
  *v = (uint32_t) b[0] | ((uint32_t) b[1] << 8) | ((uint32_t) b[2] << 16) |
1719
0
       ((uint32_t) b[3] << 24);
1720
0
  return HTS_TRUE;
1721
0
}
1722
1723
0
static hts_boolean ref_get_u64(FILE *fp, uint64_t *v) {
1724
0
  unsigned char b[8];
1725
0
  int i;
1726
1727
0
  if (!hts_fread_exact(b, sizeof(b), fp))
1728
0
    return HTS_FALSE;
1729
0
  *v = 0;
1730
0
  for (i = 0; i < 8; i++)
1731
0
    *v |= (uint64_t) b[i] << (8 * i);
1732
0
  return HTS_TRUE;
1733
0
}
1734
1735
/* Two's complement both ways, spelled out: a plain cast back is
1736
   implementation-defined above the signed maximum. */
1737
0
static hts_boolean ref_put_int(FILE *fp, int v) {
1738
0
  return ref_put_u32(fp, (uint32_t) v);
1739
0
}
1740
1741
0
static hts_boolean ref_get_int(FILE *fp, int *v) {
1742
0
  uint32_t u;
1743
1744
0
  if (!ref_get_u32(fp, &u))
1745
0
    return HTS_FALSE;
1746
0
  *v = u <= (uint32_t) INT32_MAX ? (int) u : -(int) (UINT32_MAX - u) - 1;
1747
0
  return HTS_TRUE;
1748
0
}
1749
1750
0
static hts_boolean ref_put_llint(FILE *fp, LLint v) {
1751
0
  return ref_put_u64(fp, (uint64_t) v);
1752
0
}
1753
1754
0
static hts_boolean ref_get_llint(FILE *fp, LLint *v) {
1755
0
  uint64_t u;
1756
1757
0
  if (!ref_get_u64(fp, &u))
1758
0
    return HTS_FALSE;
1759
0
  *v =
1760
0
      u <= (uint64_t) INT64_MAX ? (int64_t) u : -(int64_t) (UINT64_MAX - u) - 1;
1761
0
  return HTS_TRUE;
1762
0
}
1763
1764
/* A value outside the field's range is malformed, not something to truncate. */
1765
0
static hts_boolean ref_get_short(FILE *fp, short int *v) {
1766
0
  int i;
1767
1768
0
  if (!ref_get_int(fp, &i) || i < SHRT_MIN || i > SHRT_MAX)
1769
0
    return HTS_FALSE;
1770
0
  *v = (short int) i;
1771
0
  return HTS_TRUE;
1772
0
}
1773
1774
/* A NULL is written like an empty string; only the heap readers below tell the
1775
   two apart, and they are the only fields where the difference matters. */
1776
0
static hts_boolean ref_put_str(FILE *fp, const char *str) {
1777
0
  const size_t len = str != NULL ? strlen(str) : 0;
1778
1779
0
  if (len > HTS_REF_MAX_STR)
1780
0
    return HTS_FALSE;
1781
0
  return ref_put_u32(fp, (uint32_t) len) &&
1782
0
         (len == 0 || hts_fwrite_exact(str, len, fp));
1783
0
}
1784
1785
/* Clips into a fixed destination rather than aborting: the bytes come off a
1786
   file another build, or another machine, wrote. */
1787
0
static hts_boolean ref_get_str(FILE *fp, char *dst, size_t size) {
1788
0
  uint32_t len;
1789
0
  size_t copied = 0;
1790
0
  char chunk[1024];
1791
1792
0
  assertf(size != 0);
1793
0
  dst[0] = '\0';
1794
0
  if (!ref_get_u32(fp, &len) || len > HTS_REF_MAX_STR)
1795
0
    return HTS_FALSE;
1796
0
  while (len != 0) {
1797
0
    const size_t n =
1798
0
        len < (uint32_t) sizeof(chunk) ? (size_t) len : sizeof(chunk);
1799
1800
0
    if (!hts_fread_exact(chunk, n, fp))
1801
0
      return HTS_FALSE;
1802
0
    len -= (uint32_t) n;
1803
0
    if (copied < size - 1) {
1804
0
      const size_t room = size - 1 - copied;
1805
0
      const size_t take = n < room ? n : room;
1806
1807
0
      memcpy(dst + copied, chunk, take);
1808
0
      copied += take;
1809
0
    }
1810
0
  }
1811
0
  dst[copied] = '\0';
1812
0
  return HTS_TRUE;
1813
0
}
1814
1815
0
static hts_boolean ref_get_heapstr(FILE *fp, char **dst) {
1816
0
  uint32_t len;
1817
0
  char *buf;
1818
1819
0
  *dst = NULL;
1820
0
  if (!ref_get_u32(fp, &len) || len > HTS_REF_MAX_STR)
1821
0
    return HTS_FALSE;
1822
0
  if (len == 0) /* a serialized NULL */
1823
0
    return HTS_TRUE;
1824
0
  buf = malloct((size_t) len + 1);
1825
0
  if (buf == NULL)
1826
0
    return HTS_FALSE;
1827
0
  buf[len] = '\0'; /* guard byte */
1828
0
  if (!hts_fread_exact(buf, (size_t) len, fp)) {
1829
0
    freet(buf);
1830
0
    return HTS_FALSE;
1831
0
  }
1832
0
  *dst = buf;
1833
0
  return HTS_TRUE;
1834
0
}
1835
1836
0
static hts_boolean ref_put_blob(FILE *fp, const void *data, uint64_t len) {
1837
0
  if (len > HTS_REF_MAX_BLOB)
1838
0
    return HTS_FALSE;
1839
0
  return ref_put_u64(fp, len) &&
1840
0
         (len == 0 || hts_fwrite_exact(data, (size_t) len, fp));
1841
0
}
1842
1843
0
static hts_boolean ref_get_blob(FILE *fp, char **dst, uint64_t *len) {
1844
0
  uint64_t size;
1845
0
  char *buf;
1846
1847
0
  *dst = NULL;
1848
0
  *len = 0;
1849
0
  if (!ref_get_u64(fp, &size))
1850
0
    return HTS_FALSE;
1851
0
  if (size == 0) /* a serialized NULL */
1852
0
    return HTS_TRUE;
1853
  /* the guard byte must not wrap the allocation on a 32-bit size_t */
1854
0
  if (size > HTS_REF_MAX_BLOB || size > (uint64_t) (SIZE_MAX - 1))
1855
0
    return HTS_FALSE;
1856
0
  buf = malloct((size_t) size + 1);
1857
0
  if (buf == NULL)
1858
0
    return HTS_FALSE;
1859
0
  buf[size] = '\0'; /* guard byte */
1860
0
  if (!hts_fread_exact(buf, (size_t) size, fp)) {
1861
0
    freet(buf);
1862
0
    return HTS_FALSE;
1863
0
  }
1864
0
  *dst = buf;
1865
0
  *len = size;
1866
0
  return HTS_TRUE;
1867
0
}
1868
1869
/* Fields the readers of a reference consume: what identifies the link, where
1870
   its partial bytes are, and the response metadata a resumed request or a
1871
   broken-cache read needs. */
1872
0
static hts_boolean ref_put_record(FILE *fp, const lien_back *src) {
1873
0
  const uint64_t body =
1874
0
      src->r.adr != NULL && src->r.size > 0 ? (uint64_t) src->r.size : 0;
1875
1876
0
  return hts_fwrite_exact(HTS_REF_MAGIC, HTS_REF_MAGIC_SIZE, fp) &&
1877
0
         ref_put_u32(fp, HTS_REF_VERSION) && ref_put_str(fp, src->url_adr) &&
1878
0
         ref_put_str(fp, src->url_fil) && ref_put_str(fp, src->url_sav) &&
1879
0
         ref_put_str(fp, src->referer_adr) &&
1880
0
         ref_put_str(fp, src->referer_fil) &&
1881
0
         ref_put_str(fp, src->r.location) &&
1882
0
         ref_put_int(fp, src->r.statuscode) &&
1883
0
         ref_put_int(fp, src->r.notmodified) &&
1884
0
         ref_put_int(fp, src->r.compressed) && ref_put_int(fp, src->r.empty) &&
1885
0
         ref_put_llint(fp, src->r.size) &&
1886
0
         ref_put_llint(fp, src->r.totalsize) &&
1887
0
         ref_put_llint(fp, src->r.crange) &&
1888
0
         ref_put_llint(fp, src->r.crange_start) &&
1889
0
         ref_put_llint(fp, src->r.crange_end) && ref_put_str(fp, src->r.msg) &&
1890
0
         ref_put_str(fp, src->r.contenttype) &&
1891
0
         ref_put_str(fp, src->r.charset) &&
1892
0
         ref_put_str(fp, src->r.contentencoding) &&
1893
0
         ref_put_str(fp, src->r.lastmodified) && ref_put_str(fp, src->r.etag) &&
1894
0
         ref_put_str(fp, src->r.cdispo) && ref_put_blob(fp, src->r.adr, body) &&
1895
0
         ref_put_str(fp, src->r.headers);
1896
0
}
1897
1898
/* Fills an entry whose in-memory scaffolding the caller already zeroed. */
1899
0
static hts_boolean ref_get_record(FILE *fp, lien_back *dst) {
1900
0
  char magic[HTS_REF_MAGIC_SIZE];
1901
0
  uint32_t version;
1902
0
  uint64_t body;
1903
1904
0
  if (!hts_fread_exact(magic, sizeof(magic), fp) ||
1905
0
      memcmp(magic, HTS_REF_MAGIC, HTS_REF_MAGIC_SIZE) != 0)
1906
0
    return HTS_FALSE; /* a pre-3.50.2 blit, or not a reference */
1907
0
  if (!ref_get_u32(fp, &version) || version != HTS_REF_VERSION)
1908
0
    return HTS_FALSE;
1909
0
  if (!ref_get_str(fp, dst->url_adr, sizeof(dst->url_adr)) ||
1910
0
      !ref_get_str(fp, dst->url_fil, sizeof(dst->url_fil)) ||
1911
0
      !ref_get_str(fp, dst->url_sav, sizeof(dst->url_sav)) ||
1912
0
      !ref_get_str(fp, dst->referer_adr, sizeof(dst->referer_adr)) ||
1913
0
      !ref_get_str(fp, dst->referer_fil, sizeof(dst->referer_fil)) ||
1914
0
      !ref_get_str(fp, dst->location_buffer, sizeof(dst->location_buffer)) ||
1915
0
      !ref_get_int(fp, &dst->r.statuscode) ||
1916
0
      !ref_get_short(fp, &dst->r.notmodified) ||
1917
0
      !ref_get_short(fp, &dst->r.compressed) ||
1918
0
      !ref_get_short(fp, &dst->r.empty) || !ref_get_llint(fp, &dst->r.size) ||
1919
0
      !ref_get_llint(fp, &dst->r.totalsize) ||
1920
0
      !ref_get_llint(fp, &dst->r.crange) ||
1921
0
      !ref_get_llint(fp, &dst->r.crange_start) ||
1922
0
      !ref_get_llint(fp, &dst->r.crange_end) ||
1923
0
      !ref_get_str(fp, dst->r.msg, sizeof(dst->r.msg)) ||
1924
0
      !ref_get_str(fp, dst->r.contenttype, sizeof(dst->r.contenttype)) ||
1925
0
      !ref_get_str(fp, dst->r.charset, sizeof(dst->r.charset)) ||
1926
0
      !ref_get_str(fp, dst->r.contentencoding,
1927
0
                   sizeof(dst->r.contentencoding)) ||
1928
0
      !ref_get_str(fp, dst->r.lastmodified, sizeof(dst->r.lastmodified)) ||
1929
0
      !ref_get_str(fp, dst->r.etag, sizeof(dst->r.etag)) ||
1930
0
      !ref_get_str(fp, dst->r.cdispo, sizeof(dst->r.cdispo)))
1931
0
    return HTS_FALSE;
1932
  /* A resume ref written before the engine refused these, or edited since */
1933
0
  if (!hts_location_is_safe(dst->location_buffer))
1934
0
    dst->location_buffer[0] = '\0';
1935
0
  if (!ref_get_blob(fp, &dst->r.adr, &body))
1936
0
    return HTS_FALSE;
1937
0
  if (!ref_get_heapstr(fp, &dst->r.headers)) {
1938
0
    freet(dst->r.adr);
1939
0
    return HTS_FALSE;
1940
0
  }
1941
  /* A bodyless slot already wrote its bytes to url_sav (FTP, direct to disk);
1942
     zeroing r.size makes the writer blank that file (#797). */
1943
0
  if (dst->r.adr != NULL)
1944
0
    dst->r.size = (LLint) body;
1945
0
  return HTS_TRUE;
1946
0
}
1947
1948
/* Record the state an interrupted transfer resumes from. Not utf-8. */
1949
0
int back_serialize_ref(httrackp * opt, const lien_back * src) {
1950
0
  const char *filename =
1951
0
    url_savename_refname_fullpath(opt, src->url_adr, src->url_fil);
1952
0
  FILE *fp = fopen(filename, "wb");
1953
1954
0
  if (fp == NULL) {
1955
#ifdef _WIN32
1956
    if (mkdir
1957
        (fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log), CACHE_REFNAME))
1958
        == 0)
1959
#else
1960
0
    if (mkdir
1961
0
        (fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log), CACHE_REFNAME),
1962
0
         S_IRWXU | S_IRWXG | S_IRWXO) == 0)
1963
0
#endif
1964
0
    {
1965
      /* note: local filename */
1966
0
      filename = url_savename_refname_fullpath(opt, src->url_adr, src->url_fil);
1967
0
      fp = fopen(filename, "wb");
1968
0
    }
1969
0
  }
1970
0
  if (fp != NULL) {
1971
0
    const int ser = ref_put_record(fp, src) && fflush(fp) == 0 ? 0 : 1;
1972
1973
0
    fclose(fp);
1974
0
    return ser;
1975
0
  }
1976
0
  return 1;
1977
0
}
1978
1979
/* Read one back into a fresh entry the caller owns (back_clear_entry, then
1980
   freet). Anything but a current record is refused, a pre-3.50.2 host-native
1981
   blit included, and *dst stays NULL. */
1982
int back_unserialize_ref(httrackp * opt, const char *adr, const char *fil,
1983
0
                         lien_back ** dst) {
1984
0
  const char *filename = url_savename_refname_fullpath(opt, adr, fil);
1985
0
  FILE *fp = FOPEN(filename, "rb");
1986
0
  lien_back *back;
1987
1988
0
  *dst = NULL;
1989
0
  if (fp == NULL)
1990
0
    return 1;
1991
0
  back = calloct(1, sizeof(lien_back));
1992
0
  if (back == NULL) {
1993
0
    fclose(fp);
1994
0
    return 1;
1995
0
  }
1996
0
  hts_init_htsblk(&back->r);
1997
0
  back->r.location = back->location_buffer;
1998
0
  errno = 0;
1999
0
  if (!ref_get_record(fp, back)) {
2000
0
    hts_log_print(opt, LOG_DEBUG,
2001
0
                  "Ignoring an unreadable resume reference for %s%s", adr, fil);
2002
0
    fclose(fp);
2003
0
    back_clear_entry(back);
2004
0
    freet(back);
2005
0
    return 1;
2006
0
  }
2007
0
  fclose(fp);
2008
0
  *dst = back;
2009
0
  return 0;
2010
0
}
2011
2012
// clear, or leave for keep-alive
2013
int back_maydelete(httrackp * opt, cache_back * cache, struct_back * sback,
2014
0
                   const int p) {
2015
0
  lien_back *const back = sback->lnk;
2016
0
  const int back_max = sback->count;
2017
2018
0
  assertf(p >= 0 && p < back_max);
2019
0
  if (p >= 0 && p < back_max) { // on sait jamais..
2020
0
    if (
2021
         /* Keep-alive authorized by user */
2022
0
         !opt->nokeepalive
2023
         /* Socket currently is keep-alive! */
2024
0
         && back[p].r.keep_alive
2025
         /* Remaining authorized requests */
2026
0
         && back[p].r.keep_alive_max > 1
2027
         /* Known keep-alive start (security) */
2028
0
         && back[p].ka_time_start
2029
         /* We're on time */
2030
0
         && time_local() < back[p].ka_time_start + back[p].r.keep_alive_t
2031
         /* Connection delay must not exceed keep-alive timeout */
2032
0
         && (opt->maxconn <= 0
2033
0
             || (back[p].r.keep_alive_t > (1.0 / opt->maxconn)))
2034
0
      ) {
2035
0
      lien_back tmp;
2036
2037
0
      strcpybuff(tmp.url_adr, back[p].url_adr);
2038
0
      tmp.ka_time_start = back[p].ka_time_start;
2039
0
      if (back_letlive(opt, cache, sback, p)) {
2040
0
        strcpybuff(back[p].url_adr, tmp.url_adr);
2041
0
        back[p].ka_time_start = tmp.ka_time_start;
2042
0
        back[p].status = STATUS_ALIVE;  // alive & waiting
2043
0
        assertf(back[p].ka_time_start != 0);
2044
0
        hts_log_print(opt, LOG_DEBUG,
2045
0
                      "(Keep-Alive): successfully saved #%d (%s)",
2046
0
                      back[p].r.debugid, back[p].url_adr);
2047
0
        return 1;
2048
0
      }
2049
0
    }
2050
0
    back_delete(opt, cache, sback, p);
2051
0
  }
2052
0
  return 0;
2053
0
}
2054
2055
// clear, or leave for keep-alive
2056
void back_maydeletehttp(httrackp * opt, cache_back * cache, struct_back * sback,
2057
0
                        const int p) {
2058
0
  lien_back *const back = sback->lnk;
2059
0
  const int back_max = sback->count;
2060
0
  TStamp lt = 0;
2061
2062
0
  assertf(p >= 0 && p < back_max);
2063
0
  if (back[p].r.soc != INVALID_SOCKET) {
2064
0
    int q;
2065
2066
0
    if (back[p].r.soc != INVALID_SOCKET /* security check */
2067
0
        && back[p].r.statuscode >= 0    /* no timeout errors & co */
2068
0
        && back[p].r.keep_alive_trailers == 0   /* not yet supported (chunk trailers) */
2069
        /* Socket not in I/O error status */
2070
0
        && !back[p].r.is_file && !check_sockerror(back[p].r.soc)
2071
        /* Keep-alive authorized by user */
2072
0
        && !opt->nokeepalive
2073
        /* Socket currently is keep-alive! */
2074
0
        && back[p].r.keep_alive
2075
        /* Remaining authorized requests */
2076
0
        && back[p].r.keep_alive_max > 1
2077
        /* Known keep-alive start (security) */
2078
0
        && back[p].ka_time_start
2079
        /* We're on time */
2080
0
        && (lt = time_local()) < back[p].ka_time_start + back[p].r.keep_alive_t
2081
        /* Connection delay must not exceed keep-alive timeout */
2082
0
        && (opt->maxconn <= 0
2083
0
            || (back[p].r.keep_alive_t > (1.0 / opt->maxconn)))
2084
        /* Available slot in backing */
2085
0
        && (q = back_search(opt, sback)) >= 0) {
2086
0
      lien_back tmp;
2087
2088
0
      strcpybuff(tmp.url_adr, back[p].url_adr);
2089
0
      tmp.ka_time_start = back[p].ka_time_start;
2090
0
      deletehttp(&back[q].r);   // security check
2091
0
      back_connxfr(&back[p].r, &back[q].r);     // transfer live connection settings from p to q
2092
0
      back[q].ka_time_start = back[p].ka_time_start;    // refresh
2093
0
      back[p].r.soc = INVALID_SOCKET;
2094
0
      strcpybuff(back[q].url_adr, tmp.url_adr); // address
2095
0
      back[q].ka_time_start = tmp.ka_time_start;
2096
0
      back[q].status = STATUS_ALIVE;    // alive & waiting
2097
0
      assertf(back[q].ka_time_start != 0);
2098
0
      hts_log_print(opt, LOG_DEBUG,
2099
0
                    "(Keep-Alive): successfully preserved #%d (%s)",
2100
0
                    back[q].r.debugid, back[q].url_adr);
2101
0
    } else {
2102
0
      deletehttp(&back[p].r);
2103
0
      back[p].r.soc = INVALID_SOCKET;
2104
0
    }
2105
0
  }
2106
0
}
2107
2108
/* attempt to attach a live connection to this slot */
2109
int back_trylive(httrackp * opt, cache_back * cache, struct_back * sback,
2110
0
                 const int p) {
2111
0
  lien_back *const back = sback->lnk;
2112
0
  const int back_max = sback->count;
2113
2114
0
  assertf(p >= 0 && p < back_max);
2115
0
  if (p >= 0 && back[p].status != STATUS_ALIVE) {       // we never know..
2116
0
    int i = back_searchlive(opt, sback, back[p].url_adr);       // search slot
2117
2118
0
    if (i >= 0 && i != p) {
2119
0
      deletehttp(&back[p].r);   // security check
2120
0
      back_connxfr(&back[i].r, &back[p].r);     // transfer live connection settings from i to p
2121
0
      back[p].ka_time_start = back[i].ka_time_start;
2122
0
      back_delete(opt, cache, sback, i);        // delete old slot
2123
0
      back[p].status = STATUS_CONNECTING;       // ready to connect
2124
0
      return 1;                 // success: will reuse live connection
2125
0
    }
2126
0
  }
2127
0
  return 0;
2128
0
}
2129
2130
/* search for a live position, or, if not possible, try to return a new one */
2131
0
int back_searchlive(httrackp * opt, struct_back * sback, const char *search_addr) {
2132
0
  lien_back *const back = sback->lnk;
2133
0
  const int back_max = sback->count;
2134
0
  int i;
2135
2136
  /* search for a live socket */
2137
0
  for(i = 0; i < back_max; i++) {
2138
0
    if (back[i].status == STATUS_ALIVE) {
2139
0
      if (strfield2(back[i].url_adr, search_addr)) {    /* same location (xxc: check also virtual hosts?) */
2140
0
        if (time_local() < back[i].ka_time_start + back[i].r.keep_alive_t) {
2141
0
          return i;
2142
0
        }
2143
0
      }
2144
0
    }
2145
0
  }
2146
0
  return -1;
2147
0
}
2148
2149
0
int back_search_quick(struct_back * sback) {
2150
0
  lien_back *const back = sback->lnk;
2151
0
  const int back_max = sback->count;
2152
0
  int i;
2153
2154
  /* try to find an empty place */
2155
0
  for(i = 0; i < back_max; i++) {
2156
0
    if (back[i].status == STATUS_FREE) {
2157
0
      return i;
2158
0
    }
2159
0
  }
2160
2161
  /* oops, can't find a place */
2162
0
  return -1;
2163
0
}
2164
2165
0
int back_search(httrackp * opt, struct_back * sback) {
2166
0
  lien_back *const back = sback->lnk;
2167
0
  const int back_max = sback->count;
2168
0
  int i;
2169
2170
  /* try to find an empty place */
2171
0
  if ((i = back_search_quick(sback)) != -1)
2172
0
    return i;
2173
2174
  /* couldn't find an empty place, try to requisition a keep-alive place */
2175
0
  for(i = 0; i < back_max; i++) {
2176
0
    if (back[i].status == STATUS_ALIVE) {
2177
0
      lien_back *const back = sback->lnk;
2178
2179
      /* close this place */
2180
0
      back_clear_entry(&back[i]);       /* Already finalized (this is the night of the living dead) */
2181
      /*back_delete(opt,cache,sback, i); */
2182
0
      return i;
2183
0
    }
2184
0
  }
2185
2186
  /* oops, can't find a place */
2187
0
  return -1;
2188
0
}
2189
2190
0
void back_set_finished(httrackp *opt, struct_back *sback, const int p) {
2191
0
  lien_back *const back = sback->lnk;
2192
0
  const int back_max = sback->count;
2193
2194
0
  assertf(p >= 0 && p < back_max);
2195
0
  if (p >= 0 && p < sback->count) {     // we never know..
2196
    /* status: finished (waiting to be validated) */
2197
0
    back[p].status = STATUS_READY;      /* finished */
2198
    /* close open r/w streams, if any */
2199
0
    if (back[p].r.fp != NULL) {
2200
0
      fclose(back[p].r.fp);
2201
0
      back[p].r.fp = NULL;
2202
0
    }
2203
0
    if (back[p].r.out != NULL) {        // fermer fichier sortie
2204
0
      const hts_boolean closed = fclose(back[p].r.out) == 0;
2205
2206
0
      back[p].r.out = NULL;
2207
0
      if (!closed)
2208
0
        back_report_write_failure(opt, &back[p]);
2209
0
    }
2210
0
  }
2211
0
}
2212
2213
0
void back_set_locked(struct_back * sback, const int p) {
2214
0
  lien_back *const back = sback->lnk;
2215
0
  const int back_max = sback->count;
2216
2217
0
  assertf(p >= 0 && p < back_max);
2218
0
  if (p >= 0 && p < sback->count) {
2219
    /* status: locked (in process, do not swap on disk) */
2220
0
    back[p].locked = 1;         /* locked */
2221
0
  }
2222
0
}
2223
2224
0
void back_set_unlocked(struct_back * sback, const int p) {
2225
0
  lien_back *const back = sback->lnk;
2226
0
  const int back_max = sback->count;
2227
2228
0
  assertf(p >= 0 && p < back_max);
2229
0
  if (p >= 0 && p < sback->count) {
2230
    /* status: unlocked (can be swapped on disk) */
2231
0
    back[p].locked = 0;         /* unlocked */
2232
0
  }
2233
0
}
2234
2235
int back_flush_output(httrackp * opt, cache_back * cache, struct_back * sback,
2236
0
                      const int p) {
2237
0
  lien_back *const back = sback->lnk;
2238
0
  const int back_max = sback->count;
2239
2240
0
  assertf(p >= 0 && p < back_max);
2241
0
  if (p >= 0 && p < sback->count) {     // on sait jamais..
2242
    /* close input file */
2243
0
    if (back[p].r.fp != NULL) {
2244
0
      fclose(back[p].r.fp);
2245
0
      back[p].r.fp = NULL;
2246
0
    }
2247
    /* fichier de sortie */
2248
0
    if (back[p].r.out != NULL) {        // fermer fichier sortie
2249
0
      const hts_boolean closed = fclose(back[p].r.out) == 0;
2250
2251
0
      back[p].r.out = NULL;
2252
0
      if (!closed)
2253
0
        back_report_write_failure(opt, &back[p]);
2254
0
    }
2255
    /* set file time */
2256
0
    if (back[p].r.is_write) {   // ecriture directe
2257
      /* écrire date "remote" */
2258
0
      if (strnotempty(back[p].url_sav)
2259
0
          && strnotempty(back[p].r.lastmodified)
2260
0
          && fexist_utf8(back[p].url_sav))      // normalement existe si on a un fichier de sortie
2261
0
      {
2262
0
        set_filetime_rfc822(back[p].url_sav, back[p].r.lastmodified);
2263
0
      }
2264
      /* executer commande utilisateur après chargement du fichier */
2265
      //xx usercommand(opt,0,NULL,back[p].url_sav, back[p].url_adr, back[p].url_fil);
2266
0
      back[p].r.is_write = 0;
2267
0
    }
2268
0
    return 1;
2269
0
  }
2270
0
  return 0;
2271
0
}
2272
2273
/* Move a still-writing .delayed placeholder to its final name (#483). */
2274
hts_boolean back_delayed_rename(httrackp *opt, lien_back *back,
2275
0
                                const char *newname) {
2276
0
  hts_boolean renamed;
2277
2278
0
  if (!back->r.is_write || back->tmpfile != NULL ||
2279
0
      !IS_DELAYED_EXT(back->url_sav) || strcmp(back->url_sav, newname) == 0)
2280
0
    return HTS_TRUE; /* nothing bound to the placeholder name */
2281
0
  if (back->r.out != NULL) {
2282
0
    fclose(back->r.out);
2283
0
    back->r.out = NULL;
2284
0
  }
2285
0
  renamed = RENAME(back->url_sav, newname) == 0 ? HTS_TRUE : HTS_FALSE;
2286
0
  if (renamed && (back->status == STATUS_READY ||
2287
0
                  (back->r.out = FOPEN(newname, "ab")) != NULL)) {
2288
0
    filenote(&opt->state.strc, newname, NULL);
2289
0
    hts_log_print(opt, LOG_DEBUG, "moved placeholder %s to %s", back->url_sav,
2290
0
                  newname);
2291
0
    return HTS_TRUE;
2292
0
  }
2293
  /* partial lost: drop only what we own (Windows rename won't overwrite) */
2294
0
  hts_log_print(opt, LOG_WARNING | LOG_ERRNO, "unable to move %s to %s",
2295
0
                back->url_sav, newname);
2296
0
  back->r.statuscode = STATUSCODE_INVALID;
2297
0
  strcpybuff(back->r.msg, "Write error on disk");
2298
0
  back->r.is_write = 0;
2299
0
  (void) UNLINK(renamed ? newname : back->url_sav);
2300
0
  return HTS_FALSE;
2301
0
}
2302
2303
// effacer entrée
2304
/* Discard a cancelled mid-write .delayed placeholder (unusable across runs). */
2305
0
void back_delayed_discard(httrackp *opt, lien_back *back) {
2306
0
  if (back->r.out != NULL) {
2307
0
    fclose(back->r.out);
2308
0
    back->r.out = NULL;
2309
0
  }
2310
0
  back->r.is_write = 0;
2311
0
  if (opt != NULL)
2312
0
    url_savename_refname_remove(opt, back->url_adr, back->url_fil);
2313
0
  (void) UNLINK(back->url_sav);
2314
0
}
2315
2316
int back_delete(httrackp * opt, cache_back * cache, struct_back * sback,
2317
0
                const int p) {
2318
0
  lien_back *const back = sback->lnk;
2319
0
  const int back_max = sback->count;
2320
2321
0
  assertf(p >= 0 && p < back_max);
2322
0
  if (p >= 0 && p < sback->count) {     // on sait jamais..
2323
    /* mid-write cancel: drop a .delayed placeholder; real-named partials
2324
       survive for resume (--continue) */
2325
0
    if (back[p].r.is_write && IS_DELAYED_EXT(back[p].url_sav) &&
2326
0
        (back[p].status != STATUS_READY || back[p].r.statuscode <= 0)) {
2327
0
      back_delayed_discard(opt, &back[p]);
2328
0
    }
2329
    // Vérificateur d'intégrité
2330
#if DEBUG_CHECKINT
2331
    _CHECKINT(&back[p], "Appel back_delete")
2332
#endif
2333
#if HTS_DEBUG_CLOSESOCK
2334
      DEBUG_W("back_delete: #%d\n" _(int) p);
2335
#endif
2336
2337
    // Finalize
2338
0
    if (!back[p].finalized) {
2339
0
      if ((back[p].status == STATUS_READY)      // ready
2340
0
          && (!back[p].testmode)        // not test mode
2341
0
          && (back[p].r.statuscode > 0) // not internal error
2342
0
        ) {
2343
0
        hts_log_print(opt, LOG_DEBUG,
2344
0
                      "File '%s%s' -> %s not yet saved in cache - saving now",
2345
0
                      back[p].url_adr, back[p].url_fil, back[p].url_sav);
2346
0
      }
2347
0
      if (cache != NULL) {
2348
0
        back_finalize(opt, cache, sback, p);
2349
0
      }
2350
0
    }
2351
0
    back[p].finalized = 0;
2352
2353
    // flush output buffers
2354
0
    (void) back_flush_output(opt, cache, sback, p);
2355
2356
0
    return back_clear_entry(&back[p]);
2357
0
  }
2358
0
  return 0;
2359
0
}
2360
2361
/* the entry is available again */
2362
0
static void back_set_free(lien_back * back) {
2363
0
  back->locked = 0;
2364
0
  back->status = STATUS_FREE;
2365
0
}
2366
2367
/* delete entry content (clear the entry), but don't unallocate the entry itself */
2368
0
int back_clear_entry(lien_back * back) {
2369
0
  if (back != NULL) {
2370
    // Libérer tous les sockets, handles, buffers..
2371
0
    if (back->r.soc != INVALID_SOCKET) {
2372
#if HTS_DEBUG_CLOSESOCK
2373
      DEBUG_W("back_delete: deletehttp\n");
2374
#endif
2375
0
      deletehttp(&back->r);
2376
0
      back->r.soc = INVALID_SOCKET;
2377
0
    }
2378
2379
0
    back_free_response(&back->r);
2380
0
    if (back->chunk_adr != NULL) {      // reste un bloc à désallouer
2381
0
      freet(back->chunk_adr);
2382
0
      back->chunk_adr = NULL;
2383
0
      back->chunk_size = 0;
2384
0
      back->chunk_blocksize = 0;
2385
0
      back->is_chunk = 0;
2386
0
    }
2387
    // only for security
2388
0
    if (back->tmpfile && back->tmpfile[0] != '\0') {
2389
0
      (void) unlink(back->tmpfile);
2390
0
      back_tmpdir_drop(back->tmpfile);
2391
0
      back->tmpfile = NULL;
2392
0
    }
2393
    // Tout nettoyer
2394
0
    memset(back, 0, sizeof(lien_back));
2395
0
    back->r.soc = INVALID_SOCKET;
2396
0
    back->r.location = back->location_buffer;
2397
2398
    // Le plus important: libérer le champ
2399
0
    back_set_free(back);
2400
2401
0
    return 1;
2402
0
  }
2403
0
  return 0;
2404
0
}
2405
2406
/* Space left on backing stack */
2407
0
int back_stack_available(struct_back * sback) {
2408
0
  lien_back *const back = sback->lnk;
2409
0
  const int back_max = sback->count;
2410
0
  int p = 0, n = 0;
2411
2412
0
  for(; p < back_max; p++)
2413
0
    if (back[p].status == STATUS_FREE)
2414
0
      n++;
2415
0
  return n;
2416
0
}
2417
2418
// ajouter un lien en backing
2419
int back_add_if_not_exists(struct_back * sback, httrackp * opt,
2420
                           cache_back * cache, const char *adr, const char *fil, const char *save,
2421
0
                           const char *referer_adr, const char *referer_fil, int test) {
2422
0
  back_clean(opt, cache, sback);        /* first cleanup the backlog to ensure that we have some entry left */
2423
0
  if (!back_exist(sback, opt, adr, fil, save)) {
2424
0
    return back_add(sback, opt, cache, adr, fil, save, referer_adr, referer_fil,
2425
0
                    test, HTS_FALSE);
2426
0
  }
2427
0
  return 0;
2428
0
}
2429
2430
int back_add(struct_back *sback, httrackp *opt, cache_back *cache,
2431
             const char *adr, const char *fil, const char *save,
2432
             const char *referer_adr, const char *referer_fil, int test,
2433
0
             hts_boolean refetch_whole) {
2434
0
  lien_back *const back = sback->lnk;
2435
0
  const int back_max = sback->count;
2436
0
  int p = 0;
2437
0
  char catbuff[CATBUFF_SIZE];
2438
0
  char catbuff2[CATBUFF_SIZE];
2439
0
  lien_back *itemback = NULL;
2440
2441
#if (defined(_DEBUG) || defined(DEBUG))
2442
  if (!test && back_exist(sback, opt, adr, fil, save)) {
2443
    int already_there = 0;
2444
2445
    hts_log_print(opt, LOG_ERROR, "error: back_add(%s,%s,%s) duplicate", adr,
2446
                  fil, save);
2447
  }
2448
#endif
2449
2450
  // vérifier cohérence de adr et fil (non vide!)
2451
0
  if (strnotempty(adr) == 0) {
2452
0
    hts_log_print(opt, LOG_WARNING, "error: adr is empty for back_add");
2453
0
    return -1;                  // erreur!
2454
0
  }
2455
0
  if (strnotempty(fil) == 0) {
2456
0
    hts_log_print(opt, LOG_WARNING, "error: fil is empty for back_add");
2457
0
    return -1;                  // erreur!
2458
0
  }
2459
  // FIN vérifier cohérence de adr et fil (non vide!)
2460
2461
  // stats
2462
0
  opt->state.back_add_stats++;
2463
2464
  // rechercher emplacement
2465
0
  back_clean(opt, cache, sback);
2466
0
  if ((p = back_search(opt, sback)) >= 0) {
2467
0
    back[p].send_too[0] = '\0'; // éventuels paramètres supplémentaires à transmettre au serveur
2468
2469
    // clear r
2470
0
    if (back[p].r.soc != INVALID_SOCKET) {      /* we never know */
2471
0
      deletehttp(&back[p].r);
2472
0
    }
2473
0
    hts_init_htsblk(&back[p].r);
2474
0
    back[p].r.location = back[p].location_buffer;
2475
2476
    // créer entrée
2477
0
    strcpybuff(back[p].url_adr, adr);
2478
0
    strcpybuff(back[p].url_fil, fil);
2479
0
    strcpybuff(back[p].url_sav, save);
2480
    // copier referer si besoin
2481
0
    strcpybuff(back[p].referer_adr, "");
2482
0
    strcpybuff(back[p].referer_fil, "");
2483
0
    if ((referer_adr) && (referer_fil)) {       // existe
2484
0
      if ((strnotempty(referer_adr)) && (strnotempty(referer_fil))) {   // non vide
2485
0
        if (referer_adr[0] != '!') {    // non détruit
2486
0
          if (strcmp(referer_adr, "file://")) { // PAS file://
2487
0
            if (strcmp(referer_adr, "primary")) {       // pas referer 1er lien
2488
0
              strcpybuff(back[p].referer_adr, referer_adr);
2489
0
              strcpybuff(back[p].referer_fil, referer_fil);
2490
0
            }
2491
0
          }
2492
0
        }
2493
0
      }
2494
0
    }
2495
    // sav ne sert à rien pour le moment
2496
0
    back[p].r.size = 0;         // rien n'a encore été chargé
2497
0
    back[p].r.adr = NULL;       // pas de bloc de mémoire
2498
0
    back[p].r.is_write = 0;     // à priori stockage en mémoire
2499
0
    back[p].maxfile_html = opt->maxfile_html;
2500
0
    back[p].maxfile_nonhtml = opt->maxfile_nonhtml;
2501
0
    back[p].testmode = test;    // mode test?
2502
0
    if (!opt->http10)           // option "forcer 1.0" désactivée
2503
0
      back[p].http11 = 1;       // autoriser http/1.1
2504
0
    back[p].head_request = 0;
2505
0
    if (strcmp(back[p].url_sav, BACK_ADD_TEST) == 0)    // HEAD
2506
0
      back[p].head_request = 1;
2507
0
    else if (strcmp(back[p].url_sav, BACK_ADD_TEST2) == 0)      // test en GET
2508
0
      back[p].head_request = 2; // test en get
2509
2510
    /* Forced whole refetch (#581): drop the stale temp-ref and skip the resume
2511
       branches below, so a surviving partial can't Range-loop. */
2512
0
    if (refetch_whole) {
2513
0
      url_savename_refname_remove(opt, adr, fil);
2514
0
    }
2515
2516
    /* Stop requested - abort backing */
2517
    /* For update mode: second check after cache lookup not to lose all previous cache data ! */
2518
0
    if (opt->state.stop && !opt->is_update) {
2519
0
      back[p].r.statuscode = STATUSCODE_INVALID;        // fatal
2520
0
      strcpybuff(back[p].r.msg, "mirror stopped by user");
2521
0
      back[p].status = STATUS_READY;    // terminé
2522
0
      back_set_finished(opt, sback, p);
2523
0
      hts_log_print(opt, LOG_WARNING,
2524
0
                    "File not added due to mirror cancel: %s%s", adr, fil);
2525
0
      return 0;
2526
0
    }
2527
    // test "fast header" cache ; that is, tests we did that lead to 3XX/4XX/5XX response codes
2528
0
    if (cache->cached_tests != NULL) {
2529
0
      intptr_t ptr = 0;
2530
2531
0
      if (coucal_read(cache->cached_tests,
2532
0
        concat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), adr, fil), &ptr)) {       // gotcha
2533
0
        if (ptr != 0) {
2534
0
          char *text = (char *) ptr;
2535
0
          char *lf = strchr(text, '\n');
2536
0
          int code = 0;
2537
2538
0
          if (sscanf(text, "%d", &code) == 1) { // got code
2539
0
            back[p].r.statuscode = code;
2540
0
            back[p].status = STATUS_READY;      // done
2541
0
            if (lf != NULL && *lf != '\0') {    // got location ?
2542
              // r.location aliases location_buffer (set above); write the array
2543
              // so the bounded macro picks up its capacity.
2544
0
              strcpybuff(back[p].location_buffer, lf + 1);
2545
0
            }
2546
0
            return 0;
2547
0
          }
2548
0
        }
2549
0
      }
2550
0
    }
2551
    // tester cache
2552
0
    if ((strcmp(adr, "file://")) /* pas fichier */
2553
0
        && ((!test) ||
2554
0
            (cache->type == 1)) /* cache prioritaire, laisser passer en test! */
2555
0
        &&
2556
0
        ((strnotempty(save)) || (strcmp(fil, "/robots.txt") ==
2557
0
                                 0))) { // si en test on ne doit pas utiliser le
2558
                                        // cache sinon telescopage avec le 302..
2559
0
      intptr_t hash_pos;
2560
0
      int hash_pos_return = 0;
2561
2562
0
      if (cache->hashtable) {
2563
0
        char BIGSTK buff[CACHE_KEY_SIZE];
2564
0
        size_t used = 0;
2565
2566
        /* a key too long to be in the table is a miss, not a fatal error */
2567
0
        if (slcatprintfbuff(buff, sizeof(buff), &used, "%s%s", adr, fil)) {
2568
0
          hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos);
2569
0
        }
2570
2571
        // negative values when data is not in cache
2572
0
        if (hash_pos_return < 0) {
2573
0
          if (!test) { // not test mode
2574
            /* note: no check with IS_DELAYED_EXT() enabled - postcheck by
2575
             * client please! */
2576
0
            if (save[0] != '\0' && !IS_DELAYED_EXT(save) &&
2577
0
                fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <=
2578
0
                    0) { // final file missing or empty
2579
0
              int found = 0;
2580
2581
              /* It is possible that the file has been moved due to changes in
2582
               * build structure */
2583
0
              {
2584
0
                char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
2585
0
                htsblk r;
2586
2587
0
                previous_save[0] = '\0';
2588
0
                r = cache_readex(opt, cache, adr, fil, /*head */ NULL,
2589
                                 /*bound to back[p] (temporary) */
2590
0
                                 back[p].location_buffer, previous_save, /*ro */
2591
0
                                 1);
2592
                /* Is supposed to be on disk only */
2593
0
                if (r.is_write && previous_save[0] != '\0') {
2594
                  /* Exists, but with another (old) filename: rename (almost)
2595
                   * silently */
2596
0
                  if (strcmp(previous_save, save) != 0 &&
2597
0
                      fexist_utf8(
2598
0
                          fconv(catbuff, sizeof(catbuff), previous_save))) {
2599
0
                    rename(fconv(catbuff, sizeof(catbuff), previous_save),
2600
0
                           fconv(catbuff2, sizeof(catbuff2), save));
2601
0
                    if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
2602
0
                      found = 1;
2603
0
                      hts_log_print(opt, LOG_DEBUG,
2604
0
                                    "File '%s' has been renamed since last "
2605
0
                                    "mirror to '%s' ; applying changes",
2606
0
                                    previous_save, save);
2607
0
                    } else {
2608
0
                      hts_log_print(opt, LOG_ERROR,
2609
0
                                    "Could not rename '%s' to '%s' ; will have "
2610
0
                                    "to retransfer it",
2611
0
                                    previous_save, save);
2612
0
                    }
2613
0
                  }
2614
0
                }
2615
0
                back[p].location_buffer[0] = '\0';
2616
0
              }
2617
2618
              /* Not found ? */
2619
0
              if (!found) {
2620
                // invalidate: gone from disk, force a refetch
2621
0
                hash_pos_return = 0;
2622
0
                if (opt->norecatch) {
2623
0
                  if (!fexist_utf8(fconv(
2624
0
                          catbuff, sizeof(catbuff),
2625
0
                          save))) { // declared but missing: user erased it
2626
0
                    FILE *fp =
2627
0
                        FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb");
2628
2629
0
                    if (fp)
2630
0
                      fclose(fp);
2631
0
                    hts_log_print(opt, LOG_WARNING,
2632
0
                                  "Previous file '%s' not found (erased by "
2633
0
                                  "user ?), ignoring: %s%s",
2634
0
                                  save, back[p].url_adr, back[p].url_fil);
2635
0
                  }
2636
0
                } else {
2637
0
                  hts_log_print(opt, LOG_WARNING,
2638
0
                                "Previous file '%s' not found (erased by user "
2639
0
                                "?), recatching: %s%s",
2640
0
                                save, back[p].url_adr, back[p].url_fil);
2641
0
                }
2642
0
              }
2643
0
            } // fsize() <= 0
2644
0
          }
2645
0
        }
2646
        //
2647
0
      } else {
2648
0
        hash_pos_return = 0;
2649
0
      }
2650
2651
0
      if (hash_pos_return) { // in cache, with data
2652
0
        const int cache_is_prioritary = cache->type == 1
2653
0
          || opt->state.stop != 0;
2654
0
        if (cache_is_prioritary) {      // cache prioritaire (pas de test if-modified..)
2655
          // dans ce cas on peut également lire des réponses cachées comme 404,302...
2656
          // lire dans le cache
2657
0
          if (!test)
2658
0
            back[p].r =
2659
0
              cache_read(opt, cache, adr, fil, save, back[p].location_buffer);
2660
0
          else
2661
0
            back[p].r = cache_read(opt, cache, adr, fil, NULL, back[p].location_buffer);        // charger en tête uniquement du cache
2662
2663
          /* ensure correct location buffer set */
2664
0
          back[p].r.location = back[p].location_buffer;
2665
2666
          /* Interdiction taille par le wizard? --> détruire */
2667
0
          if (back[p].r.statuscode != -1) {     // pas d'erreur de lecture
2668
0
            if (!back_checksize(opt, &back[p], 0)) {
2669
0
              back[p].status = STATUS_READY;    // FINI
2670
0
              back_set_finished(opt, sback, p);
2671
0
              back[p].r.statuscode = STATUSCODE_TOO_BIG;
2672
0
              if (!back[p].testmode)
2673
0
                strcpybuff(back[p].r.msg, "Cached file skipped (too big)");
2674
0
              else
2675
0
                strcpybuff(back[p].r.msg,
2676
0
                           "Test: Cached file skipped  (too big)");
2677
0
              return 0;
2678
0
            }
2679
0
          }
2680
2681
0
          if (back[p].r.statuscode != -1 || IS_DELAYED_EXT(save)) {     // pas d'erreur de lecture ou test retardé
2682
0
            if (!test) {
2683
0
              hts_log_print(opt, LOG_DEBUG,
2684
0
                            "File immediately loaded from cache: %s%s",
2685
0
                            back[p].url_adr, back[p].url_fil);
2686
0
            } else {
2687
0
              hts_log_print(opt, LOG_DEBUG,
2688
0
                            "File immediately tested from cache: %s%s",
2689
0
                            back[p].url_adr, back[p].url_fil);
2690
0
            }
2691
0
            back[p].r.notmodified = 1;  // fichier non modifié
2692
            // no request was sent at all, so this is never a server 304 (#839)
2693
0
            back[p].r.warc_forced_notmodified = HTS_TRUE;
2694
0
            back[p].status = STATUS_READY; // OK prêt
2695
0
            back_set_finished(opt, sback, p);
2696
2697
            // finalize transfer
2698
0
            if (!test) {
2699
0
              if (back[p].r.statuscode > 0) {
2700
0
                hts_log_print(opt, LOG_TRACE, "finalizing in back_add");
2701
0
                back_finalize(opt, cache, sback, p);
2702
0
              }
2703
0
            }
2704
2705
0
            return 0;
2706
0
          } else {              // erreur
2707
            // effacer r
2708
0
            hts_init_htsblk(&back[p].r);
2709
0
            back[p].r.location = back[p].location_buffer;
2710
            // et continuer (chercher le fichier)
2711
0
          }
2712
2713
0
        } else if (cache->type == 2) {  // si en cache, demander de tester If-Modified-Since
2714
0
          htsblk r;
2715
2716
0
          cache_header(opt, cache, adr, fil, &r);
2717
2718
          /* Interdiction taille par le wizard? */
2719
0
          {
2720
0
            LLint save_totalsize = back[p].r.totalsize;
2721
2722
0
            back[p].r.totalsize = r.totalsize;
2723
0
            if (!back_checksize(opt, &back[p], 1)) {
2724
0
              r.statuscode = STATUSCODE_INVALID;
2725
              //
2726
0
              back[p].status = STATUS_READY;    // FINI
2727
0
              back_set_finished(opt, sback, p);
2728
0
              back[p].r.statuscode = STATUSCODE_TOO_BIG;
2729
0
              deletehttp(&back[p].r);
2730
0
              back[p].r.soc = INVALID_SOCKET;
2731
0
              if (!back[p].testmode)
2732
0
                strcpybuff(back[p].r.msg, "File too big");
2733
0
              else
2734
0
                strcpybuff(back[p].r.msg, "Test: File too big");
2735
0
              return 0;
2736
0
            }
2737
0
            back[p].r.totalsize = save_totalsize;
2738
0
          }
2739
2740
0
          if (r.statuscode != -1) {
2741
0
            if (r.statuscode == HTTP_OK) {      // uniquement des 200 (OK)
2742
0
              if (strnotempty(r.etag)) {        // ETag (RFC2616)
2743
                /*
2744
                   - If both an entity tag and a Last-Modified value have been
2745
                   provided by the origin server, SHOULD use both validators in
2746
                   cache-conditional requests. This allows both HTTP/1.0 and
2747
                   HTTP/1.1 caches to respond appropriately.
2748
                 */
2749
0
                if (strnotempty(r.lastmodified))
2750
0
                  sprintf(back[p].send_too,
2751
0
                          "If-None-Match: %s\r\nIf-Modified-Since: %s\r\n",
2752
0
                          r.etag, r.lastmodified);
2753
0
                else
2754
0
                  sprintf(back[p].send_too, "If-None-Match: %s\r\n", r.etag);
2755
0
              } else if (strnotempty(r.lastmodified))
2756
0
                sprintf(back[p].send_too, "If-Modified-Since: %s\r\n",
2757
0
                        r.lastmodified);
2758
0
              else if (strnotempty(cache->lastmodified))
2759
0
                sprintf(back[p].send_too, "If-Modified-Since: %s\r\n",
2760
0
                        cache->lastmodified);
2761
2762
              /* this is an update of a file */
2763
0
              if (strnotempty(back[p].send_too))
2764
0
                back[p].is_update = 1;
2765
0
              back[p].r.req.nocompression = 1;  /* Do not compress when updating! */
2766
2767
0
            }
2768
0
          }
2769
#if DEBUGCA
2770
          printf("..is modified test %s\n", back[p].send_too);
2771
#endif
2772
0
        }
2773
0
      }
2774
      /* Not in cache ; maybe in temporary cache ? Warning: non-movable
2775
         "url_sav" (skipped on a forced whole refetch, #581) */
2776
0
      else if (!refetch_whole &&
2777
0
               back_unserialize_ref(opt, adr, fil, &itemback) == 0) {
2778
0
        const LLint file_size = fsize_utf8(itemback->url_sav);
2779
2780
        /* Found file on disk */
2781
0
        if (file_size > 0) {
2782
0
          char *send_too = back[p].send_too;
2783
2784
0
          sprintf(send_too, "Range: bytes=" LLintP "-\r\n", (LLint) file_size);
2785
0
          send_too += strlen(send_too);
2786
          /* add etag information */
2787
0
          if (strnotempty(itemback->r.etag)) {
2788
0
            sprintf(send_too, "If-Match: %s\r\n", itemback->r.etag);
2789
0
            send_too += strlen(send_too);
2790
0
          }
2791
          /* add date information */
2792
0
          if (strnotempty(itemback->r.lastmodified)) {
2793
0
            sprintf(send_too, "If-Unmodified-Since: %s\r\n",
2794
0
                    itemback->r.lastmodified);
2795
0
            send_too += strlen(send_too);
2796
0
          }
2797
0
          back[p].http11 = 1;   /* 1.1 */
2798
0
          back[p].range_req_size = (LLint) file_size;
2799
0
          back[p].r.req.range_used = 1;
2800
0
          back[p].is_update = 1;        /* this is an update of a file */
2801
0
          back[p].r.req.nocompression = 1;      /* Do not compress when updating! */
2802
0
        } else {
2803
          /* broken ; remove */
2804
0
          url_savename_refname_remove(opt, adr, fil);
2805
0
        }
2806
        /* cleanup */
2807
0
        back_clear_entry(itemback);     /* delete entry content */
2808
0
        freet(itemback);        /* delete item */
2809
0
        itemback = NULL;
2810
0
      }
2811
      /* Not in cache or temporary cache ; found on disk ? (hack)
2812
         (skipped on a forced whole refetch, #581) */
2813
0
      else if (!refetch_whole && fexist_utf8(save)) {
2814
0
        const LLint sz = fsize_utf8(save);
2815
2816
        // Bon, là il est possible que le fichier ait été partiellement transféré
2817
        // (s'il l'avait été en totalité il aurait été inscrit dans le cache ET existerait sur disque)
2818
        // PAS de If-Modified-Since, on a pas connaissance des données à la date du cache
2819
        // On demande juste les données restantes si le date est valide (206), tout sinon (200)
2820
0
        if ((ishtml(opt, save) != 1) && (ishtml(opt, back[p].url_fil) != 1)) {  // NON HTML (liens changés!!)
2821
0
          if (sz > 0) {         // Fichier non vide? (question bête, sinon on transfert tout!)
2822
0
            char lastmodified[256];
2823
2824
0
            get_filetime_rfc822(save, lastmodified);
2825
0
            if (strnotempty(lastmodified)) {    /* pas de If-.. possible */
2826
#if DEBUGCA
2827
              printf("..if unmodified since %s size " LLintP "\n", lastmodified,
2828
                     (LLint) sz);
2829
#endif
2830
0
              hts_log_print(opt, LOG_DEBUG,
2831
0
                            "File partially present (" LLintP " bytes): %s%s",
2832
0
                            (LLint) sz, back[p].url_adr, back[p].url_fil);
2833
2834
              /* impossible - don't have etag or date
2835
                 if (strnotempty(back[p].r.etag)) {  // ETag (RFC2616)
2836
                 sprintf(back[p].send_too,"If-None-Match: %s\r\n",back[p].r.etag);
2837
                 back[p].http11=1;    // En tête 1.1
2838
                 } else if (strnotempty(back[p].r.lastmodified)) {
2839
                 sprintf(back[p].send_too,"If-Unmodified-Since: %s\r\n",back[p].r.lastmodified);
2840
                 back[p].http11=1;    // En tête 1.1
2841
                 } else 
2842
               */
2843
0
              if (strlen(lastmodified)) {
2844
0
                sprintf(back[p].send_too,
2845
0
                        "If-Unmodified-Since: %s\r\nRange: bytes=" LLintP
2846
0
                        "-\r\n", lastmodified, (LLint) sz);
2847
0
                back[p].http11 = 1;     // En tête 1.1
2848
0
                back[p].is_update = 1;  /* this is an update of a file */
2849
0
                back[p].range_req_size = sz;
2850
0
                back[p].r.req.range_used = 1;
2851
0
                back[p].r.req.nocompression = 1;
2852
0
              } else {
2853
0
                hts_log_print(opt, LOG_WARNING,
2854
0
                              "Could not find timestamp for partially present file, restarting (lost "
2855
0
                              LLintP " bytes): %s%s", (LLint) sz,
2856
0
                              back[p].url_adr, back[p].url_fil);
2857
0
              }
2858
2859
0
            } else {
2860
0
              hts_log_print(opt, LOG_NOTICE,
2861
0
                            "File partially present (" LLintP
2862
0
                            " bytes) retransferred due to lack of cache: %s%s",
2863
0
                            (LLint) sz, back[p].url_adr, back[p].url_fil);
2864
              /* Sinon requête normale... */
2865
0
              back[p].http11 = 0;
2866
0
            }
2867
0
          } else if (opt->norecatch) {  // tester norecatch
2868
0
            filenote(&opt->state.strc, save, NULL);     // ne pas purger tout de même
2869
0
            file_notify(opt, back[p].url_adr, back[p].url_fil, back[p].url_sav,
2870
0
                        0, 0, back[p].r.notmodified);
2871
0
            back[p].status = STATUS_READY;      // OK prêt
2872
0
            back_set_finished(opt, sback, p);
2873
0
            back[p].r.statuscode = STATUSCODE_INVALID;  // erreur
2874
0
            strcpybuff(back[p].r.msg, "Null-size file not recaught");
2875
0
            return 0;
2876
0
          }
2877
0
        } else {
2878
0
          hts_log_print(opt, LOG_NOTICE,
2879
0
                        "HTML file (" LLintP
2880
0
                        " bytes) retransferred due to lack of cache: %s%s",
2881
0
                        (LLint) sz, back[p].url_adr, back[p].url_fil);
2882
          /* Sinon requête normale... */
2883
0
          back[p].http11 = 0;
2884
0
        }
2885
0
      }
2886
0
    }
2887
2888
    /* Stop requested - abort backing */
2889
0
    if (opt->state.stop) {
2890
0
      back[p].r.statuscode = STATUSCODE_INVALID;        // fatal
2891
0
      strcpybuff(back[p].r.msg, "mirror stopped by user");
2892
0
      back[p].status = STATUS_READY;    // terminé
2893
0
      back_set_finished(opt, sback, p);
2894
0
      hts_log_print(opt, LOG_WARNING,
2895
0
                    "File not added due to mirror cancel: %s%s", adr, fil);
2896
0
      return 0;
2897
0
    }
2898
2899
0
    {
2900
      ///htsblk r;   non directement dans la structure-réponse!
2901
0
      T_SOC soc;
2902
2903
      // ouvrir liaison, envoyer requète
2904
      // ne pas traiter ou recevoir l'en tête immédiatement
2905
0
      hts_init_htsblk(&back[p].r);
2906
0
      back[p].r.location = back[p].location_buffer;
2907
      // fresh connect: address list not yet probed, start at the first
2908
0
      sback->connect_fallback[p].addr_index = 0;
2909
0
      sback->connect_fallback[p].addr_count = -1;
2910
      // recopier proxy
2911
0
      if ((back[p].r.req.proxy.active = opt->proxy.active)) {
2912
0
        if (StringBuff(opt->proxy.bindhost) != NULL)
2913
0
          back[p].r.req.proxy.bindhost = StringBuff(opt->proxy.bindhost);
2914
0
        if (StringBuff(opt->proxy.name) != NULL)
2915
0
          back[p].r.req.proxy.name = StringBuff(opt->proxy.name);
2916
0
        back[p].r.req.proxy.port = opt->proxy.port;
2917
0
      }
2918
      // et user-agent
2919
0
      back[p].r.req.user_agent = StringBuff(opt->user_agent);
2920
0
      back[p].r.req.referer = StringBuff(opt->referer);
2921
0
      back[p].r.req.from = StringBuff(opt->from);
2922
0
      back[p].r.req.lang_iso = StringBuff(opt->lang_iso);
2923
0
      back[p].r.req.accept = StringBuff(opt->accept);
2924
0
      back[p].r.req.headers = StringBuff(opt->headers);
2925
0
      back[p].r.req.user_agent_send = opt->user_agent_send;
2926
      // et http11
2927
0
      back[p].r.req.http11 = back[p].http11;
2928
0
      back[p].r.req.nocompression = opt->nocompression;
2929
0
      back[p].r.req.nokeepalive = opt->nokeepalive;
2930
2931
      // mode ftp, court-circuit!
2932
0
      if (strfield(back[p].url_adr, "ftp://")) {
2933
0
        if (back[p].testmode) {
2934
0
          hts_log_print(opt, LOG_DEBUG,
2935
0
                        "error: forbidden test with ftp link for back_add");
2936
0
          return -1;            // erreur pas de test permis
2937
0
        }
2938
        // the ftp client dials the origin itself: over socks that would bypass
2939
        // the proxy, so fail the link rather than leak the connection (#563)
2940
0
        if (back[p].r.req.proxy.active &&
2941
0
            hts_proxy_is_socks(back[p].r.req.proxy.name)) {
2942
0
          back[p].r.statuscode = STATUSCODE_NON_FATAL;
2943
0
          strcpybuff(back[p].r.msg,
2944
0
                     "ftp:// is not supported over a SOCKS proxy");
2945
0
          back[p].status = STATUS_READY;
2946
0
          back_set_finished(opt, sback, p);
2947
0
          return 0;
2948
0
        }
2949
0
        if (!(back[p].r.req.proxy.active && opt->ftp_proxy)) {  // connexion directe, gérée en thread
2950
0
          FTPDownloadStruct *str =
2951
0
            (FTPDownloadStruct *) malloc(sizeof(FTPDownloadStruct));
2952
0
          str->pBack = &back[p];
2953
0
          str->pOpt = opt;
2954
          /* */
2955
0
          back[p].status = STATUS_FTP_TRANSFER; // connexion ftp
2956
0
#if USE_BEGINTHREAD
2957
0
          launch_ftp(str);
2958
#else
2959
#error Must have pthreads
2960
#endif
2961
0
          return 0;
2962
0
        }
2963
0
      } else if (strfield(back[p].url_adr, "https://")) {
2964
#if HTS_USEOPENSSL
2965
        back[p].r.ssl = 1; // let's rock
2966
        back[p].r.ssl_con = NULL;
2967
#else
2968
        // Transferring it would mean a cleartext request to an https URL.
2969
0
        back[p].r.statuscode = STATUSCODE_NON_FATAL;
2970
0
        strcpybuff(back[p].r.msg, "https:// is not supported by this build");
2971
0
        back[p].status = STATUS_READY;
2972
0
        back_set_finished(opt, sback, p);
2973
0
        return 0;
2974
0
#endif
2975
0
      }
2976
2977
0
      if (!back_trylive(opt, cache, sback, p)) {
2978
0
#if HTS_XGETHOST
2979
0
        back[p].status = STATUS_WAIT_DNS; // host name resolution attempt
2980
0
        soc = INVALID_SOCKET;             // not opened yet
2981
0
        if (host_wait(opt, &back[p])) {   // ready (file, or cached dns)
2982
#if HDEBUG
2983
          printf("ok, dns cache ready..\n");
2984
#endif
2985
0
          soc =
2986
0
            http_xfopen(opt, 0, 0, 0, back[p].send_too, adr, fil, &back[p].r);
2987
0
          if (soc == INVALID_SOCKET) {
2988
0
            back[p].status = STATUS_READY;      // fini, erreur
2989
0
            back_set_finished(opt, sback, p);
2990
0
          }
2991
0
        }
2992
        //
2993
#else
2994
        //
2995
#if CNXDEBUG
2996
        printf("XFopen..\n");
2997
#endif
2998
2999
        if (strnotempty(back[p].send_too))      // envoyer un if-modified-since
3000
#if HTS_XCONN
3001
          soc = http_xfopen(0, 0, 0, back[p].send_too, adr, fil, &(back[p].r));
3002
#else
3003
          soc = http_xfopen(0, 0, 1, back[p].send_too, adr, fil, &(back[p].r));
3004
#endif
3005
        else
3006
#if HTS_XCONN
3007
          soc = http_xfopen(test, 0, 0, NULL, adr, fil, &(back[p].r));
3008
#else
3009
          soc = http_xfopen(test, 0, 1, NULL, adr, fil, &(back[p].r));
3010
#endif
3011
#endif
3012
0
      } else {
3013
0
        soc = back[p].r.soc;
3014
3015
0
        hts_log_print(opt, LOG_DEBUG,
3016
0
                      "(Keep-Alive): successfully linked #%d (for %s%s)",
3017
0
                      back[p].r.debugid, back[p].url_adr, back[p].url_fil);
3018
0
      }
3019
3020
0
      if (opt->timeout > 0) {   // gestion du opt->timeout
3021
0
        back[p].timeout = opt->timeout;
3022
0
        back[p].timeout_refresh = time_local();
3023
0
      } else {
3024
0
        back[p].timeout = -1;   // pas de gestion (default)
3025
0
      }
3026
3027
0
      if (opt->rateout > 0) {   // gestion d'un taux minimum de transfert toléré
3028
0
        back[p].rateout = opt->rateout;
3029
0
        back[p].rateout_time = time_local();
3030
0
      } else {
3031
0
        back[p].rateout = -1;   // pas de gestion (default)
3032
0
      }
3033
3034
      // Note: on charge les code-page erreurs (erreur 404, etc) dans le cas où
3035
      // cela est rattrapable (exemple: 301,302 moved xxx -> refresh sur la
3036
      // page!)
3037
3038
#if CNXDEBUG
3039
      printf("Xfopen ok, poll..\n");
3040
#endif
3041
3042
0
#if HTS_XGETHOST
3043
0
      if (soc != INVALID_SOCKET)
3044
0
        if (back[p].status == STATUS_WAIT_DNS) {        // pas d'erreur
3045
0
          if (!back[p].r.is_file)
3046
0
            back[p].status = STATUS_CONNECTING; // connexion en cours
3047
0
          else
3048
0
            back[p].status = 1; // fichier
3049
0
        }
3050
#else
3051
      if (soc == INVALID_SOCKET) {      // erreur socket
3052
        back[p].status = STATUS_READY;  // FINI
3053
        back_set_finished(opt, sback, p);
3054
        back[p].r.soc = INVALID_SOCKET;
3055
      } else {
3056
        if (!back[p].r.is_file)
3057
#if HTS_XCONN
3058
          back[p].status = STATUS_CONNECTING;   // connexion en cours
3059
#else
3060
          back[p].status = STATUS_WAIT_HEADERS;  // chargement en tête en cours
3061
#endif
3062
        else
3063
          back[p].status = 1;   // chargement fichier
3064
#if BDEBUG==1
3065
        printf("..loading header\n");
3066
#endif
3067
      }
3068
#endif
3069
3070
0
    }
3071
3072
    // note: si il y a erreur (404,etc) status=2 (terminé/échec) mais
3073
    // le lien est considéré comme traité
3074
3075
0
    return 0;
3076
0
  } else {
3077
0
    if (opt->log != NULL) {
3078
0
      hts_log_print(opt, LOG_WARNING,
3079
0
                    "error: no space left in stack for back_add");
3080
0
      if ((opt->state.debug_state & 1) == 0) {  /* debug_state<0> == debug 'no space left in stack' */
3081
0
        int i;
3082
3083
0
        hts_log_print(opt, LOG_WARNING, "debug: DUMPING %d BLOCKS", back_max);
3084
0
        opt->state.debug_state |= 1;    /* once */
3085
        /* OUTPUT FULL DEBUG INFORMATION THE FIRST TIME WE SEE THIS VERY ANNOYING BUG,
3086
           HOPING THAT SOME USER REPORT WILL QUICKLY SOLVE THIS PROBLEM :p */
3087
0
        for(i = 0; i < back_max; i++) {
3088
0
          if (back[i].status != -1) {
3089
0
            int may_clean = slot_can_be_cleaned(&back[i]);
3090
0
            int may_finalize = may_clean
3091
0
              && slot_can_be_finalized(opt, &back[i]);
3092
0
            int may_serialize = slot_can_be_cached_on_disk(&back[i]);
3093
3094
0
            hts_log_print(
3095
0
                opt, LOG_DEBUG,
3096
0
                "back[%03d]: may_clean=%d, may_finalize_disk=%d, "
3097
0
                "may_serialize=%d:" LF "\t"
3098
0
                "finalized(%d), status(%d), locked(%d), delayed(%d), "
3099
0
                "test(%d), " LF "\t"
3100
0
                "statuscode(%d), size(%d), is_write(%d), may_hypertext(%d), " LF
3101
0
                "\t"
3102
0
                "contenttype(%s), url(%s%s), save(%s)",
3103
0
                i, may_clean, may_finalize, may_serialize, back[i].finalized,
3104
0
                back[i].status, back[i].locked, IS_DELAYED_EXT(back[i].url_sav),
3105
0
                back[i].testmode, back[i].r.statuscode, (int) back[i].r.size,
3106
0
                back[i].r.is_write,
3107
0
                may_be_hypertext_mime(opt, back[i].r.contenttype,
3108
0
                                      back[i].url_fil),
3109
                /* */
3110
0
                back[i].r.contenttype, back[i].url_adr, back[i].url_fil,
3111
0
                back[i].url_sav);
3112
0
          }
3113
0
        }
3114
0
      }
3115
3116
0
    }
3117
0
    return -1;                  // plus de place
3118
0
  }
3119
0
}
3120
3121
#if HTS_XGETHOST
3122
// Resolution is synchronous inside the connect path; no pre-resolve step, so
3123
// the host is always immediately ready.
3124
0
int host_wait(httrackp *opt, lien_back *back) { return 1; }
3125
#endif
3126
3127
// élimine les fichiers non html en backing (anticipation)
3128
// cleanup non-html files in backing to save backing space
3129
// and allow faster "save in cache" operation
3130
// also cleanup keep-alive sockets and ensure that not too many sockets are being opened
3131
3132
0
static int slot_can_be_cleaned(const lien_back * back) {
3133
0
  return (back->status == STATUS_READY) // ready
3134
         /* Check autoclean */
3135
0
         && (!back->locked)   // not held by hts_wait_delayed (name pending)
3136
0
         && (!back->testmode) // not test mode
3137
0
         && (strnotempty(back->url_sav))     // filename exists
3138
0
         && (HTTP_IS_OK(back->r.statuscode)) // HTTP "OK"
3139
0
         && (back->r.size >= 0)              // size>=0
3140
0
      ;
3141
0
}
3142
3143
0
static int slot_can_be_finalized(httrackp * opt, const lien_back * back) {
3144
0
  return back->r.is_write       // not in memory (on disk, ready)
3145
0
    && !is_hypertext_mime(opt, back->r.contenttype, back->url_fil)      // not HTML/hypertext
3146
0
    && !may_be_hypertext_mime(opt, back->r.contenttype, back->url_fil)  // may NOT be parseable mime type
3147
    /* Has not been added before the heap saw the link, or now exists on heap */
3148
0
    && (!back->early_add
3149
0
        || hash_read(opt->hash, back->url_sav, NULL, HASH_STRUCT_FILENAME) >= 0);
3150
0
}
3151
3152
0
void back_clean(httrackp * opt, cache_back * cache, struct_back * sback) {
3153
0
  lien_back *const back = sback->lnk;
3154
0
  const int back_max = sback->count;
3155
0
  int oneMore = ((opt->state._hts_in_html_parsing == 2 && opt->maxsoc >= 2) || (opt->state._hts_in_html_parsing == 1 && opt->maxsoc >= 4)) ? 1 : 0;     // testing links
3156
0
  int i;
3157
3158
0
  for(i = 0; i < back_max; i++) {
3159
0
    if (slot_can_be_cleaned(&back[i])) {
3160
0
      if (slot_can_be_finalized(opt, &back[i])) {
3161
0
        (void) back_flush_output(opt, cache, sback, i); // flush output buffers
3162
0
        usercommand(opt, 0, NULL, back[i].url_sav, back[i].url_adr,
3163
0
                    back[i].url_fil);
3164
        /* MANDATORY if we don't want back_fill() to endlessly put the same file on download! */
3165
0
        {
3166
0
          int index = hash_read(opt->hash, back[i].url_sav, NULL, HASH_STRUCT_FILENAME );       // lecture type 0 (sav)
3167
3168
0
          if (index >= 0) {
3169
0
            opt->liens[index]->pass2 = -1;        /* DONE! */
3170
0
          } else {
3171
0
            hts_log_print(opt, LOG_INFO,
3172
0
                          "engine: warning: entry cleaned up, but no trace on heap: %s%s (%s)",
3173
0
                          back[i].url_adr, back[i].url_fil, back[i].url_sav);
3174
0
          }
3175
0
        }
3176
0
        HTS_STAT.stat_background++;
3177
0
        hts_log_print(opt, LOG_INFO,
3178
0
                      "File successfully written in background: %s",
3179
0
                      back[i].url_sav);
3180
0
        back_maydelete(opt, cache, sback, i);   // May delete backing entry
3181
0
      } else {
3182
0
        if (!back[i].finalized) {
3183
          /* recycle the socket, but keep back[i].r.adr in memory */
3184
0
          hts_log_print(opt, LOG_DEBUG,
3185
0
                        "file %s%s validated (cached, left in memory)",
3186
0
                        back[i].url_adr, back[i].url_fil);
3187
0
          back_maydeletehttp(opt, cache, sback, i);
3188
0
        }
3189
0
      }
3190
0
    } else if (back[i].status == STATUS_ALIVE) {        // waiting (keep-alive)
3191
0
      if (!back[i].r.keep_alive || back[i].r.soc == INVALID_SOCKET
3192
0
          || back[i].r.keep_alive_max < 1
3193
0
          || time_local() >= back[i].ka_time_start + back[i].r.keep_alive_t) {
3194
0
        const char *reason = "unknown";
3195
0
        char buffer[128];
3196
0
        if (!back[i].r.keep_alive) {
3197
0
          reason = "not keep-alive";
3198
0
        } else if (back[i].r.soc == INVALID_SOCKET) {
3199
0
          reason = "closed";
3200
0
        } else if (back[i].r.keep_alive_max < 1) {
3201
0
          reason = "keep-alive-max reached";
3202
0
        } else if (time_local() >= back[i].ka_time_start + back[i].r.keep_alive_t) {
3203
0
          assertf(back[i].ka_time_start != 0);
3204
0
          snprintf(buffer, sizeof(buffer), "keep-alive timeout = %ds)",
3205
0
                   (int) back[i].r.keep_alive_t);
3206
0
          reason = buffer;
3207
0
        }
3208
0
        hts_log_print(opt, LOG_DEBUG,
3209
0
                      "(Keep-Alive): live socket #%d (%s) closed (%s)",
3210
0
                      back[i].r.debugid, back[i].url_adr, reason);
3211
0
        back_delete(opt, cache, sback, i);      // delete backing entry
3212
0
      }
3213
0
    }
3214
0
  }
3215
  /* switch connections to live ones */
3216
0
  for(i = 0; i < back_max; i++) {
3217
0
    if (back[i].status == STATUS_READY) {       // ready
3218
0
      if (back[i].r.soc != INVALID_SOCKET) {
3219
0
        back_maydeletehttp(opt, cache, sback, i);
3220
0
      }
3221
0
    }
3222
0
  }
3223
  /* delete sockets if too many keep-alive'd sockets in background */
3224
0
  if (opt->maxsoc > 0) {
3225
0
    int max = opt->maxsoc + oneMore;
3226
0
    int curr = back_nsoc_overall(sback);
3227
3228
0
    if (curr > max) {
3229
0
      hts_log_print(opt, LOG_DEBUG, "(Keep-Alive): deleting #%d sockets",
3230
0
                    curr - max);
3231
0
    }
3232
0
    for(i = 0; i < back_max && curr > max; i++) {
3233
0
      if (back[i].status == STATUS_ALIVE) {
3234
0
        back_delete(opt, cache, sback, i);      // delete backing entry
3235
0
        curr--;
3236
0
      }
3237
0
    }
3238
0
  }
3239
  /* transfer ready slots to the storage hashtable */
3240
0
  {
3241
0
    int nxfr = back_cleanup_background(opt, cache, sback);
3242
3243
0
    if (nxfr > 0) {
3244
0
      hts_log_print(opt, LOG_DEBUG,
3245
0
                    "(htsback): %d slots ready moved to background", nxfr);
3246
0
    }
3247
0
  }
3248
0
}
3249
3250
/* Slot waiting for a connection to come up: nothing requested on it yet. */
3251
0
static hts_boolean back_is_preconnect(const int status) {
3252
0
  return status == STATUS_WAIT_DNS || status == STATUS_CONNECTING ||
3253
0
         status == STATUS_SSL_WAIT_HANDSHAKE;
3254
0
}
3255
3256
/* Slot carrying a transfer of ours. An FTP one is its worker thread's, which
3257
   owns socket and slot, so no sweep below may touch it. */
3258
0
static hts_boolean back_is_live(const int status) {
3259
0
  return status > 0 && status < STATUS_FTP_TRANSFER;
3260
0
}
3261
3262
/* Tear down live slot p, reported as statuscode/msg. trunc is the
3263
   WARC-Truncated reason to archive its partial body under, WARC_TRUNC_NONE to
3264
   leave the body unarchived. */
3265
static void back_abort_slot(httrackp *opt, struct_back *sback, const int p,
3266
                            const int statuscode, const char *msg,
3267
0
                            const int trunc) {
3268
0
  lien_back *const back = &sback->lnk[p];
3269
3270
  /* A cap-truncated body is deliberate, not broken: archive what arrived with
3271
     WARC-Truncated before the abort overwrites the slot's real 2xx status.
3272
     HTTrack still treats the slot as incomplete afterwards. */
3273
0
  if (trunc != WARC_TRUNC_NONE && StringNotEmpty(opt->warc_file) &&
3274
0
      back->r.statuscode > 0 && back->r.warc_resphdr != NULL &&
3275
0
      back->r.size > 0 &&
3276
0
      !(back->r.is_write && IS_DELAYED_EXT(back->url_sav))) {
3277
0
    if (back->r.is_write && back->r.out != NULL)
3278
0
      fflush(back->r.out);
3279
0
    back->r.warc_truncated = trunc;
3280
0
    warc_write_backtransaction(opt, back);
3281
0
  }
3282
0
  if (back->r.soc != INVALID_SOCKET)
3283
0
    deletehttp(&back->r);
3284
0
  back->r.soc = INVALID_SOCKET;
3285
  /* drop a .delayed placeholder; real partials survive for resume */
3286
0
  if (back->r.is_write && IS_DELAYED_EXT(back->url_sav))
3287
0
    back_delayed_discard(opt, back);
3288
  /* That partial outlives the run, so hts-cache/ref must too or the next
3289
     --continue refetches the file whole (#1595). */
3290
0
  else if (back->r.is_write)
3291
0
    opt->abort_left_partial = HTS_TRUE;
3292
0
  back->r.statuscode = statuscode;
3293
0
  strcpybuff(back->r.msg, msg);
3294
0
  back->status = STATUS_READY;
3295
0
  back_set_finished(opt, sback, p);
3296
0
}
3297
3298
/* Drop what a user stop must not leave running, and return the count. A cap
3299
   raises the stop flag itself, then grants the transfers already running a
3300
   grace that only back_abort_limit() may end (#77, #481). A slot still waiting
3301
   to connect has nothing to finish and would hold the drain (#1073). */
3302
0
static int back_abort_stopped(httrackp *opt, struct_back *sback) {
3303
0
  const hts_boolean grace = back_mirror_capped(opt);
3304
0
  int aborted = 0;
3305
0
  int i;
3306
3307
0
  for (i = 0; i < sback->count; i++) {
3308
0
    const int status = sback->lnk[i].status;
3309
3310
0
    if (!back_is_live(status) || (grace && !back_is_preconnect(status)))
3311
0
      continue;
3312
    /* fatal, as back_add() reports a stop: no retry may reschedule the link */
3313
0
    back_abort_slot(opt, sback, i, STATUSCODE_INVALID, "mirror stopped by user",
3314
0
                    WARC_TRUNC_NONE);
3315
0
    aborted++;
3316
0
  }
3317
0
  return aborted;
3318
0
}
3319
3320
/* Abort every live slot once a cap has overrun its grace, and return the
3321
   count. Its partial body is archived, because the truncation is the user's own
3322
   cap, and back_abort_slot() keeps the resume data describing it. */
3323
static int back_abort_limit(httrackp *opt, struct_back *sback,
3324
0
                            const hts_mirror_limit limit) {
3325
0
  const hts_boolean size = limit == HTS_MIRROR_LIMIT_SIZE;
3326
0
  int aborted = 0;
3327
0
  int i;
3328
3329
0
  for (i = 0; i < sback->count; i++) {
3330
0
    if (!back_is_live(sback->lnk[i].status))
3331
0
      continue;
3332
0
    back_abort_slot(opt, sback, i, STATUSCODE_TIMEOUT,
3333
0
                    size ? "Mirror Size Limit" : "Mirror Time Out",
3334
0
                    size ? WARC_TRUNC_LENGTH : WARC_TRUNC_TIME);
3335
0
    aborted++;
3336
0
  }
3337
0
  return aborted;
3338
0
}
3339
3340
// attente (gestion des buffers des sockets)
3341
void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
3342
0
               TStamp stat_timestart) {
3343
0
  char catbuff[CATBUFF_SIZE];
3344
0
  lien_back *const back = sback->lnk;
3345
0
  const int back_max = sback->count;
3346
0
  unsigned int i_mod;
3347
0
  T_SOC nfds = INVALID_SOCKET;
3348
0
  fd_set fds, fds_c, fds_e;     // fds pour lecture, connect (write), et erreur
3349
0
  int nsockets;                 // nbre sockets
3350
0
  LLint max_read_bytes;         // max bytes read per sockets
3351
0
  struct timeval tv;
3352
0
  int do_wait = 0;
3353
0
  int gestion_timeout = 0;
3354
0
  int busy_recv = 0;            // pas de données pour le moment   
3355
0
  int busy_state = 0;           // pas de connexions
3356
0
  int max_loop;                 // nombre de boucles max à parcourir..
3357
0
  int max_loop_chk = 0;
3358
0
  unsigned int mod_random =
3359
0
    (unsigned int) (time_local() + HTS_STAT.HTS_TOTAL_RECV);
3360
3361
  // max. number of loops
3362
0
  max_loop = 8;
3363
3364
0
#if 1
3365
  // Cleanup the stack to save space!
3366
0
  back_clean(opt, cache, sback);
3367
0
#endif
3368
3369
0
  if (opt->state.stop) {
3370
0
    const int aborted = back_abort_stopped(opt, sback);
3371
3372
0
    if (aborted > 0)
3373
0
      hts_log_print(opt, LOG_WARNING,
3374
0
                    "Mirror stopped by user, %d transfer(s) aborted", aborted);
3375
0
  }
3376
3377
  /* Time/size limit exceeded past grace: abort in-flight transfers so no wait
3378
     loop starves (#481, #77). */
3379
0
  if (!back_checkmirror(opt)) {
3380
0
    const hts_mirror_limit limit = back_mirror_limit(opt);
3381
0
    const char *const reason =
3382
0
        (limit == HTS_MIRROR_LIMIT_SIZE) ? "size limit" : "time limit";
3383
0
    const int aborted = back_abort_limit(opt, sback, limit);
3384
3385
0
    if (aborted > 0)
3386
0
      hts_log_print(opt, LOG_WARNING, "%s reached, %d transfer(s) aborted",
3387
0
                    reason, aborted);
3388
0
    return;
3389
0
  }
3390
3391
  // recevoir tant qu'il y a des données (avec un maximum de max_loop boucles)
3392
0
  do_wait = 0;
3393
0
  gestion_timeout = 0;
3394
0
  do {
3395
0
    int max_c;
3396
3397
0
    busy_state = busy_recv = 0;
3398
3399
    // inscrire les sockets actuelles, et rechercher l'ID la plus élevée
3400
0
    FD_ZERO(&fds);
3401
0
    FD_ZERO(&fds_c);
3402
0
    FD_ZERO(&fds_e);
3403
0
    nsockets = 0;
3404
0
    max_read_bytes = TAILLE_BUFFER;     // maximum bytes that can be read
3405
0
    nfds = INVALID_SOCKET;
3406
3407
0
    max_c = 1;
3408
0
    for (i_mod = 0; i_mod < (unsigned int) back_max; i_mod++) {
3409
0
      unsigned int i = (i_mod + mod_random) % (back_max);
3410
3411
      // en cas de gestion du connect préemptif
3412
0
#if HTS_XCONN
3413
0
      if (back[i].status == STATUS_CONNECTING) {        // connexion
3414
        // a connecting slot always carries a live socket; guard anyway so a
3415
        // stray INVALID_SOCKET can never reach FD_SET (mirrors the recv branch)
3416
0
        if (back[i].r.soc != INVALID_SOCKET) {
3417
0
          do_wait = 1;
3418
3419
          // noter socket write
3420
0
          FD_SET(back[i].r.soc, &fds_c);
3421
3422
          // noter socket erreur
3423
0
          FD_SET(back[i].r.soc, &fds_e);
3424
3425
          // calculer max
3426
0
          if (max_c) {
3427
0
            max_c = 0;
3428
0
            nfds = back[i].r.soc;
3429
0
          } else if (back[i].r.soc > nfds) {
3430
            // ID socket la plus élevée
3431
0
            nfds = back[i].r.soc;
3432
0
          }
3433
0
        }
3434
3435
0
      } else
3436
0
#endif
3437
0
#if HTS_XGETHOST
3438
0
      if (back[i].status == STATUS_WAIT_DNS) {  // attente
3439
        // rien à faire..
3440
0
      } else
3441
0
#endif
3442
        // poll pour la lecture sur les sockets
3443
0
      if ((back[i].status > 0) && (back[i].status < 100)) {     // en réception http
3444
3445
#if BDEBUG == 1
3446
#endif
3447
        // non local et non ftp
3448
0
        if (!back[i].r.is_file) {
3449
          //## if (back[i].url_adr[0]!=lOCAL_CHAR) {
3450
3451
          // vérification de sécurité
3452
0
          if (back[i].r.soc != INVALID_SOCKET) {        // hey, you never know..
3453
0
            if (
3454
                // Do not endlessly wait when receiving SSL http data (Patrick Pfeifer)
3455
#if HTS_USEOPENSSL
3456
                !back[i].r.ssl && 
3457
#endif
3458
0
                back[i].status > 0 && back[i].status < 1000) {
3459
0
              do_wait = 1;
3460
3461
              // noter socket read
3462
0
              FD_SET(back[i].r.soc, &fds);
3463
3464
              // noter socket error
3465
0
              FD_SET(back[i].r.soc, &fds_e);
3466
3467
              // incrémenter nombre de sockets
3468
0
              nsockets++;
3469
3470
              // calculer max
3471
0
              if (max_c) {
3472
0
                max_c = 0;
3473
0
                nfds = back[i].r.soc;
3474
0
              } else if (back[i].r.soc > nfds) {
3475
                // ID socket la plus élevée
3476
0
                nfds = back[i].r.soc;
3477
0
              }
3478
0
            }
3479
0
          } else {
3480
0
            back[i].r.statuscode = STATUSCODE_CONNERROR;
3481
0
            if (back[i].status == STATUS_CONNECTING)
3482
0
              strcpybuff(back[i].r.msg, "Connect Error");
3483
0
            else
3484
0
              strcpybuff(back[i].r.msg, "Receive Error");
3485
0
            back[i].status = STATUS_READY;      // terminé
3486
0
            back_set_finished(opt, sback, i);
3487
0
            hts_log_print(opt, LOG_WARNING,
3488
0
                          "Unexpected socket error during pre-loop");
3489
0
          }
3490
0
        }
3491
3492
0
      }
3493
0
    }
3494
0
    nfds++;
3495
3496
0
    if (do_wait) {              // attendre
3497
      // temps d'attente max: 2.5 seconde
3498
0
      tv.tv_sec = HTS_SOCK_SEC;
3499
0
      tv.tv_usec = HTS_SOCK_MS;
3500
3501
#if BDEBUG==1
3502
      printf("..select\n");
3503
#endif
3504
3505
      // poller les sockets-attention au noyau sous Unix..
3506
#if HTS_WIDE_DEBUG
3507
      DEBUG_W("select\n");
3508
#endif
3509
      /* Discard the sets select() did not write: on EINTR they still hold
3510
         every socket we filled in, which reads back as an error (#1110). */
3511
0
      if (select((int) nfds, &fds, &fds_c, &fds_e, &tv) <= 0) {
3512
0
        FD_ZERO(&fds);
3513
0
        FD_ZERO(&fds_c);
3514
0
        FD_ZERO(&fds_e);
3515
0
      }
3516
#if HTS_WIDE_DEBUG
3517
      DEBUG_W("select done\n");
3518
#endif
3519
0
    }
3520
    // maximum data which can be received for a socket, if limited
3521
0
    if (nsockets) {
3522
0
      if (opt->maxrate > 0) {
3523
0
        max_read_bytes = (check_downloadable_bytes(opt->maxrate) / nsockets);
3524
0
        if (max_read_bytes > TAILLE_BUFFER) {
3525
          /* limit size */
3526
0
          max_read_bytes = TAILLE_BUFFER;
3527
0
        } else if (max_read_bytes < TAILLE_BUFFER) {
3528
          /* a small pause */
3529
0
          Sleep(10);
3530
0
        }
3531
0
      }
3532
0
    }
3533
0
    if (!max_read_bytes)
3534
0
      busy_recv = 0;
3535
3536
    // recevoir les données arrivées
3537
0
    for (i_mod = 0; i_mod < (unsigned int) back_max; i_mod++) {
3538
0
      unsigned int i = (i_mod + mod_random) % (back_max);
3539
3540
      // winsock flags a failed connect in the exception set only: leave a
3541
      // connecting slot to the connect handler, which can still fall back
3542
0
      if (back[i].status > 0 && back[i].status != STATUS_CONNECTING) {
3543
0
        if (!back[i].r.is_file) {       // not file..
3544
0
          if (back[i].r.soc != INVALID_SOCKET) {        // hey, you never know..
3545
0
            int err = FD_ISSET(back[i].r.soc, &fds_e);
3546
3547
0
            if (err) {
3548
0
              if (back[i].r.soc != INVALID_SOCKET) {
3549
#if HTS_DEBUG_CLOSESOCK
3550
                DEBUG_W("back_wait: deletehttp\n");
3551
#endif
3552
0
                deletehttp(&back[i].r);
3553
0
              }
3554
0
              back[i].r.soc = INVALID_SOCKET;
3555
0
              back[i].r.statuscode = STATUSCODE_CONNERROR;
3556
0
              strcpybuff(back[i].r.msg, "Receive Error");
3557
0
              if (back[i].status == STATUS_ALIVE) {     /* Keep-alive socket */
3558
0
                back_delete(opt, cache, sback, i);
3559
0
              } else {
3560
0
                back[i].status = STATUS_READY;  // terminé
3561
0
                back_set_finished(opt, sback, i);
3562
0
              }
3563
0
            }
3564
0
          }
3565
0
        }
3566
0
      }
3567
      // ---- FLAG WRITE MIS A UN?: POUR LE CONNECT
3568
0
      if (back[i].status == STATUS_CONNECTING) {        // attendre connect
3569
0
        hts_connect_fallback *const cf = &sback->connect_fallback[i];
3570
0
        int dispo = 0;
3571
3572
        // probe the resolved address list once per fresh connect (cache hit:
3573
        // the host was resolved when this connect was opened). Not under a
3574
        // proxy: the socket dials the proxy, so resolving the origin here leaks
3575
        // its DNS and lets a proxy-connect failure fall back to dialing it
3576
        // direct.
3577
0
        if (cf->addr_count < 0 && back[i].r.soc != INVALID_SOCKET &&
3578
0
            !back[i].r.is_file && !back[i].r.req.proxy.active) {
3579
0
          SOCaddr scratch[HTS_MAXADDRNUM];
3580
3581
0
          cf->addr_count = hts_dns_resolve_all(opt, back[i].url_adr, scratch,
3582
0
                                               HTS_MAXADDRNUM, NULL);
3583
0
          cf->connect_start = time_local();
3584
0
        }
3585
3586
        // vérifier l'existance de timeout-check
3587
0
        if (!gestion_timeout)
3588
0
          if (back[i].timeout > 0)
3589
0
            gestion_timeout = 1;
3590
3591
        // connecté?
3592
0
        dispo = back[i].r.soc != INVALID_SOCKET &&
3593
0
                (FD_ISSET(back[i].r.soc, &fds_c) ||
3594
0
                 FD_ISSET(back[i].r.soc, &fds_e));
3595
0
        if (dispo) { // socket ready: connect() finished (ok or failed)
3596
          // probe SO_ERROR and, on failure, fall back to the next address
3597
0
          if (connect_socket_error(back[i].r.soc) != 0) {
3598
0
            if (!back_connect_next(opt, sback, i)) {
3599
0
              deletehttp(&back[i].r);
3600
0
              back[i].r.soc = INVALID_SOCKET;
3601
0
              back[i].r.statuscode = STATUSCODE_CONNERROR;
3602
0
              strcpybuff(back[i].r.msg, "Connect Error");
3603
0
              back[i].status = STATUS_READY;
3604
0
              back_set_finished(opt, sback, i);
3605
0
            }
3606
0
            continue; // reconnected (stay connecting) or failed
3607
0
          }
3608
0
          busy_state = 1;
3609
3610
          // socks5: tunnel to the origin before anything is written, for http
3611
          // as well as https. Skip on a reused keep-alive socket (already
3612
          // tunneled) and on the post-TLS re-entry (ssl_con set) (#563).
3613
0
          if (back[i].r.req.proxy.active &&
3614
0
              hts_proxy_is_socks(back[i].r.req.proxy.name) &&
3615
0
              !back[i].r.keep_alive
3616
#if HTS_USEOPENSSL
3617
              && back[i].r.ssl_con == NULL
3618
#endif
3619
0
          ) {
3620
0
            const int timeout = back[i].timeout > 0 ? back[i].timeout : 30;
3621
3622
0
            if (!socks5_handshake(opt, &back[i].r, back[i].url_adr, timeout)) {
3623
0
              if (!strnotempty(back[i].r.msg))
3624
0
                strcpybuff(back[i].r.msg, "SOCKS5 handshake failed");
3625
0
              deletehttp(&back[i].r);
3626
0
              back[i].r.soc = INVALID_SOCKET;
3627
0
              back[i].r.statuscode = STATUSCODE_NON_FATAL;
3628
0
              back[i].status = STATUS_READY;
3629
0
              back_set_finished(opt, sback, i);
3630
0
              continue;
3631
0
            }
3632
0
          }
3633
3634
          // plain http tunneled through a CONNECT-only proxy (#564)
3635
0
          if (back[i].r.req.proxy.active &&
3636
0
              hts_proxy_is_connect(back[i].r.req.proxy.name) &&
3637
0
              !back[i].r.keep_alive
3638
#if HTS_USEOPENSSL
3639
              && !back[i].r.ssl
3640
#endif
3641
0
          ) {
3642
0
            const int timeout = back[i].timeout > 0 ? back[i].timeout : 30;
3643
3644
0
            if (!http_proxy_tunnel(opt, &back[i].r, back[i].url_adr, timeout)) {
3645
0
              if (!strnotempty(back[i].r.msg))
3646
0
                strcpybuff(back[i].r.msg, "proxy CONNECT failed");
3647
0
              deletehttp(&back[i].r);
3648
0
              back[i].r.soc = INVALID_SOCKET;
3649
0
              back[i].r.statuscode = STATUSCODE_NON_FATAL;
3650
0
              back[i].status = STATUS_READY;
3651
0
              back_set_finished(opt, sback, i);
3652
0
              continue;
3653
0
            }
3654
0
          }
3655
3656
#if HTS_USEOPENSSL
3657
          /* SSL mode */
3658
          if (back[i].r.ssl) {
3659
            int tunnel_ok = 1;
3660
3661
            // https via an http proxy: CONNECT-tunnel before TLS (#85); socks
3662
            // already carries the origin connection
3663
            if (back[i].r.req.proxy.active &&
3664
                !hts_proxy_is_socks(back[i].r.req.proxy.name) &&
3665
                back[i].r.ssl_con == NULL) {
3666
              const int timeout = back[i].timeout > 0 ? back[i].timeout : 30;
3667
3668
              tunnel_ok =
3669
                  http_proxy_tunnel(opt, &back[i].r, back[i].url_adr, timeout);
3670
              if (!tunnel_ok) {
3671
                if (!strnotempty(back[i].r.msg))
3672
                  strcpybuff(back[i].r.msg, "proxy CONNECT failed");
3673
                deletehttp(&back[i].r);
3674
                back[i].r.soc = INVALID_SOCKET;
3675
                back[i].r.statuscode = STATUSCODE_NON_FATAL;
3676
                back[i].status = STATUS_READY;
3677
                back_set_finished(opt, sback, i);
3678
              }
3679
            }
3680
            // handshake not yet launched
3681
            if (tunnel_ok && !back[i].r.ssl_con) {
3682
              SSL_CTX_set_options(openssl_ctx, SSL_OP_ALL);
3683
              // new session
3684
              back[i].r.ssl_con = SSL_new(openssl_ctx);
3685
              if (back[i].r.ssl_con) {
3686
                /* non-const twin: the OpenSSL macro casts the qualifier away */
3687
                char *hostname = jump_protocol(back[i].url_adr);
3688
                // some servers expect the hostname on the clienthello (SNI TLS extension)
3689
                SSL_set_tlsext_host_name(back[i].r.ssl_con, hostname);
3690
                SSL_clear(back[i].r.ssl_con);
3691
                if (SSL_set_fd(back[i].r.ssl_con, (int) back[i].r.soc) == 1) {
3692
                  SSL_set_connect_state(back[i].r.ssl_con);
3693
                  back[i].status = STATUS_SSL_WAIT_HANDSHAKE;   /* handshake wait */
3694
                  // the handshake gets its own timeout window, as connect does
3695
                  if (back[i].timeout > 0)
3696
                    back[i].timeout_refresh = time_local();
3697
                } else
3698
                  back[i].r.statuscode = STATUSCODE_SSL_HANDSHAKE;
3699
              } else
3700
                back[i].r.statuscode = STATUSCODE_SSL_HANDSHAKE;
3701
            }
3702
            /* Error */
3703
            if (tunnel_ok && back[i].r.statuscode == STATUSCODE_SSL_HANDSHAKE) {
3704
              strcpybuff(back[i].r.msg, "bad SSL/TLS handshake");
3705
              deletehttp(&back[i].r);
3706
              back[i].r.soc = INVALID_SOCKET;
3707
              back[i].r.statuscode = STATUSCODE_NON_FATAL;
3708
              back[i].status = STATUS_READY;
3709
              back_set_finished(opt, sback, i);
3710
            }
3711
          }
3712
#endif
3713
3714
#if BDEBUG==1
3715
          printf("..connect ok on socket %d\n", back[i].r.soc);
3716
#endif
3717
3718
0
          if ((back[i].r.soc != INVALID_SOCKET)
3719
0
              && (back[i].status == STATUS_CONNECTING)) {
3720
            /* limit nb. connections/seconds to avoid server overload */
3721
            /*if (opt->maxconn>0) {
3722
               Sleep(1000/opt->maxconn);
3723
               } */
3724
3725
0
            back[i].ka_time_start = time_local();
3726
0
            if (back[i].timeout > 0) {  // refresh timeout si besoin est
3727
0
              back[i].timeout_refresh = back[i].ka_time_start;
3728
0
            }
3729
0
            if (back[i].rateout > 0) {  // le taux de transfert de base sur le début de la connexion
3730
0
              back[i].rateout_time = back[i].ka_time_start;
3731
0
            }
3732
            // envoyer header
3733
0
            HTS_STAT.stat_nrequests++;
3734
0
            if (!back[i].head_request)
3735
0
              http_sendhead(opt, opt->cookie, 0, back[i].send_too,
3736
0
                            back[i].url_adr, back[i].url_fil,
3737
0
                            back[i].referer_adr, back[i].referer_fil,
3738
0
                            &back[i].r);
3739
0
            else if (back[i].head_request == 2) // test en GET!
3740
0
              http_sendhead(opt, opt->cookie, 0, back[i].send_too,
3741
0
                            back[i].url_adr, back[i].url_fil,
3742
0
                            back[i].referer_adr, back[i].referer_fil,
3743
0
                            &back[i].r);
3744
0
            else                // test!
3745
0
              http_sendhead(opt, opt->cookie, 1, back[i].send_too,
3746
0
                            back[i].url_adr, back[i].url_fil,
3747
0
                            back[i].referer_adr, back[i].referer_fil,
3748
0
                            &back[i].r);
3749
0
            back[i].status = STATUS_WAIT_HEADERS;        // attendre en tête maintenant
3750
0
          }
3751
0
        }
3752
        // attente gethostbyname
3753
0
      }
3754
#if HTS_USEOPENSSL
3755
      else if (back[i].status == STATUS_SSL_WAIT_HANDSHAKE) {       // wait for SSL handshake
3756
        // a peer that never speaks TLS must be reaped by --timeout too (#607)
3757
        if (!gestion_timeout)
3758
          if (back[i].timeout > 0)
3759
            gestion_timeout = 1;
3760
3761
        /* SSL mode */
3762
        if (back[i].r.ssl) {
3763
          int conn_code;
3764
3765
          if ((conn_code = SSL_connect(back[i].r.ssl_con)) <= 0) {
3766
            /* non blocking I/O, will retry */
3767
            int err_code = SSL_get_error(back[i].r.ssl_con, conn_code);
3768
3769
            if ((err_code != SSL_ERROR_WANT_READ)
3770
                && (err_code != SSL_ERROR_WANT_WRITE)
3771
              ) {
3772
              char tmp[256];
3773
3774
              tmp[0] = '\0';
3775
              ERR_error_string(err_code, tmp);
3776
              back[i].r.msg[0] = '\0';
3777
              strncatbuff(back[i].r.msg, tmp, sizeof(back[i].r.msg) - 2);
3778
              if (!strnotempty(back[i].r.msg)) {
3779
                htsblk_failf(&back[i].r, "SSL/TLS error %d", err_code);
3780
              }
3781
              deletehttp(&back[i].r);
3782
              back[i].r.soc = INVALID_SOCKET;
3783
              back[i].r.statuscode = STATUSCODE_NON_FATAL;
3784
              back[i].status = STATUS_READY;
3785
              back_set_finished(opt, sback, i);
3786
            }
3787
          } else {              /* got it! */
3788
            back[i].status = STATUS_CONNECTING; // back to waitconnect
3789
          }
3790
        } else {
3791
          strcpybuff(back[i].r.msg, "unexpected SSL/TLS error");
3792
          deletehttp(&back[i].r);
3793
          back[i].r.soc = INVALID_SOCKET;
3794
          back[i].r.statuscode = STATUSCODE_NON_FATAL;
3795
          back[i].status = STATUS_READY;
3796
          back_set_finished(opt, sback, i);
3797
        }
3798
3799
      }
3800
#endif
3801
0
#if HTS_XGETHOST
3802
0
      else if (back[i].status == STATUS_WAIT_DNS) {     // attendre gethostbyname
3803
#if DEBUGDNS
3804
#endif
3805
3806
0
        if (!gestion_timeout)
3807
0
          if (back[i].timeout > 0)
3808
0
            gestion_timeout = 1;
3809
3810
0
        if (host_wait(opt, &back[i])) { // prêt
3811
0
          back[i].status = STATUS_CONNECTING;   // attente connexion
3812
0
          if (back[i].timeout > 0) {    // refresh timeout si besoin est
3813
0
            back[i].timeout_refresh = time_local();
3814
0
          }
3815
0
          if (back[i].rateout > 0) {    // le taux de transfert de base sur le début de la connexion
3816
0
            back[i].rateout_time = time_local();
3817
0
          }
3818
3819
0
          back[i].r.soc =
3820
0
            http_xfopen(opt, 0, 0, 0, back[i].send_too, back[i].url_adr,
3821
0
                        back[i].url_fil, &(back[i].r));
3822
0
          if (back[i].r.soc == INVALID_SOCKET) {
3823
0
            back[i].status = STATUS_READY;      // fini, erreur
3824
0
            back_set_finished(opt, sback, i);
3825
0
            if (back[i].r.soc != INVALID_SOCKET) {
3826
#if HTS_DEBUG_CLOSESOCK
3827
              DEBUG_W("back_wait(2): deletehttp\n");
3828
#endif
3829
0
              deletehttp(&back[i].r);
3830
0
            }
3831
0
            back[i].r.soc = INVALID_SOCKET;
3832
0
            back[i].r.statuscode = STATUSCODE_NON_FATAL;
3833
0
            if (strnotempty(back[i].r.msg) == 0)
3834
0
              strcpybuff(back[i].r.msg, "Unable to resolve host name");
3835
0
          }
3836
0
        }
3837
3838
        // ---- FLAG READ MIS A UN?: POUR LA RECEPTION
3839
0
      }
3840
0
#endif
3841
0
#if USE_BEGINTHREAD
3842
      // ..rien à faire, c'est magic les threads
3843
#else
3844
      else if (back[i].status == STATUS_FTP_TRANSFER) { // en réception ftp
3845
        if (!fexist(back[i].location_buffer)) { // terminé
3846
          FILE *fp;
3847
3848
          fp =
3849
            FOPEN(fconcat(OPT_GET_BUFF(opt), back[i].location_buffer, ".ok"),
3850
                  "rb");
3851
          if (fp) {
3852
            back_read_ftp_result(fp, &back[i].r);
3853
            fclose(fp);
3854
            UNLINK(fconcat(OPT_GET_BUFF(opt), back[i].location_buffer, ".ok"));
3855
            strcpybuff(fconcat
3856
                       (OPT_GET_BUFF(opt), back[i].location_buffer, ".ok"), "");
3857
          } else {
3858
            strcpybuff(back[i].r.msg,
3859
                       "Unknown ftp result, check if file is ok");
3860
            back[i].r.statuscode = STATUSCODE_INVALID;
3861
          }
3862
          back[i].status = STATUS_READY;
3863
          back_set_finished(opt, sback, i);
3864
          // finalize transfer
3865
          if (back[i].r.statuscode > 0) {
3866
            hts_log_print(opt, LOG_TRACE, "finalizing ftp");
3867
            back_finalize(opt, cache, sback, i);
3868
          }
3869
        }
3870
      }
3871
#endif
3872
0
      else if (back[i].status == STATUS_FTP_READY) {    // ftp ready
3873
0
        back[i].status = STATUS_READY;
3874
0
        back_set_finished(opt, sback, i);
3875
        // finalize transfer
3876
0
        if (back[i].r.statuscode > 0) {
3877
0
          hts_log_print(opt, LOG_TRACE, "finalizing ftp");
3878
0
          back_finalize(opt, cache, sback, i);
3879
0
        }
3880
0
      } else if ((back[i].status > 0) && (back[i].status < 1000)) {     // en réception http
3881
0
        int dispo = 0;
3882
3883
        // vérifier l'existance de timeout-check
3884
0
        if (!gestion_timeout)
3885
0
          if (back[i].timeout > 0)
3886
0
            gestion_timeout = 1;
3887
3888
        // données dispo?
3889
        //## if (back[i].url_adr[0]!=lOCAL_CHAR)
3890
0
        if (back[i].r.is_file)
3891
0
          dispo = 1;
3892
#if HTS_USEOPENSSL
3893
        else if (back[i].r.ssl)
3894
          dispo = 1;
3895
#endif
3896
0
        else
3897
0
          dispo = FD_ISSET(back[i].r.soc, &fds);
3898
3899
        // Check transfer rate!
3900
0
        if (!max_read_bytes)
3901
0
          dispo = 0;            // limit transfer rate
3902
3903
0
        if (dispo) {            // données dispo
3904
0
          LLint retour_fread;
3905
3906
0
          busy_recv = 1;        // on récupère encore
3907
#if BDEBUG==1
3908
          printf("..data available on socket %d\n", back[i].r.soc);
3909
#endif
3910
3911
          // range size hack old location
3912
3913
0
#if HTS_DIRECTDISK
3914
          // Shortcut: store the file directly on disk when possible,
3915
          // sparing memory
3916
0
          if (back[i].status &&
3917
0
              !back[i].locked) { // name still pending when locked
3918
0
            if (back[i].r.is_write == 0) {      // mode mémoire
3919
0
              if (back[i].r.adr == NULL) {      // rien n'a été écrit
3920
0
                if (!back[i].testmode) {        // pas mode test
3921
0
                  if (strnotempty(back[i].url_sav)) {
3922
0
                    if (strcmp(back[i].url_fil, "/robots.txt")) {
3923
0
                      if (back[i].r.statuscode == HTTP_OK) {    // 'OK'
3924
0
                        if (!is_hypertext_mime(opt, back[i].r.contenttype, back[i].url_fil)) {  // pas HTML
3925
0
                          if (opt->getmode & HTS_GETMODE_NONHTML) {
3926
0
                            int fcheck = 0;
3927
0
                            int last_errno = 0;
3928
3929
0
                            back[i].r.is_write = 1;     // écrire
3930
                            /* a .gz/.br/.zst saved under its own coding stays
3931
                               packed on disk */
3932
0
                            if (back[i].r.compressed &&
3933
0
                                !hts_codec_is_archive_ext(
3934
0
                                    hts_codec_parse(back[i].r.contentencoding),
3935
0
                                    get_ext(catbuff, sizeof(catbuff),
3936
0
                                            back[i].url_sav))) {
3937
0
                              if (create_back_tmpfile(opt, &back[i], "z") ==
3938
0
                                  0) {
3939
0
                                assertf(back[i].tmpfile != NULL);
3940
                                /* note: tmpfile is utf-8 */
3941
0
                                if ((back[i].r.out =
3942
0
                                     FOPEN(back[i].tmpfile, "wb")) == NULL) {
3943
0
                                  last_errno = errno;
3944
0
                                }
3945
0
                              }
3946
0
                            } else {
3947
0
                              file_notify(opt, back[i].url_adr, back[i].url_fil,
3948
0
                                          back[i].url_sav, 1, 1,
3949
0
                                          back[i].r.notmodified);
3950
0
                              back[i].r.compressed = 0;
3951
0
                              back_refetch_backup(opt, &back[i]);
3952
0
                              if ((back[i].r.out =
3953
0
                                   filecreate(&opt->state.strc,
3954
0
                                              back[i].url_sav)) == NULL) {
3955
0
                                last_errno = errno;
3956
0
                              }
3957
0
                            }
3958
0
                            if (back[i].r.out == NULL) {
3959
0
                              errno = last_errno;
3960
0
                              if ((fcheck = check_fatal_io_errno())) {
3961
0
                                hts_log_print(opt, LOG_ERROR,
3962
0
                                              "Mirror aborted: disk full or filesystem problems");
3963
0
                                opt->state.exit_xh = -1;        /* fatal error */
3964
0
                              }
3965
0
                            }
3966
#if HDEBUG
3967
                            printf("direct-disk: %s\n", back[i].url_sav);
3968
#endif
3969
0
                            hts_log_print(opt, LOG_DEBUG,
3970
0
                                          "File received from net to disk: %s%s",
3971
0
                                          back[i].url_adr, back[i].url_fil);
3972
3973
0
                            if (back[i].r.out == NULL) {
3974
0
                              hts_log_print(opt, LOG_ERROR | LOG_ERRNO,
3975
0
                                            "Unable to save file %s",
3976
0
                                            back[i].url_sav);
3977
0
                              if (fcheck) {
3978
0
                                hts_log_print(opt, LOG_ERROR,
3979
0
                                              "* * Fatal write error, giving up");
3980
0
                              }
3981
0
                              back[i].r.is_write = 0;   // erreur, abandonner
3982
0
                              back[i].status = STATUS_READY;      // terminé
3983
0
                              back_set_finished(opt, sback, i);
3984
0
                              if (back[i].r.soc != INVALID_SOCKET) {
3985
0
                                deletehttp(&back[i].r);
3986
0
                                back[i].r.soc = INVALID_SOCKET;
3987
0
                              }
3988
0
                            } else {
3989
0
#ifndef _WIN32
3990
0
                              chmod(back[i].url_sav, HTS_ACCESS_FILE);
3991
0
#endif
3992
                              /* create a temporary reference file in case of broken mirror */
3993
0
                              if (back[i].r.out != NULL && opt->cache != 0) {
3994
0
                                if (back_serialize_ref(opt, &back[i]) != 0) {
3995
0
                                  hts_log_print(opt, LOG_WARNING,
3996
0
                                                "Could not create temporary reference file for %s%s",
3997
0
                                                back[i].url_adr,
3998
0
                                                back[i].url_fil);
3999
0
                                }
4000
0
                              }
4001
0
                            }
4002
0
                          } else { // on coupe tout!
4003
0
                            hts_log_print(opt, LOG_DEBUG,
4004
0
                                          "File cancelled (non HTML): %s%s",
4005
0
                                          back[i].url_adr, back[i].url_fil);
4006
0
                            back[i].status = STATUS_READY;      // terminé
4007
0
                            back_set_finished(opt, sback, i);
4008
0
                            if (!back[i].testmode)
4009
0
                              back[i].r.statuscode = STATUSCODE_INVALID;        // EUHH CANCEL
4010
0
                            else
4011
0
                              back[i].r.statuscode = STATUSCODE_TEST_OK;        // "TEST OK"
4012
0
                            if (back[i].r.soc != INVALID_SOCKET) {
4013
#if HTS_DEBUG_CLOSESOCK
4014
                              DEBUG_W("back_wait(3): deletehttp\n");
4015
#endif
4016
0
                              deletehttp(&back[i].r);
4017
0
                            }
4018
0
                            back[i].r.soc = INVALID_SOCKET;
4019
0
                          }
4020
0
                        }
4021
0
                      }
4022
0
                    }
4023
0
                  }
4024
0
                }
4025
0
              }
4026
0
            }
4027
0
          }
4028
0
#endif
4029
4030
          // réception de données depuis socket ou fichier
4031
0
          if (back[i].status) {
4032
0
            if (back[i].status == STATUS_WAIT_HEADERS)
4033
0
              retour_fread = http_xfread1(&(back[i].r), HTS_XFREAD_LINE_BLOCK);
4034
0
            else if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) {        // recevoir longueur chunk en hexa caractère par caractère
4035
              // backuper pour lire dans le buffer chunk
4036
0
              htsblk r;
4037
              /* Block mode bounds the trailer section, which declares no length
4038
                 of its own, by HTS_LINE_BLOCK_SIZE. */
4039
0
              const int chunk_read_mode = back_in_chunk_trailers(&back[i])
4040
0
                                              ? HTS_XFREAD_LINE_BLOCK
4041
0
                                              : HTS_XFREAD_LINE;
4042
4043
0
              memcpy(&r, &(back[i].r), sizeof(htsblk));
4044
0
              back[i].r.is_write = 0;   // mémoire
4045
0
              back[i].r.adr = back[i].chunk_adr;        // adresse
4046
0
              back[i].r.size = back[i].chunk_size;      // taille taille chunk
4047
0
              back[i].r.totalsize = -1; // total inconnu
4048
0
              back[i].r.out = NULL;
4049
0
              back[i].r.is_file = 0;
4050
              //
4051
              // one line, or the whole trailer block
4052
0
              retour_fread = http_xfread1(&(back[i].r), chunk_read_mode);
4053
              // modifier et restaurer
4054
0
              back[i].chunk_adr = back[i].r.adr;        // adresse
4055
0
              back[i].chunk_size = back[i].r.size;      // taille taille chunk
4056
0
              memcpy(&(back[i].r), &r, sizeof(htsblk)); // restaurer véritable r
4057
0
            } else if (back[i].is_chunk) {      // attention chunk, limiter taille à lire
4058
#if CHUNKDEBUG==1
4059
              printf("[%d] read %d bytes\n", (int) back[i].r.soc,
4060
                     (int) min(back[i].r.totalsize - back[i].r.size,
4061
                               max_read_bytes));
4062
#endif
4063
0
              retour_fread =
4064
0
                (int) http_xfread1(&(back[i].r),
4065
0
                                   (int) min(back[i].r.totalsize -
4066
0
                                             back[i].r.size, max_read_bytes));
4067
0
            } else
4068
0
              retour_fread =
4069
0
                (int) http_xfread1(&(back[i].r), (int) max_read_bytes);
4070
0
          } else
4071
0
            retour_fread = READ_EOF;    // interruption ou annulation interne (peut ne pas être une erreur)
4072
4073
          // Si réception chunk, tester si on est pas à la fin!
4074
          /* Skipped on a write error: these completion tests read r.size,
4075
             which counts bytes read, and would relaunder the error into EOF. */
4076
0
          if (back[i].status == 1 &&
4077
0
              !statuscode_is_write_error(back[i].r.statuscode)) {
4078
0
            if (back[i].is_chunk) {     // attendre prochain chunk
4079
0
              if (back[i].r.size == back[i].r.totalsize) { // fin chunk!
4080
0
                back[i].status = STATUS_CHUNK_CR;       /* fetch ending CRLF */
4081
0
                if (back[i].chunk_adr != NULL) {
4082
0
                  freet(back[i].chunk_adr);
4083
0
                  back[i].chunk_adr = NULL;
4084
0
                }
4085
0
                back[i].chunk_size = 0;
4086
0
                retour_fread = 0;       // pas d'erreur
4087
#if CHUNKDEBUG==1
4088
                printf("[%d] waiting for current chunk CRLF..\n",
4089
                       (int) back[i].r.soc);
4090
#endif
4091
0
              }
4092
0
            } else if (back[i].r.keep_alive) {
4093
0
              if (back[i].r.size == back[i].r.totalsize) {      // fin!
4094
0
                retour_fread = READ_EOF;        // end
4095
0
              }
4096
0
            }
4097
0
          }
4098
4099
0
          if (retour_fread < 0) {       // fin réception
4100
0
            back[i].status = STATUS_READY;      // terminé
4101
0
            back_set_finished(opt, sback, i);
4102
            /*KA back[i].r.soc=INVALID_SOCKET; */
4103
#if CHUNKDEBUG==1
4104
            if (back[i].is_chunk)
4105
              printf
4106
                ("[%d] must be the last chunk for %s (connection closed) - %d/%d\n",
4107
                 (int) back[i].r.soc, back[i].url_fil, back[i].r.size,
4108
                 back[i].r.totalsize);
4109
#endif
4110
0
            if (retour_fread < 0 && retour_fread != READ_EOF) {
4111
0
              if (statuscode_is_write_error(back[i].r.statuscode)) {
4112
                /* our disk, not the server: keep the write error rather than
4113
                   blame the transfer for what we could not store */
4114
0
                hts_log_print(opt, LOG_ERROR, "Unable to write file %s",
4115
0
                              back[i].url_sav);
4116
0
                if (back[i].r.statuscode == STATUSCODE_IO_FATAL) {
4117
                  /* disk full, not a network blink: a retry writes nothing and
4118
                     the purge would measure a truncated mirror */
4119
0
                  hts_log_print(
4120
0
                      opt, LOG_ERROR,
4121
0
                      "Mirror aborted: disk full or filesystem problems");
4122
0
                  opt->state.exit_xh = -1;
4123
0
                }
4124
0
              } else {
4125
0
                if (back[i].r.size > 0)
4126
0
                  strcpybuff(back[i].r.msg, "Interrupted transfer");
4127
0
                else
4128
0
                  strcpybuff(back[i].r.msg, "No data (connection closed)");
4129
0
                back[i].r.statuscode = STATUSCODE_CONNERROR;
4130
0
              }
4131
0
            } else if ((back[i].r.statuscode <= 0)
4132
0
                       && (strnotempty(back[i].r.msg) == 0)) {
4133
#if HDEBUG
4134
              printf("error interruped: %s\n", back[i].r.adr);
4135
#endif
4136
0
              if (back[i].r.size > 0)
4137
0
                strcpybuff(back[i].r.msg, "Interrupted transfer");
4138
0
              else
4139
0
                strcpybuff(back[i].r.msg, "No data (connection closed)");
4140
0
              back[i].r.statuscode = STATUSCODE_CONNERROR;
4141
0
            }
4142
            // Close socket
4143
0
            if (back[i].r.soc != INVALID_SOCKET) {
4144
#if HTS_DEBUG_CLOSESOCK
4145
              DEBUG_W("back_wait(4): deletehttp\n");
4146
#endif
4147
              /*KA deletehttp(&back[i].r); */
4148
0
              back_maydeletehttp(opt, cache, sback, i);
4149
0
            }
4150
            // finalize transfer
4151
0
            if (back[i].r.statuscode > 0 && !IS_DELAYED_EXT(back[i].url_sav)
4152
0
              ) {
4153
0
              hts_log_print(opt, LOG_TRACE, "finalizing regular file");
4154
0
              back_finalize(opt, cache, sback, i);
4155
0
            }
4156
4157
            /* Same treatment for an unterminated chunked stream: the byte count
4158
               agrees with the chunks that arrived, the framing does not. */
4159
0
            if (back[i].r.statuscode > 0 &&
4160
0
                back_chunked_unterminated(&back[i])) {
4161
0
              if (!opt->tolerant) {
4162
0
                deleteaddr(&back[i].r);
4163
0
                back[i].r.statuscode = STATUSCODE_CONNERROR; // recatch
4164
0
                htsblk_failf(&back[i].r,
4165
0
                             "Truncated chunked transfer (" LLintP
4166
0
                             " Bytes, terminating chunk missing)",
4167
0
                             (LLint) back[i].r.size);
4168
0
              } else {
4169
0
                hts_log_print(opt, LOG_WARNING,
4170
0
                              "Truncated chunked transfer (" LLintP
4171
0
                              " Bytes, terminating chunk missing) for %s%s",
4172
0
                              (LLint) back[i].r.size, back[i].url_adr,
4173
0
                              back[i].url_fil);
4174
0
              }
4175
0
            }
4176
4177
            /* A body cut short by a failed write is short by definition: keep
4178
               the write failure it is already classed as. */
4179
0
            if (back[i].r.totalsize >= 0 &&
4180
0
                !statuscode_is_write_error(back[i].r.statuscode)) {
4181
0
              if (back[i].r.totalsize != back[i].r.size) {      // pas la même!
4182
0
                if (!opt->tolerant) {
4183
0
                  deleteaddr(&back[i].r);
4184
0
                  if (back[i].r.size < back[i].r.totalsize)
4185
0
                    back[i].r.statuscode = STATUSCODE_CONNERROR;        // recatch
4186
0
                  htsblk_failf(&back[i].r,
4187
0
                               "Incorrect length (" LLintP " Bytes, " LLintP
4188
0
                               " expected)",
4189
0
                               (LLint) back[i].r.size,
4190
0
                               (LLint) back[i].r.totalsize);
4191
0
                } else {
4192
                  // Un warning suffira..
4193
0
                  hts_log_print(opt, LOG_WARNING,
4194
0
                                "Incorrect length (" LLintP "!=" LLintP
4195
0
                                " expected) for %s%s",
4196
0
                                (LLint) back[i].r.size,
4197
0
                                (LLint) back[i].r.totalsize, back[i].url_adr,
4198
0
                                back[i].url_fil);
4199
0
                }
4200
0
              }
4201
0
            }
4202
#if BDEBUG==1
4203
            printf("transfer ok\n");
4204
#endif
4205
0
          } else if (retour_fread > 0) {        // pas d'erreur de réception et data
4206
0
            if (back[i].timeout > 0) {  // refresh timeout si besoin est
4207
0
              back[i].timeout_refresh = time_local();
4208
0
            }
4209
            // Traitement des en têtes chunks ou en têtes
4210
0
            if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) {     // réception taille chunk en hexa (  après les en têtes, peut ne pas
4211
0
              const hts_boolean in_trailers = back_in_chunk_trailers(&back[i]);
4212
4213
              /* A chunk-size or chunk-CRLF line closes on its first LF, the
4214
                 trailer section on the blank line ending it. Two LFs mean a
4215
                 blank line only because the reader drops every CR. */
4216
0
              if (back[i].chunk_size > 0 &&
4217
0
                  back[i].chunk_adr[back[i].chunk_size - 1] == 10 &&
4218
0
                  (!in_trailers || back[i].chunk_size == 1 ||
4219
0
                   back[i].chunk_adr[back[i].chunk_size - 2] == 10)) {
4220
0
                int chunk_size = -1;
4221
0
                char chunk_data[64];
4222
4223
0
                if (in_trailers) {
4224
0
                  chunk_size =
4225
0
                      0; /* fields discarded, the blank line ends the body */
4226
0
                } else if (back[i].chunk_size < 32) { // not too big
4227
0
                  char *chstrip = back[i].chunk_adr;
4228
4229
0
                  back[i].chunk_adr[back[i].chunk_size - 1] = '\0';     // octet nul 
4230
                  // skip leading spaces or cr
4231
0
                  while(isspace(*chstrip))
4232
0
                    chstrip++;
4233
0
                  chunk_data[0] = '\0';
4234
0
                  strncatbuff(chunk_data, chstrip, sizeof(chunk_data) - 2);
4235
                  // strip chunk-extension
4236
0
                  while((chstrip = strchr(chunk_data, ';')))
4237
0
                    *chstrip = '\0';
4238
0
                  while((chstrip = strchr(chunk_data, ' ')))
4239
0
                    *chstrip = '\0';
4240
0
                  while((chstrip = strchr(chunk_data, '\r')))
4241
0
                    *chstrip = '\0';
4242
#if CHUNKDEBUG==1
4243
                  printf("[%d] chunk received and read: %s\n",
4244
                         (int) back[i].r.soc, chunk_data);
4245
#endif
4246
0
                  if (back[i].r.totalsize < 0)
4247
0
                    back[i].r.totalsize = 0;    // initialiser à 0 (-1 == unknown)
4248
0
                  if (back[i].status == STATUS_CHUNK_WAIT) {    // "real" chunk
4249
                    /* The chunk-size line is hostile input, so parse it wide
4250
                       and unsigned and drop anything an int cannot hold: sscanf
4251
                       "%x" lands 80000000 on INT_MIN, which sign-extends into a
4252
                       16EB realloc below and drives totalsize negative. */
4253
0
                    char *chunk_end = NULL;
4254
0
                    const unsigned long long chunk_value =
4255
0
                        strtoull(chunk_data, &chunk_end, 16);
4256
4257
0
                    if (chunk_end != chunk_data && *chunk_end == '\0' &&
4258
0
                        chunk_value <= (unsigned long long) INT32_MAX) {
4259
0
                      chunk_size = (int) chunk_value;
4260
0
                      if (chunk_size > 0)
4261
0
                        back[i].chunk_blocksize = chunk_size;   /* the data block chunk size */
4262
                      /* only a real 0 ends the stream; the bound above keeps a
4263
                         negative from ever claiming the sentinel (#840) */
4264
0
                      else if (chunk_size == 0)
4265
0
                        back[i].chunk_blocksize = -1;   /* ending */
4266
0
                      back[i].r.totalsize += chunk_size;        // noter taille
4267
0
                      if (back[i].r.adr != NULL || !back[i].r.is_write) {       // Not to disk
4268
                        /* A wider bound here buys the realloc that only the
4269
                           next read would refuse; an invalid chunk tears the
4270
                           transfer down. */
4271
0
                        if (!hts_inmem_size_fits(back[i].r.totalsize)) {
4272
0
                          hts_log_print(opt, LOG_WARNING,
4273
0
                                        "Chunked resource too large for %s%s",
4274
0
                                        back[i].url_adr, back[i].url_fil);
4275
0
                          chunk_size = -1;
4276
0
                        } else {
4277
0
                          back[i].r.adr = (char *) realloct(
4278
0
                              back[i].r.adr, (size_t) back[i].r.totalsize + 1);
4279
0
                          if (!back[i].r.adr) {
4280
0
                            if (cache->log != NULL) {
4281
0
                              hts_log_print(opt, LOG_ERROR,
4282
0
                                            "not enough memory (" LLintP
4283
0
                                            ") for %s%s",
4284
0
                                            (LLint) back[i].r.totalsize,
4285
0
                                            back[i].url_adr, back[i].url_fil);
4286
0
                            }
4287
0
                          }
4288
0
                        }
4289
0
                      }
4290
#if CHUNKDEBUG==1
4291
                      printf("[%d] chunk length: %d - next total " LLintP ":\n",
4292
                             (int) back[i].r.soc, (int) chunk_size,
4293
                             (LLint) back[i].r.totalsize);
4294
#endif
4295
0
                    } else {
4296
0
                      hts_log_print(opt, LOG_WARNING,
4297
0
                                    "Illegal chunk (%s) for %s%s",
4298
0
                                    back[i].chunk_adr, back[i].url_adr,
4299
0
                                    back[i].url_fil);
4300
0
                    }
4301
0
                  } else {      /* back[i].status==STATUS_CHUNK_CR : just receiving ending CRLF after data */
4302
0
                    if (chunk_data[0] == '\0') {
4303
0
                      if (back[i].chunk_blocksize > 0)
4304
0
                        chunk_size = (int) back[i].chunk_blocksize;     /* recent data chunk size */
4305
0
                      else if (back[i].chunk_blocksize == -1)
4306
0
                        chunk_size = 0; /* ending chunk */
4307
0
                      else
4308
0
                        chunk_size = 1; /* fake positive size for 1st chunk history */
4309
#if CHUNKDEBUG==1
4310
                      printf("[%d] chunk CRLF seen\n", (int) back[i].r.soc);
4311
#endif
4312
0
                    } else {
4313
0
                      hts_log_print(opt, LOG_WARNING,
4314
0
                                    "illegal chunk CRLF (%s) for %s%s",
4315
0
                                    back[i].chunk_adr, back[i].url_adr,
4316
0
                                    back[i].url_fil);
4317
#if CHUNKDEBUG==1
4318
                      printf("[%d] chunk CRLF ERROR!! : '%s'\n",
4319
                             (int) back[i].r.soc, chunk_data);
4320
#endif
4321
0
                    }
4322
0
                  }
4323
0
                } else {
4324
0
                  hts_log_print(opt, LOG_WARNING,
4325
0
                                "chunk too big (" LLintP ") for %s%s",
4326
0
                                (LLint) back[i].chunk_size, back[i].url_adr,
4327
0
                                back[i].url_fil);
4328
0
                }
4329
4330
                // ok, continuer sur le body
4331
4332
                // si chunk non nul continuer (ou commencer)
4333
0
                if (back[i].status == STATUS_CHUNK_CR && chunk_size > 0) {
4334
0
                  back[i].status = STATUS_CHUNK_WAIT;   /* waiting for next chunk (NN\r\n<data>\r\nNN\r\n<data>..\r\n0\r\n\r\n) */
4335
#if CHUNKDEBUG==1
4336
                  printf("[%d] waiting for next chunk\n", (int) back[i].r.soc);
4337
#endif
4338
0
                } else if (back[i].status == STATUS_CHUNK_WAIT && chunk_size == 0) {    /* final chunk */
4339
0
                  back[i].status = STATUS_CHUNK_CR;     /* final CRLF */
4340
#if CHUNKDEBUG==1
4341
                  printf("[%d] waiting for final CRLF (chunk)\n",
4342
                         (int) back[i].r.soc);
4343
#endif
4344
0
                } else if (back[i].status == STATUS_CHUNK_WAIT && chunk_size >= 0) {    /* will fetch data now */
4345
0
                  back[i].status = 1;   // continuer body    
4346
#if CHUNKDEBUG==1
4347
                  printf("[%d] waiting for body (chunk)\n",
4348
                         (int) back[i].r.soc);
4349
#endif
4350
0
                } else {        /* zero-size-chunk-CRLF (end) or error */
4351
#if CHUNKDEBUG==1
4352
                  printf("[%d] chunk end, total: %d\n", (int) back[i].r.soc,
4353
                         back[i].r.size);
4354
#endif
4355
                  /* End */
4356
0
                  back[i].status = STATUS_READY; // fin
4357
0
                  back_set_finished(opt, sback, i);
4358
4359
                  // finalize transfer if not temporary
4360
0
                  if (!IS_DELAYED_EXT(back[i].url_sav)) {
4361
0
                    hts_log_print(opt, LOG_TRACE, "finalizing at chunk end");
4362
0
                    back_finalize(opt, cache, sback, i);
4363
0
                  } else {
4364
0
                    if (back[i].r.statuscode == HTTP_OK) {
4365
0
                      hts_log_print(opt, LOG_WARNING,
4366
0
                                    "unexpected incomplete type with 200 code at %s%s",
4367
0
                                    back[i].url_adr, back[i].url_fil);
4368
0
                    }
4369
0
                  }
4370
0
                  if (back[i].r.soc != INVALID_SOCKET) {
4371
#if HTS_DEBUG_CLOSESOCK
4372
                    DEBUG_W("back_wait(5): deletehttp\n");
4373
#endif
4374
                    /* Error */
4375
0
                    if (chunk_size < 0) {
4376
0
                      deletehttp(&back[i].r);
4377
0
                      back[i].r.soc = INVALID_SOCKET;
4378
0
                      deleteaddr(&back[i].r);
4379
0
                      back[i].r.statuscode = STATUSCODE_INVALID;
4380
0
                      strcpybuff(back[i].r.msg, "Invalid chunk");
4381
#if CHUNKDEBUG==1
4382
                      printf("[%d] chunk error\n", (int) back[i].r.soc);
4383
#endif
4384
0
                    } else {    /* if chunk_size == 0 */
4385
4386
#if CHUNKDEBUG==1
4387
                      printf("[%d] all chunks now received\n",
4388
                             (int) back[i].r.soc);
4389
#endif
4390
4391
                      /* Tester totalsize en fin de chunk */
4392
0
                      if ((back[i].r.totalsize >= 0)) { // tester totalsize
4393
0
                        if (back[i].r.totalsize != back[i].r.size) {    // pas la même!
4394
0
                          if (!opt->tolerant) {
4395
0
                            deleteaddr(&back[i].r);
4396
0
                            back[i].r.statuscode = STATUSCODE_INVALID;
4397
0
                            strcpybuff(back[i].r.msg, "Incorrect length");
4398
0
                          } else {
4399
                            // Un warning suffira..
4400
0
                            hts_log_print(opt, LOG_WARNING,
4401
0
                                          "Incorrect length (" LLintP "!="
4402
0
                                          LLintP " expected) for %s%s",
4403
0
                                          (LLint) back[i].r.size,
4404
0
                                          (LLint) back[i].r.totalsize,
4405
0
                                          back[i].url_adr, back[i].url_fil);
4406
0
                          }
4407
0
                        }
4408
0
                      }
4409
0
                    }
4410
4411
0
                  }
4412
0
                }
4413
4414
                // effacer buffer (chunk en tete)
4415
0
                if (back[i].chunk_adr != NULL) {
4416
0
                  freet(back[i].chunk_adr);
4417
0
                  back[i].chunk_adr = NULL;
4418
0
                  back[i].chunk_size = 0;
4419
                  // NO! xxback[i].chunk_blocksize = 0;
4420
0
                }
4421
4422
0
              } // chunk buffer holds a complete line
4423
              //
4424
0
            } else if (back[i].status == STATUS_WAIT_HEADERS) { // en têtes (avant le chunk si il est présent)
4425
              //
4426
0
              if (back[i].r.size >= 2) {
4427
                // double LF
4428
0
                if (((back[i].r.adr[back[i].r.size - 1] == 10)
4429
0
                     && (back[i].r.adr[back[i].r.size - 2] == 10))
4430
0
                    || (back[i].r.adr[0] == '<')        /* bogus server */
4431
0
                  ) {
4432
0
                  char rcvd[2048];
4433
0
                  int ptr = 0;
4434
0
                  int adv = 0;
4435
0
                  int noFreebuff = 0;
4436
4437
#if BDEBUG==1
4438
                  printf("..ok, header received\n");
4439
#endif
4440
4441
                  /* Hack for zero-length headers */
4442
0
                  if (back[i].status != 0 && back[i].r.adr[0] != '<') {
4443
4444
                    // ----------------------------------------
4445
                    // traiter en-tête!
4446
                    // status-line à récupérer
4447
0
                    binput_line(back[i].r.adr + ptr,
4448
0
                                back[i].r.adr + back[i].r.size, rcvd, 2000,
4449
0
                                &adv);
4450
0
                    ptr += adv;
4451
0
                    if (strnotempty(rcvd) == 0) {
4452
                      /* Bogus CRLF, OR recycled connection and trailing chunk CRLF */
4453
0
                      binput_line(back[i].r.adr + ptr,
4454
0
                                  back[i].r.adr + back[i].r.size, rcvd, 2000,
4455
0
                                  &adv);
4456
0
                      ptr += adv;
4457
0
                    }
4458
                    // traiter status-line
4459
0
                    treatfirstline(&back[i].r, rcvd);
4460
4461
#if HDEBUG
4462
                    printf("(Buffer) Status-Code=%d\n", back[i].r.statuscode);
4463
#endif
4464
0
                    if (_DEBUG_HEAD) {
4465
0
                      if (ioinfo) {
4466
0
                        fprintf(ioinfo,
4467
0
                                "[%d] response for %s%s:\r\ncode=%d\r\n",
4468
0
                                back[i].r.debugid,
4469
0
                                jump_identification_const(back[i].url_adr),
4470
0
                                back[i].url_fil, back[i].r.statuscode);
4471
0
                        fprintfio(ioinfo, back[i].r.adr, ">>> ");
4472
0
                        fprintf(ioinfo, "\r\n");
4473
0
                        fflush(ioinfo);
4474
0
                      }         // en-tête
4475
0
                    }
4476
                    // header // ** !attention! HTTP/0.9 non supporté
4477
0
                    do {
4478
0
                      const hts_boolean cut = binput_line(
4479
0
                          back[i].r.adr + ptr, back[i].r.adr + back[i].r.size,
4480
0
                          rcvd, 2000, &adv);
4481
4482
0
                      ptr += adv;
4483
0
                      if (cut) {
4484
                        /* not what the server sent: parsing it would follow a
4485
                           truncated Location, or read its tail as headers */
4486
0
                        hts_log_print(opt, LOG_WARNING,
4487
0
                                      "Over-long header dropped for %s%s",
4488
0
                                      back[i].url_adr, back[i].url_fil);
4489
0
                        continue;
4490
0
                      }
4491
#if HDEBUG
4492
                      printf("(buffer)>%s\n", rcvd);
4493
#endif
4494
                      /*
4495
                         if (_DEBUG_HEAD) {
4496
                         if (ioinfo) {
4497
                         fprintf(ioinfo,"(buffer)>%s\r\n",rcvd);      
4498
                         fflush(ioinfo);
4499
                         }
4500
                         }
4501
                       */
4502
4503
0
                      if (strnotempty(rcvd))
4504
0
                        treathead(opt->cookie, back[i].url_adr, back[i].url_fil, &back[i].r, rcvd);     // traiter
4505
4506
                      // parfois les serveurs buggés renvoient un content-range avec un 200
4507
0
                      if (back[i].r.statuscode == HTTP_OK)      // 'OK'
4508
0
                        if (strfield(rcvd, "content-range:")) { // Avec un content-range: relisez les RFC..
4509
                          // Fake range (the file is complete)
4510
0
                          if (!
4511
0
                              (back[i].r.crange_start == 0
4512
0
                               && back[i].r.crange_end ==
4513
0
                               back[i].r.crange - 1)) {
4514
0
                            back[i].r.statuscode = HTTP_PARTIAL_CONTENT;        // FORCER A 206 !!!!!
4515
0
                          }
4516
0
                        }
4517
4518
0
                    } while(strnotempty(rcvd));
4519
                    // ----------------------------------------                    
4520
4521
0
                  } else {
4522
                    // assume text/html, OK
4523
0
                    treatfirstline(&back[i].r, back[i].r.adr);
4524
0
                    noFreebuff = 1;
4525
0
                  }
4526
4527
                  // Callback
4528
0
                  {
4529
0
                    int test_head = RUN_CALLBACK6(opt, receivehead,
4530
0
                                                  back[i].r.adr,
4531
0
                                                  back[i].url_adr,
4532
0
                                                  back[i].url_fil,
4533
0
                                                  back[i].referer_adr,
4534
0
                                                  back[i].referer_fil,
4535
0
                                                  &back[i].r);
4536
0
                    if (test_head != 1) {
4537
0
                      hts_log_print(opt, LOG_WARNING,
4538
0
                                    "External wrapper aborted transfer, breaking connection: %s%s",
4539
0
                                    back[i].url_adr, back[i].url_fil);
4540
0
                      back[i].status = STATUS_READY;    // FINI
4541
0
                      back_set_finished(opt, sback, i);
4542
0
                      deletehttp(&back[i].r);
4543
0
                      back[i].r.soc = INVALID_SOCKET;
4544
0
                      strcpybuff(back[i].r.msg,
4545
0
                                 "External wrapper aborted transfer");
4546
0
                      back[i].r.statuscode = STATUSCODE_INVALID;
4547
0
                    }
4548
0
                  }
4549
4550
                  // Free headers memory now
4551
                  // Actually, save them for informational purpose
4552
0
                  if (!noFreebuff) {
4553
0
                    char *block = back[i].r.adr;
4554
4555
0
                    back[i].r.adr = NULL;
4556
0
                    deleteaddr(&back[i].r);
4557
0
                    back[i].r.headers = block;
4558
0
                  }
4559
                  // Stash the raw response headers for WARC (deletehttp frees
4560
                  // r.headers when the socket closes, before back_finalize)
4561
0
                  if (StringNotEmpty(opt->warc_file))
4562
0
                    warc_stash_response(&back[i].r, back[i].r.headers);
4563
4564
                  /* 
4565
                     Status code and header-response hacks
4566
                   */
4567
4568
                  // Check response : 203 == 200
4569
0
                  if (back[i].r.statuscode ==
4570
0
                      HTTP_NON_AUTHORITATIVE_INFORMATION) {
4571
0
                    back[i].r.statuscode = HTTP_OK;     // forcer "OK"
4572
0
                  } else if (back[i].r.statuscode == HTTP_CONTINUE) {
4573
0
                    back[i].status = STATUS_WAIT_HEADERS;
4574
0
                    back[i].r.size = 0;
4575
0
                    back[i].r.totalsize = -1;
4576
0
                    back[i].chunk_size = 0;
4577
0
                    back[i].r.statuscode = STATUSCODE_INVALID;
4578
0
                    back[i].r.msg[0] = '\0';
4579
0
                    hts_log_print(opt, LOG_DEBUG,
4580
0
                                  "Status 100 detected for %s%s, continuing headers",
4581
0
                                  back[i].url_adr, back[i].url_fil);
4582
0
                    continue;
4583
0
                  }
4584
4585
                  // The server really sent 304 here; the *-hacks below force
4586
                  // NOT_MODIFIED only after confirming the file is complete.
4587
0
                  const hts_boolean server_sent_304 =
4588
0
                      (back[i].r.statuscode == HTTP_NOT_MODIFIED);
4589
4590
                  /*
4591
                     Solve "false" 416 problems
4592
                   */
4593
0
                  if (back[i].r.statuscode == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE) {   // 'Requested Range Not Satisfiable'
4594
                    // Example:
4595
                    // Range: bytes=2830-
4596
                    // ->
4597
                    // Content-Range: bytes */2830
4598
0
                    if (back[i].range_req_size == back[i].r.crange) {
4599
0
                      filenote(&opt->state.strc, back[i].url_sav, NULL);
4600
0
                      file_notify(opt, back[i].url_adr, back[i].url_fil,
4601
0
                                  back[i].url_sav, 0, 0, back[i].r.notmodified);
4602
0
                      deletehttp(&back[i].r);
4603
0
                      back[i].r.soc = INVALID_SOCKET;
4604
0
                      back[i].status = STATUS_READY;    // READY
4605
0
                      back_set_finished(opt, sback, i);
4606
0
                      back[i].r.size = back[i].r.totalsize =
4607
0
                        back[i].range_req_size;
4608
0
                      back[i].r.statuscode = HTTP_NOT_MODIFIED; // NOT MODIFIED
4609
0
                      hts_log_print(
4610
0
                          opt, LOG_NOTICE,
4611
0
                          "Kept existing file %s (" LLintP
4612
0
                          " bytes), matching size and timestamp: %s%s",
4613
0
                          back[i].url_sav, (LLint) back[i].range_req_size,
4614
0
                          back[i].url_adr, back[i].url_fil);
4615
0
                    }
4616
0
                  }
4617
                  // transform 406 into 200 ; we'll catch embedded links inside the choice page
4618
0
                  if (back[i].r.statuscode == 406) {    // 'Not Acceptable'
4619
0
                    back[i].r.statuscode = HTTP_OK;
4620
0
                  }
4621
                  // On update, keep the good copy on error (mask as 304); skip
4622
                  // resume paths so a stale-partial 416 still re-fetches.
4623
0
                  if (HTTP_IS_ERROR(back[i].r.statuscode) &&
4624
0
                      back[i].is_update && !back[i].testmode &&
4625
0
                      back[i].range_req_size == 0 && back[i].url_sav[0] &&
4626
0
                      fexist_utf8(back[i].url_sav)) {
4627
0
                    hts_log_print(opt, LOG_NOTICE,
4628
0
                                  "Kept existing file %s after error %d (%s) "
4629
0
                                  "on update: %s%s",
4630
0
                                  back[i].url_sav, back[i].r.statuscode,
4631
0
                                  back[i].r.msg, back[i].url_adr,
4632
0
                                  back[i].url_fil);
4633
0
                    back[i].r.statuscode = HTTP_NOT_MODIFIED;
4634
0
                    deletehttp(&back[i].r);
4635
0
                    back[i].r.soc = INVALID_SOCKET;
4636
0
                  }
4637
                  // Various hacks to limit re-transfers when updating a mirror
4638
                  // Force update if same size detected
4639
0
                  if (opt->sizehack) {
4640
                    // We already have the file
4641
                    // and ask the remote server for an update
4642
                    // Some servers, especially dynamic pages severs, always
4643
                    // answer that the page has been modified since last visit
4644
                    // And answer with a 200 (OK) response, and the same page
4645
                    // If the size is the same, and the option has been set, we assume
4646
                    // that the file is identical - and therefore let's break the connection
4647
0
                    if (back[i].is_update) {    // mise à jour
4648
                      // only a file stored verbatim, as in the two blocks below
4649
0
                      if (back[i].r.statuscode == HTTP_OK &&
4650
0
                          !back[i].testmode &&
4651
0
                          !is_hypertext_mime(opt, back[i].r.contenttype,
4652
0
                                             back[i].url_fil) &&
4653
0
                          strnotempty(back[i].url_sav)) {
4654
0
                        htsblk r = cache_read(opt, cache, back[i].url_adr, back[i].url_fil, NULL, NULL);        // lire entrée cache
4655
4656
0
                        if (r.statuscode == HTTP_OK) {  // OK pas d'erreur cache
4657
0
                          LLint len1, len2;
4658
0
                          const LLint ondisk = fsize_utf8(back[i].url_sav);
4659
4660
0
                          len1 = r.totalsize;
4661
0
                          len2 = back[i].r.totalsize;
4662
0
                          if (r.size > 0)
4663
0
                            len1 = r.size;
4664
0
                          if (len1 >= 0) {
4665
                            // the cache size records a past fetch, so the
4666
                            // copy on disk has to still agree with it
4667
0
                            if (len1 == len2 && ondisk == len2) {
4668
0
                              back[i].r.statuscode = HTTP_NOT_MODIFIED; // forcer NOT MODIFIED
4669
0
                              deletehttp(&back[i].r);
4670
0
                              back[i].r.soc = INVALID_SOCKET;
4671
0
                              hts_log_print(
4672
0
                                  opt, LOG_NOTICE,
4673
0
                                  "Kept existing file %s (" LLintP
4674
0
                                  " bytes), matching the cached size: %s%s",
4675
0
                                  back[i].url_sav, (LLint) len1,
4676
0
                                  back[i].url_adr, back[i].url_fil);
4677
0
                            }
4678
0
                          }
4679
0
                        } else {
4680
0
                          hts_log_print(opt, LOG_WARNING,
4681
0
                                        "File seems complete (same size), but there was a cache read error (%u): %s%s",
4682
0
                                        r.statuscode, back[i].url_adr,
4683
0
                                        back[i].url_fil);
4684
0
                        }
4685
0
                        if (r.adr) {
4686
0
                          freet(r.adr);
4687
0
                          r.adr = NULL;
4688
0
                        }
4689
0
                      }
4690
0
                    }
4691
0
                  }
4692
                  // Various hacks to limit re-transfers when updating a mirror
4693
                  // Detect already downloaded file (with another browser, for example)
4694
0
                  if (opt->sizehack) {
4695
0
                    if (!back[i].is_update) {   // mise à jour
4696
0
                      if (back[i].r.statuscode == HTTP_OK && !back[i].testmode) {       // 'OK'
4697
0
                        if (!is_hypertext_mime(opt, back[i].r.contenttype, back[i].url_fil)) {  // not HTML
4698
0
                          if (strnotempty(back[i].url_sav)) {   // target found
4699
0
                            LLint size = fsize_utf8(back[i].url_sav);
4700
4701
0
                            if (size >= 0) {
4702
0
                              if (back[i].r.totalsize == size) {        // same size!
4703
0
                                deletehttp(&back[i].r);
4704
0
                                back[i].r.soc = INVALID_SOCKET;
4705
0
                                back[i].status = STATUS_READY;  // READY
4706
0
                                back_set_finished(opt, sback, i);
4707
0
                                back[i].r.size = back[i].r.totalsize;
4708
0
                                filenote(&opt->state.strc, back[i].url_sav,
4709
0
                                         NULL);
4710
0
                                file_notify(opt, back[i].url_adr,
4711
0
                                            back[i].url_fil, back[i].url_sav, 0,
4712
0
                                            0, back[i].r.notmodified);
4713
0
                                back[i].r.statuscode = HTTP_NOT_MODIFIED;       // NOT MODIFIED
4714
0
                                hts_log_print(opt, LOG_NOTICE,
4715
0
                                              "Kept existing file %s (" LLintP
4716
0
                                              " bytes), matching the announced "
4717
0
                                              "size: %s%s",
4718
0
                                              back[i].url_sav, (LLint) size,
4719
0
                                              back[i].url_adr, back[i].url_fil);
4720
0
                              }
4721
0
                            }
4722
0
                          }
4723
0
                        }
4724
0
                      }
4725
0
                    }
4726
0
                  }
4727
                  // Various hacks to limit re-transfers when updating a mirror
4728
                  // Detect bad range: header
4729
0
                  if (opt->sizehack) {
4730
                    // We have request for a partial file (with a 'Range: NNN-' header)
4731
                    // and received a complete file notification (200), with 'Content-length: NNN'
4732
                    // it might be possible that we had the complete file
4733
                    // this is the case in *most* cases, so break the connection
4734
0
                    if (back[i].r.is_write == 0) {      // mode mémoire
4735
0
                      if (back[i].r.adr == NULL) {      // rien n'a été écrit
4736
0
                        if (!back[i].testmode) {        // pas mode test
4737
0
                          if (strnotempty(back[i].url_sav)) {
4738
0
                            if (strcmp(back[i].url_fil, "/robots.txt")) {
4739
0
                              if (back[i].r.statuscode == HTTP_OK) {    // 'OK'
4740
0
                                if (!is_hypertext_mime(opt, back[i].r.contenttype, back[i].url_fil)) {  // pas HTML
4741
0
                                  if (back[i].r.statuscode == HTTP_OK) {        // "OK"
4742
0
                                    if (back[i].range_req_size > 0) {   // but Range: requested
4743
0
                                      if (back[i].range_req_size == back[i].r.totalsize) {      // And same size
4744
#if HTS_DEBUG_CLOSESOCK
4745
                                        DEBUG_W
4746
                                          ("back_wait(skip_range): deletehttp\n");
4747
#endif
4748
0
                                        deletehttp(&back[i].r);
4749
0
                                        back[i].r.soc = INVALID_SOCKET;
4750
0
                                        back[i].status = STATUS_READY;  // READY
4751
0
                                        back_set_finished(opt, sback, i);
4752
0
                                        back[i].r.size = back[i].r.totalsize;
4753
0
                                        filenote(&opt->state.strc,
4754
0
                                                 back[i].url_sav, NULL);
4755
0
                                        file_notify(opt, back[i].url_adr,
4756
0
                                                    back[i].url_fil,
4757
0
                                                    back[i].url_sav, 0, 0,
4758
0
                                                    back[i].r.notmodified);
4759
0
                                        back[i].r.statuscode = HTTP_NOT_MODIFIED;       // NOT MODIFIED
4760
0
                                        hts_log_print(
4761
0
                                            opt, LOG_NOTICE,
4762
0
                                            "Kept existing file %s (" LLintP
4763
0
                                            " bytes), matching the announced "
4764
0
                                            "size, range ignored: %s%s",
4765
0
                                            back[i].url_sav,
4766
0
                                            (LLint) back[i].range_req_size,
4767
0
                                            back[i].url_adr, back[i].url_fil);
4768
0
                                      }
4769
0
                                    }
4770
0
                                  }
4771
4772
0
                                }
4773
0
                              }
4774
0
                            }
4775
0
                          }
4776
0
                        }
4777
0
                      }
4778
0
                    }
4779
0
                  }
4780
                  // END - Various hacks to limit re-transfers when updating a mirror
4781
4782
                  /* 
4783
                     End of status code and header-response hacks
4784
                   */
4785
4786
                  /* Interdiction taille par le wizard? */
4787
0
                  if (back[i].r.soc != INVALID_SOCKET) {
4788
0
                    if (!back_checksize(opt, &back[i], 1)) {
4789
0
                      back[i].status = STATUS_READY;    // FINI
4790
0
                      back_set_finished(opt, sback, i);
4791
0
                      back[i].r.statuscode = STATUSCODE_TOO_BIG;
4792
0
                      deletehttp(&back[i].r);
4793
0
                      back[i].r.soc = INVALID_SOCKET;
4794
0
                      if (!back[i].testmode)
4795
0
                        strcpybuff(back[i].r.msg, "File too big");
4796
0
                      else
4797
0
                        strcpybuff(back[i].r.msg, "Test: File too big");
4798
0
                    }
4799
0
                  }
4800
4801
                  // Out-of-protocol 304 to a Range resume: the file is still
4802
                  // partial, so drop it and refetch instead of trusting it.
4803
0
                  if (server_sent_304 && back[i].range_req_size > 0) {
4804
0
                    url_savename_refname_remove(opt, back[i].url_adr,
4805
0
                                                back[i].url_fil);
4806
0
                    UNLINK(back[i].url_sav);
4807
0
                    deletehttp(&back[i].r);
4808
0
                    back[i].r.soc = INVALID_SOCKET;
4809
0
                    back[i].r.statuscode = STATUSCODE_NON_FATAL;
4810
0
                    back[i].r.refetch_wholefile =
4811
0
                        HTS_TRUE; // retry whole, no Range (#581)
4812
0
                    strcpybuff(back[i].r.msg,
4813
0
                               "Bogus 304 on resume, restarting");
4814
0
                    back[i].status = STATUS_READY;
4815
0
                    back_set_finished(opt, sback, i);
4816
0
                  }
4817
4818
                  /* sinon, continuer */
4819
                  /* if (back[i].r.soc!=INVALID_SOCKET) {   // ok récupérer body? */
4820
                  // head: terminé
4821
0
                  if (back[i].head_request) {
4822
0
                    hts_log_print(opt, LOG_DEBUG, "Tested file: %s%s",
4823
0
                                  back[i].url_adr, back[i].url_fil);
4824
#if HTS_DEBUG_CLOSESOCK
4825
                    DEBUG_W("back_wait(head request): deletehttp\n");
4826
#endif
4827
                    // Couper connexion
4828
0
                    if (!back[i].http11) {      /* NO KA */
4829
0
                      deletehttp(&back[i].r);
4830
0
                      back[i].r.soc = INVALID_SOCKET;
4831
0
                    }
4832
0
                    back[i].status = STATUS_READY;      // terminé
4833
0
                    back_set_finished(opt, sback, i);
4834
0
                  }
4835
                  // traiter une éventuelle erreur 304 (cache à jour utilisable)
4836
0
                  else if (back[i].r.statuscode == HTTP_NOT_MODIFIED) { // document à jour dans le cache
4837
                    // lire dans le cache
4838
                    // ** NOTE: pas de vérif de la taille ici!!
4839
#if HTS_DEBUG_CLOSESOCK
4840
                    DEBUG_W("back_wait(file is not modified): deletehttp\n");
4841
#endif
4842
                    /* clear everything but connection: switch, close, and reswitch */
4843
0
                    {
4844
0
                      htsblk tmp;
4845
4846
0
                      memset(&tmp, 0, sizeof(tmp));
4847
0
                      back_connxfr(&back[i].r, &tmp);
4848
                      /* a real 304's headers belong to the revisit record, so
4849
                         they must survive the swap (#826); a forced one has
4850
                         none */
4851
0
                      if (server_sent_304)
4852
0
                        warc_move_request(&back[i].r, &tmp);
4853
                      /* the cache entry overwrites the whole struct, so drop
4854
                         what the 304 response still owns first (#782) */
4855
0
                      back_free_response(&back[i].r);
4856
0
                      back[i].r =
4857
0
                        cache_read(opt, cache, back[i].url_adr, back[i].url_fil,
4858
0
                                   back[i].url_sav, back[i].location_buffer);
4859
0
                      back[i].r.location = back[i].location_buffer;
4860
0
                      back_connxfr(&tmp, &back[i].r);
4861
0
                      warc_move_request(&tmp, &back[i].r);
4862
0
                    }
4863
4864
                    // hack:
4865
                    // In case of 'if-unmodified-since' hack, a 304 status can be sent
4866
                    // then, force 'ok' status
4867
0
                    if (back[i].r.statuscode == STATUSCODE_INVALID) {
4868
0
                      if (fexist_utf8(back[i].url_sav)) {
4869
0
                        back[i].r.statuscode = HTTP_OK; // OK
4870
0
                        strcpybuff(back[i].r.msg, "OK (cached)");
4871
0
                        back[i].r.is_file = 1;
4872
0
                        back[i].r.totalsize = back[i].r.size =
4873
0
                          fsize_utf8(back[i].url_sav);
4874
0
                        get_httptype_sized(opt, back[i].r.contenttype,
4875
0
                                           sizeof(back[i].r.contenttype),
4876
0
                                           back[i].url_sav, 1);
4877
0
                        hts_log_print(opt, LOG_DEBUG,
4878
0
                                      "Not-modified status without cache guessed: %s%s",
4879
0
                                      back[i].url_adr, back[i].url_fil);
4880
0
                      }
4881
0
                    }
4882
                    // Status is okay?
4883
0
                    if (back[i].r.statuscode != -1) {   // pas d'erreur de lecture
4884
0
                      back[i].status = STATUS_READY;    // OK prêt
4885
0
                      back_set_finished(opt, sback, i);
4886
0
                      back[i].r.notmodified = 1;        // NON modifié!
4887
                      // WARC must not claim a 304 the server never sent (#839)
4888
0
                      back[i].r.warc_forced_notmodified =
4889
0
                          server_sent_304 ? HTS_FALSE : HTS_TRUE;
4890
0
                      hts_log_print(opt, LOG_DEBUG,
4891
0
                                    "File loaded after test from cache: %s%s",
4892
0
                                    back[i].url_adr, back[i].url_fil);
4893
4894
                      // finalize
4895
0
                      if (back[i].r.statuscode > 0) {
4896
0
                        hts_log_print(opt, LOG_TRACE, "finalizing after cache load");
4897
0
                        back_finalize(opt, cache, sback, i);
4898
0
                      }
4899
#if DEBUGCA
4900
                      printf("..document à jour après requète: %s%s\n",
4901
                             back[i].url_adr, back[i].url_fil);
4902
#endif
4903
4904
0
                    } else {    // erreur
4905
0
                      back[i].status = STATUS_READY;    // terminé
4906
0
                      back_set_finished(opt, sback, i);
4907
0
                    }
4908
4909
0
                  }
4910
                  // MIME type excluded by a -mime: filter: abort, don't fetch
4911
                  // the body (#58)
4912
0
                  else if (HTTP_IS_OK(back[i].r.statuscode) &&
4913
0
                           !back[i].testmode &&
4914
0
                           strnotempty(back[i].r.contenttype) &&
4915
0
                           hts_acceptmime(opt, 0, back[i].url_adr,
4916
0
                                          back[i].url_fil,
4917
0
                                          back[i].r.contenttype) == 1) {
4918
0
                    deletehttp(&back[i].r);
4919
0
                    back[i].r.soc = INVALID_SOCKET;
4920
0
                    back[i].status = STATUS_READY;
4921
0
                    back_set_finished(opt, sback, i);
4922
0
                    back[i].r.statuscode = STATUSCODE_EXCLUDED;
4923
0
                    strcpybuff(back[i].r.msg, "Excluded by MIME type filter");
4924
0
                    hts_log_print(
4925
0
                        opt, LOG_NOTICE,
4926
0
                        "File excluded by MIME type filter (%s): %s%s",
4927
0
                        back[i].r.contenttype, back[i].url_adr,
4928
0
                        back[i].url_fil);
4929
0
                  } else { // il faut aller le chercher
4930
4931
                    // effacer buffer (requète)
4932
0
                    if (!noFreebuff) {
4933
0
                      deleteaddr(&back[i].r);
4934
0
                      back[i].r.size = 0;
4935
0
                    }
4936
                    // traiter 206 (partial content)
4937
                    // xxc SI CHUNK VERIFIER QUE CA MARCHE??
4938
0
                    if (back[i].r.statuscode == 206) {  // on nous envoie un morceau (la fin) coz une partie sur disque!
4939
0
                      LLint sz = fsize_utf8(back[i].url_sav);
4940
                      /* RFC 7233: resume at the server's Content-Range start,
4941
                         not the offset we requested; a server may resume
4942
                         earlier and appending the overlap duplicates bytes
4943
                         (#198). */
4944
0
                      const LLint resume = back[i].r.crange_start;
4945
0
                      const hts_boolean range_ok =
4946
0
                          back[i].r.crange > 0 && resume >= 0 &&
4947
0
                          resume <= (LLint) sz &&
4948
0
                          back[i].r.crange_end == back[i].r.crange - 1 &&
4949
0
                          (back[i].r.totalsize < 0 ||
4950
0
                           back[i].r.totalsize ==
4951
0
                               back[i].r.crange_end - resume + 1);
4952
4953
#if HDEBUG
4954
                      printf("partial content: " LLintP " on disk..\n",
4955
                             (LLint) sz);
4956
#endif
4957
0
                      if (sz >= 0 && range_ok) {
4958
0
                        if (!is_hypertext_mime(opt, back[i].r.contenttype, back[i].url_sav)) {  // pas HTML
4959
0
                          if (opt->getmode & HTS_GETMODE_NONHTML) {
4960
0
                            filenote(&opt->state.strc, back[i].url_sav, NULL);  // noter fichier comme connu
4961
0
                            file_notify(opt, back[i].url_adr, back[i].url_fil,
4962
0
                                        back[i].url_sav, 0, 1,
4963
0
                                        back[i].r.notmodified);
4964
0
                            back[i].r.out =
4965
0
                                FOPEN(fconv(catbuff, sizeof(catbuff),
4966
0
                                            back[i].url_sav),
4967
0
                                      "r+b"); // resume in place
4968
0
                            if (back[i].r.out && opt->cache != 0) {
4969
0
                              back[i].r.is_write = 1;
4970
0
                              back[i].r.size = resume; // bytes already on disk
4971
0
                              back[i].r.statuscode = HTTP_OK; // force 'OK'
4972
0
                              if (back[i].r.totalsize >= 0)
4973
0
                                back[i].r.totalsize += resume; // -> full size
4974
                              // drop bytes past the resume point; a silent
4975
                              // failure could leave a stale tail, so on error
4976
                              // drop the partial and refetch the whole file
4977
                              /* not (off_t): 32-bit on MSVC, wrapping a resume
4978
                                 past 2GB */
4979
0
                              if (HTS_FTRUNCATE(back[i].r.out, resume) != 0) {
4980
0
                                fclose(back[i].r.out);
4981
0
                                back[i].r.out = NULL;
4982
0
                                url_savename_refname_remove(
4983
0
                                    opt, back[i].url_adr, back[i].url_fil);
4984
0
                                UNLINK(back[i].url_sav);
4985
0
                                back[i].status = STATUS_READY;
4986
0
                                back_set_finished(opt, sback, i);
4987
0
                                strcpybuff(back[i].r.msg,
4988
0
                                           "Can not truncate partial file, "
4989
0
                                           "restarting");
4990
0
                              } else {
4991
                                /* not (off_t): 32-bit on MSVC, truncating a
4992
                                   resume past 2GB */
4993
0
                                fseeko(back[i].r.out, resume, SEEK_SET);
4994
                                /* create a temporary reference file in case of
4995
                                 * broken mirror */
4996
0
                                if (back_serialize_ref(opt, &back[i]) != 0) {
4997
0
                                  hts_log_print(opt, LOG_WARNING,
4998
0
                                                "Could not create temporary "
4999
0
                                                "reference file for %s%s",
5000
0
                                                back[i].url_adr,
5001
0
                                                back[i].url_fil);
5002
0
                                }
5003
#if HDEBUG
5004
                                printf("continue interrupted file\n");
5005
#endif
5006
0
                              }
5007
0
                            } else {    // On est dans la m**
5008
0
                              back[i].status = STATUS_READY;    // terminé (voir plus loin)
5009
0
                              back_set_finished(opt, sback, i);
5010
0
                              strcpybuff(back[i].r.msg,
5011
0
                                         "Can not open partial file");
5012
0
                            }
5013
0
                          }
5014
0
                        } else {        // mémoire
5015
0
                          FILE *fp =
5016
0
                            FOPEN(fconv(catbuff, sizeof(catbuff), back[i].url_sav), "rb");
5017
0
                          if (fp) {
5018
0
                            LLint alloc_mem = resume + 1;
5019
5020
                            // Bound the in-memory buffer to a 32-bit size (real
5021
                            // in-RAM resources are far smaller); a hostile
5022
                            // Content-Length that would overflow the add or the
5023
                            // (size_t) cast is dropped and refetched instead.
5024
0
                            if (back[i].r.totalsize > INT32_MAX - alloc_mem) {
5025
                              /* Windows refuses to unlink a file still open */
5026
0
                              fclose(fp);
5027
0
                              fp = NULL;
5028
0
                              url_savename_refname_remove(opt, back[i].url_adr,
5029
0
                                                          back[i].url_fil);
5030
0
                              UNLINK(back[i].url_sav);
5031
0
                              alloc_mem = -1;
5032
0
                            } else if (back[i].r.totalsize >= 0)
5033
0
                              alloc_mem += back[i].r.totalsize; // AJOUTER RESTANT!
5034
0
                            if (alloc_mem >= 0 && deleteaddr(&back[i].r) &&
5035
0
                                (back[i].r.adr =
5036
0
                                     (char *) malloct((size_t) alloc_mem))) {
5037
0
                              back[i].r.size = resume;
5038
0
                              if (back[i].r.totalsize >= 0)
5039
0
                                back[i].r.totalsize += resume; // -> full size
5040
0
                              if (!hts_fread_exact(back[i].r.adr,
5041
0
                                                   (size_t) resume, fp)) {
5042
0
                                back[i].status = STATUS_READY;  // terminé (voir plus loin)
5043
0
                                back_set_finished(opt, sback, i);
5044
0
                                strcpybuff(back[i].r.msg,
5045
0
                                           "Can not read partial file");
5046
0
                              } else {
5047
0
                                back[i].r.statuscode = HTTP_OK; // Forcer 'OK'
5048
#if HDEBUG
5049
                                printf("continue in mem interrupted file\n");
5050
#endif
5051
0
                              }
5052
0
                            } else {
5053
0
                              back[i].status = STATUS_READY;    // terminé (voir plus loin)
5054
0
                              back_set_finished(opt, sback, i);
5055
0
                              strcpybuff(back[i].r.msg,
5056
0
                                         "No memory for partial file");
5057
0
                            }
5058
0
                            if (fp != NULL)
5059
0
                              fclose(fp);
5060
0
                          } else {                              // open failed
5061
0
                            back[i].status = STATUS_READY;      // terminé (voir plus loin)
5062
0
                            back_set_finished(opt, sback, i);
5063
0
                            strcpybuff(back[i].r.msg,
5064
0
                                       "Can not open partial file");
5065
0
                          }
5066
0
                        }
5067
0
                      } else if (sz >=
5068
0
                                 0) { // unusable range -> restart whole file
5069
0
                        hts_log_print(opt, LOG_WARNING,
5070
0
                                      "Unusable partial-content range for %s%s "
5071
0
                                      "(have " LLintP " bytes, got " LLintP
5072
0
                                      "-" LLintP "/" LLintP "), restarting",
5073
0
                                      back[i].url_adr, back[i].url_fil,
5074
0
                                      (LLint) sz, back[i].r.crange_start,
5075
0
                                      back[i].r.crange_end, back[i].r.crange);
5076
0
                        url_savename_refname_remove(opt, back[i].url_adr,
5077
0
                                                    back[i].url_fil);
5078
0
                        UNLINK(back[i].url_sav);
5079
0
                        back[i].status = STATUS_READY;
5080
0
                        back_set_finished(opt, sback, i);
5081
0
                        strcpybuff(back[i].r.msg,
5082
0
                                   "Unusable partial content, restarting");
5083
0
                      } else {                          // partial not found
5084
0
                        back[i].status = STATUS_READY;  // terminé (voir plus loin)
5085
0
                        back_set_finished(opt, sback, i);
5086
0
                        strcpybuff(back[i].r.msg, "Can not find partial file");
5087
0
                      }
5088
                      // Erreur?
5089
0
                      if (back[i].status == STATUS_READY) {
5090
0
                        if (back[i].r.soc != INVALID_SOCKET) {
5091
#if HTS_DEBUG_CLOSESOCK
5092
                          DEBUG_W
5093
                            ("back_wait(206 solve problems): deletehttp\n");
5094
#endif
5095
0
                          deletehttp(&back[i].r);
5096
0
                        }
5097
0
                        back[i].r.soc = INVALID_SOCKET;
5098
0
                        back[i].r.statuscode = STATUSCODE_NON_FATAL;
5099
                        // the resume was rejected: the retry must GET the whole
5100
                        // file, never re-Range a surviving partial/ref (#581)
5101
0
                        back[i].r.refetch_wholefile = HTS_TRUE;
5102
0
                        if (strnotempty(back[i].r.msg))
5103
0
                          strcpybuff(back[i].r.msg,
5104
0
                                     "Error attempting to solve status 206 (partial file)");
5105
0
                      }
5106
0
                    }
5107
5108
0
                    if (back[i].status != 0) {  // non terminé (erreur)
5109
0
                      if (!back[i].testmode) {  // fichier normal
5110
5111
0
                        if (back[i].r.empty /* ?? && back[i].r.statuscode==HTTP_OK */ ) {       // empty response
5112
                          // Couper connexion
5113
0
                          back_maydeletehttp(opt, cache, sback, i);
5114
                          /* KA deletehttp(&back[i].r); back[i].r.soc=INVALID_SOCKET; */
5115
0
                          back[i].status = STATUS_READY;        // terminé
5116
0
                          back_set_finished(opt, sback, i);
5117
0
                          if (deleteaddr(&back[i].r)
5118
0
                              && (back[i].r.adr = (char *) malloct(2))) {
5119
0
                            back[i].r.adr[0] = 0;
5120
0
                          }
5121
                          /* locked = name pending; the waiter finalizes after
5122
                             patching url_sav (else: cached as .delayed, #5) */
5123
0
                          if (!back[i].locked) {
5124
0
                            hts_log_print(opt, LOG_TRACE, "finalizing empty");
5125
0
                            back_finalize(opt, cache, sback, i);
5126
0
                          }
5127
0
                        } else if (!back[i].r.is_chunk) { // pas de chunk
5128
0
                          back[i].is_chunk = 0;
5129
0
                          back[i].status = 1;   // start body
5130
0
                        } else {
5131
#if CHUNKDEBUG==1
5132
                          printf("[%d] chunk encoding detected %s..\n",
5133
                                 (int) back[i].r.soc, back[i].url_fil);
5134
#endif
5135
0
                          back[i].is_chunk = 1;
5136
0
                          back[i].chunk_adr = NULL;
5137
0
                          back[i].chunk_size = 0;
5138
0
                          back[i].chunk_blocksize = 0;
5139
0
                          back[i].status = STATUS_CHUNK_WAIT;   // start body wait chunk
5140
0
                          back[i].r.totalsize = -1;     /* devalidate size! (rfc) */
5141
0
                        }
5142
0
                        if (back[i].rateout > 0) {
5143
0
                          back[i].rateout_time = time_local();  // refresh pour transfer rate
5144
0
                        }
5145
#if HDEBUG
5146
                        printf("(buffer) start body!\n");
5147
#endif
5148
0
                      } else {  // mode test, ne pas passer en 1!!
5149
0
                        back[i].status = STATUS_READY;  // READY
5150
0
                        back_set_finished(opt, sback, i);
5151
#if HTS_DEBUG_CLOSESOCK
5152
                        DEBUG_W("back_wait(test ok): deletehttp\n");
5153
#endif
5154
0
                        deletehttp(&back[i].r);
5155
0
                        back[i].r.soc = INVALID_SOCKET;
5156
0
                        if (back[i].r.statuscode == HTTP_OK) {
5157
0
                          strcpybuff(back[i].r.msg, "Test: OK");
5158
0
                          back[i].r.statuscode = STATUSCODE_TEST_OK;    // test réussi
5159
0
                        } else {        // test a échoué, on ne change rien sauf que l'erreur est à titre indicatif
5160
0
                          char tempo[1000];
5161
5162
0
                          strcpybuff(tempo, back[i].r.msg);
5163
0
                          strcpybuff(back[i].r.msg, "Test: ");
5164
0
                          strcatbuff(back[i].r.msg, tempo);
5165
0
                        }
5166
5167
0
                      }
5168
0
                    }
5169
0
                  }
5170
5171
                  /*} */
5172
5173
0
                }               // si LF
5174
0
              }                 // r.size>2
5175
0
            }                   // si == 99
5176
5177
0
          }                     // si pas d'erreurs
5178
#if BDEBUG==1
5179
          printf("bytes overall: %d\n", back[i].r.size);
5180
#endif
5181
0
        }                       // données dispo
5182
5183
        // en cas d'erreur cl, supprimer éventuel fichier sur disque
5184
#if HTS_REMOVE_BAD_FILES
5185
        if (back[i].status < 0) {
5186
          if (!back[i].testmode) {      // pas en test
5187
            UNLINK(back[i].url_sav);    // éliminer fichier (endommagé)
5188
          }
5189
        }
5190
#endif
5191
5192
        /* funny log for commandline users */
5193
0
        if (opt->verbosedisplay == HTS_VERBOSE_SIMPLE) {
5194
0
          if (back[i].status == STATUS_READY) {
5195
0
            if (back[i].r.statuscode == HTTP_OK)
5196
0
              printf("* %s%s (" LLintP " bytes) - OK\n", back[i].url_adr,
5197
0
                     back[i].url_fil, (LLint) back[i].r.size);
5198
0
            else
5199
0
              printf("* %s%s (" LLintP " bytes) - %d\n", back[i].url_adr,
5200
0
                     back[i].url_fil, (LLint) back[i].r.size,
5201
0
                     back[i].r.statuscode);
5202
0
            fflush(stdout);
5203
0
          }
5204
0
        }
5205
5206
0
      }                         // status>0
5207
0
    } // for
5208
5209
    // vérifier timeouts
5210
0
    if (gestion_timeout) {
5211
0
      TStamp act;
5212
5213
0
      act = time_local();       // temps en secondes
5214
0
      for (i_mod = 0; i_mod < (unsigned int) back_max; i_mod++) {
5215
0
        unsigned int i = (i_mod + mod_random) % (back_max);
5216
5217
0
        if (back[i].status > 0) {       // réception/connexion/..
5218
0
          if (back[i].timeout > 0) {
5219
            // a stuck connect with a fallback address: retry the next one well
5220
            // before the full timeout (dead IPv6 on a dual-stack host, ...)
5221
0
            if (back[i].status == STATUS_CONNECTING) {
5222
0
              const hts_connect_fallback *const cf =
5223
0
                  &sback->connect_fallback[i];
5224
5225
0
              if (back_connect_fallback_due(cf->addr_index, cf->addr_count,
5226
0
                                            (int) (act - cf->connect_start),
5227
0
                                            back[i].timeout)) {
5228
0
                if (back_connect_next(opt, sback, i)) {
5229
0
                  continue; // reconnected to the next candidate
5230
0
                }
5231
                // fallback was due but no socket could be opened
5232
                // (back_connect_next closed the dead one): stop now rather than
5233
                // spin on an invalid fd
5234
0
                back[i].r.soc = INVALID_SOCKET;
5235
0
                back[i].r.statuscode = STATUSCODE_CONNERROR;
5236
0
                strcpybuff(back[i].r.msg, "Connect Error");
5237
0
                back[i].status = STATUS_READY;
5238
0
                back_set_finished(opt, sback, i);
5239
0
                continue;
5240
0
              }
5241
0
            }
5242
0
            if (((int) (act - back[i].timeout_refresh)) >= back[i].timeout) {
5243
0
              hts_log_print(opt, LOG_DEBUG, "connection timed out for %s%s", back[i].url_adr,
5244
0
                back[i].url_fil);
5245
0
              if (back[i].r.soc != INVALID_SOCKET) {
5246
#if HTS_DEBUG_CLOSESOCK
5247
                DEBUG_W("back_wait(timeout): deletehttp\n");
5248
#endif
5249
0
                deletehttp(&back[i].r);
5250
0
              }
5251
0
              back[i].r.soc = INVALID_SOCKET;
5252
0
              back[i].r.statuscode = STATUSCODE_TIMEOUT;
5253
0
              if (back[i].status == STATUS_CONNECTING)
5254
0
                strcpybuff(back[i].r.msg, "Connect Time Out");
5255
0
              else if (back[i].status == STATUS_WAIT_DNS)
5256
0
                strcpybuff(back[i].r.msg, "DNS Time Out");
5257
0
              else if (back[i].status == STATUS_SSL_WAIT_HANDSHAKE)
5258
0
                strcpybuff(back[i].r.msg, "SSL/TLS Handshake Time Out");
5259
0
              else
5260
0
                strcpybuff(back[i].r.msg, "Receive Time Out");
5261
0
              back[i].status = STATUS_READY;    // terminé
5262
0
              back_set_finished(opt, sback, i);
5263
0
            } else if ((back[i].rateout > 0) && (back[i].status < 99)) {
5264
0
              if (((int) (act - back[i].rateout_time)) >= HTS_WATCHRATE) {      // checker au bout de 15s
5265
0
                if ((int) ((back[i].r.size) / (act - back[i].rateout_time)) < back[i].rateout) {        // trop lent
5266
0
                  back[i].status = STATUS_READY;        // terminé
5267
0
                  back_set_finished(opt, sback, i);
5268
0
                  if (back[i].r.soc != INVALID_SOCKET) {
5269
#if HTS_DEBUG_CLOSESOCK
5270
                    DEBUG_W("back_wait(rateout): deletehttp\n");
5271
#endif
5272
0
                    deletehttp(&back[i].r);
5273
0
                  }
5274
0
                  back[i].r.soc = INVALID_SOCKET;
5275
0
                  back[i].r.statuscode = STATUSCODE_SLOW;
5276
0
                  strcpybuff(back[i].r.msg, "Transfer Rate Too Low");
5277
0
                }
5278
0
              }
5279
0
            }
5280
0
          }
5281
0
        }
5282
0
      }
5283
0
    }
5284
0
    max_loop--;
5285
0
    max_loop_chk++;
5286
0
  } while((busy_state) && (busy_recv) && (max_loop > 0));
5287
0
  if ((!busy_recv) && (!busy_state)) {
5288
0
    if (max_loop_chk >= 1) {
5289
0
      Sleep(10);                // un tite pause pour éviter les lag..
5290
0
    }
5291
0
  }
5292
0
}
5293
5294
0
int back_checksize(httrackp * opt, lien_back * eback, int check_only_totalsize) {
5295
0
  LLint size_to_test;
5296
5297
0
  if (check_only_totalsize)
5298
0
    size_to_test = eback->r.totalsize;
5299
0
  else
5300
0
    size_to_test = max(eback->r.totalsize, eback->r.size);
5301
0
  if (size_to_test >= 0) {
5302
5303
    /* Interdiction taille par le wizard? */
5304
0
    if (hts_testlinksize(opt, eback->url_adr, eback->url_fil,
5305
0
                         size_to_test / 1024) == -1) {
5306
0
      return 0;                 /* interdit */
5307
0
    }
5308
5309
    /* vérifier taille classique (heml et non html) */
5310
0
    if ((istoobig
5311
0
         (opt, size_to_test, eback->maxfile_html, eback->maxfile_nonhtml,
5312
0
          eback->r.contenttype))) {
5313
0
      return 0;                 /* interdit */
5314
0
    }
5315
0
  }
5316
0
  return 1;
5317
0
}
5318
5319
/* Grace left to the smooth stop before in-flight transfers are aborted. */
5320
0
static int back_maxtime_grace(const int maxtime) {
5321
0
  return maximum(5, minimum(30, maxtime / 10));
5322
0
}
5323
5324
/* Bytes the smooth stop may overrun before in-flight transfers are aborted.
5325
   No floor (unlike the time grace): a size overrun should abort promptly. */
5326
0
static LLint back_maxsize_grace(const LLint maxsite) { return maxsite / 10; }
5327
5328
/* Which cap has overrun its grace and must hard-stop the mirror (#77, #481).
5329
   -M measures received volume (HTS_TOTAL_RECV), not saved 200-only stat_bytes
5330
   which undercounts redirect/error-heavy crawls (#520). */
5331
0
static hts_boolean back_maxsize_reached(const httrackp *opt) {
5332
0
  return opt->maxsite > 0 && HTS_STAT.HTS_TOTAL_RECV >= opt->maxsite;
5333
0
}
5334
5335
0
static hts_boolean back_maxtime_reached(const httrackp *opt) {
5336
0
  return opt->maxtime > 0 &&
5337
0
         (time_local() - HTS_STAT.stat_timestart) >= opt->maxtime;
5338
0
}
5339
5340
/* A cap has been reached, so back_checkmirror() below is what raised the stop
5341
   flag: the stop the mirror is under is the engine's, not the user's. */
5342
0
static hts_boolean back_mirror_capped(const httrackp *opt) {
5343
0
  return back_maxsize_reached(opt) || back_maxtime_reached(opt);
5344
0
}
5345
5346
0
static hts_mirror_limit back_mirror_limit(httrackp *opt) {
5347
0
  if (back_maxsize_reached(opt)) {
5348
0
    const LLint over = HTS_STAT.HTS_TOTAL_RECV - opt->maxsite;
5349
5350
0
    if (over >= back_maxsize_grace(opt->maxsite))
5351
0
      return HTS_MIRROR_LIMIT_SIZE;
5352
0
  }
5353
0
  if (back_maxtime_reached(opt)) {
5354
0
    const TStamp elapsed = time_local() - HTS_STAT.stat_timestart;
5355
5356
0
    if (elapsed - opt->maxtime >= back_maxtime_grace(opt->maxtime))
5357
0
      return HTS_MIRROR_LIMIT_TIME;
5358
0
  }
5359
0
  return HTS_MIRROR_LIMIT_NONE;
5360
0
}
5361
5362
/* See htsback.h. */
5363
0
void back_check_worker_fault(httrackp *opt) {
5364
  /* Already aborted, and -1 outranks the other verdicts: a user stop and a
5365
     rolled-back session both exit 0, and this mirror must not. */
5366
0
  if (!hts_worker_faulted() || opt->state.exit_xh == -1)
5367
0
    return;
5368
0
  hts_log_print(opt, LOG_ERROR,
5369
0
                "Mirror aborted: a worker thread crashed and the front end "
5370
0
                "recovered it, so the mirror cannot be trusted");
5371
0
  hts_mutexlock(&opt->state.lock);
5372
0
  opt->state.stop = 1;
5373
0
  opt->state.exit_xh = -1;
5374
0
  hts_mutexrelease(&opt->state.lock);
5375
0
}
5376
5377
0
int back_checkmirror(httrackp *opt) {
5378
  /* request a smooth stop the first time each cap is reached */
5379
0
  if (back_maxsize_reached(opt) && !opt->state.stop) {
5380
0
    hts_log_print(opt, LOG_ERROR,
5381
0
                  "More than " LLintP
5382
0
                  " bytes have been transferred.. giving up",
5383
0
                  (LLint) opt->maxsite);
5384
0
    hts_request_stop(opt, 0);
5385
0
  }
5386
0
  if (back_maxtime_reached(opt) && !opt->state.stop) {
5387
0
    hts_log_print(opt, LOG_ERROR, "More than %d seconds passed.. giving up",
5388
0
                  opt->maxtime);
5389
0
    hts_request_stop(opt, 0);
5390
0
  }
5391
0
  back_check_worker_fault(opt);
5392
  /* hard stop once a cap overruns its grace (callers must stop waiting) */
5393
0
  return back_mirror_limit(opt) == HTS_MIRROR_LIMIT_NONE;
5394
0
}
5395
5396
// octets transférés + add
5397
0
LLint back_transferred(LLint nb, struct_back * sback) {
5398
0
  lien_back *const back = sback->lnk;
5399
0
  const int back_max = sback->count;
5400
0
  int i;
5401
5402
  // ajouter octets en instance
5403
0
  for(i = 0; i < back_max; i++)
5404
0
    if ((back[i].status > 0) && (back[i].status < 99 || back[i].status >= 1000))
5405
0
      nb += back[i].r.size;
5406
  // stored (ready) slots
5407
0
  if (sback->ready != NULL) {
5408
0
#ifndef HTS_NO_BACK_ON_DISK
5409
0
    nb += sback->ready_size_bytes;
5410
#else
5411
    struct_coucal_enum e = coucal_enum_new(sback->ready);
5412
    coucal_item *item;
5413
5414
    while((item = coucal_enum_next(&e))) {
5415
      lien_back *ritem = (lien_back *) item->value.ptr;
5416
5417
      if ((ritem->status > 0) && (ritem->status < 99 || ritem->status >= 1000))
5418
        nb += ritem->r.size;
5419
    }
5420
#endif
5421
0
  }
5422
0
  return nb;
5423
0
}
5424
5425
// backing info
5426
// j: 1=show sockets 2=show others 3=show all
5427
0
void back_info(struct_back * sback, int i, int j, FILE * fp) {
5428
0
  lien_back *const back = sback->lnk;
5429
0
  const int back_max = sback->count;
5430
5431
0
  assertf(i >= 0 && i < back_max);
5432
0
  if (back[i].status >= 0) {
5433
    // Holds the status tag plus the full URL: url_adr and url_fil are each
5434
    // HTS_URLMAXSIZE*2, so reserve room for both (*4) plus framing/trailer.
5435
    // Undersizing would make back_infostr's bounded appends abort on a long
5436
    // URL.
5437
0
    char BIGSTK s[HTS_URLMAXSIZE * 4 + 1024];
5438
5439
0
    s[0] = '\0';
5440
0
    back_infostr(sback, i, j, s, sizeof(s));
5441
0
    strcatbuff(s, LF);
5442
0
    fprintf(fp, "%s", s);
5443
0
  }
5444
0
}
5445
5446
// backing info
5447
// j: 1=show sockets 2=show others 3=show all
5448
0
void back_infostr(struct_back *sback, int i, int j, char *s, size_t size) {
5449
0
  lien_back *const back = sback->lnk;
5450
0
  const int back_max = sback->count;
5451
5452
0
  assertf(i >= 0 && i < back_max);
5453
0
  if (back[i].status >= 0) {
5454
0
    int aff = 0;
5455
5456
0
    if (j & 1) {
5457
0
      if (back[i].status == STATUS_CONNECTING) {
5458
0
        strlcatbuff(s, "CONNECT ", size);
5459
0
      } else if (back[i].status == STATUS_WAIT_HEADERS) {
5460
0
        strlcatbuff(s, "INFOS ", size);
5461
0
        aff = 1;
5462
0
      } else if (back[i].status == STATUS_CHUNK_WAIT
5463
0
                 || back[i].status == STATUS_CHUNK_CR) {
5464
0
        strlcatbuff(s, "INFOSC", size); // chunk info
5465
0
        aff = 1;
5466
0
      } else if (back[i].status > 0) {
5467
0
        strlcatbuff(s, "RECEIVE ", size);
5468
0
        aff = 1;
5469
0
      }
5470
0
    }
5471
0
    if (j & 2) {
5472
0
      if (back[i].status == STATUS_READY) {
5473
0
        switch (back[i].r.statuscode) {
5474
0
        case 200:
5475
0
          strlcatbuff(s, "READY ", size);
5476
0
          aff = 1;
5477
0
          break;
5478
0
        case -1:
5479
0
          strlcatbuff(s, "ERROR ", size);
5480
0
          aff = 1;
5481
0
          break;
5482
0
        case -2:
5483
0
          strlcatbuff(s, "TIMEOUT ", size);
5484
0
          aff = 1;
5485
0
          break;
5486
0
        case -3:
5487
0
          strlcatbuff(s, "TOOSLOW ", size);
5488
0
          aff = 1;
5489
0
          break;
5490
0
        case 400:
5491
0
          strlcatbuff(s, "BADREQUEST ", size);
5492
0
          aff = 1;
5493
0
          break;
5494
0
        case 401:
5495
0
        case 403:
5496
0
          strlcatbuff(s, "FORBIDDEN ", size);
5497
0
          aff = 1;
5498
0
          break;
5499
0
        case 404:
5500
0
          strlcatbuff(s, "NOT FOUND ", size);
5501
0
          aff = 1;
5502
0
          break;
5503
0
        case 500:
5504
0
          strlcatbuff(s, "SERVERROR ", size);
5505
0
          aff = 1;
5506
0
          break;
5507
0
        default:
5508
0
          {
5509
0
            char s2[256];
5510
5511
0
            snprintf(s2, sizeof(s2), "ERROR(%d)", back[i].r.statuscode);
5512
0
            strlcatbuff(s, s2, size);
5513
0
          }
5514
0
          aff = 1;
5515
0
        }
5516
0
      }
5517
0
    }
5518
5519
0
    if (aff) {
5520
0
      {
5521
0
        char BIGSTK s2[HTS_URLMAXSIZE * 2 + 1024];
5522
5523
0
        snprintf(s2, sizeof(s2), "\"%s", back[i].url_adr);
5524
0
        strlcatbuff(s, s2, size);
5525
5526
0
        if (back[i].url_fil[0] != '/')
5527
0
          strlcatbuff(s, "/", size);
5528
0
        snprintf(s2, sizeof(s2), "%s\" ", back[i].url_fil);
5529
0
        strlcatbuff(s, s2, size);
5530
        // size/totalsize trailer: build in s2, then append (the old code wrote
5531
        // straight into s here, clobbering the URL it had just assembled).
5532
0
        snprintf(s2, sizeof(s2), LLintP " " LLintP " ", (LLint) back[i].r.size,
5533
0
                 (LLint) back[i].r.totalsize);
5534
0
        strlcatbuff(s, s2, size);
5535
0
      }
5536
0
    }
5537
0
  }
5538
0
}
5539
5540
// -- backing --