Coverage Report

Created: 2026-08-13 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htstools.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
/*       various tools (filename analyzing ..)                  */
31
/* Author: Xavier Roche                                         */
32
/* ------------------------------------------------------------ */
33
34
/* Internal engine bytecode */
35
#define HTS_INTERNAL_BYTECODE
36
37
/* String */
38
#include <ctype.h>
39
#include "htscore.h"
40
#include "htstools.h"
41
#include "htsstrings.h"
42
#include "htscharset.h"
43
#ifdef _WIN32
44
#include "windows.h"
45
#include <io.h> /* _isatty */
46
#else
47
#include <dirent.h>
48
#ifdef HAVE_UNISTD_H
49
#include <unistd.h>
50
#endif
51
#include <sys/stat.h>
52
#endif
53
54
// Portable directory find functions
55
#ifndef HTS_DEF_FWSTRUCT_find_handle_struct
56
#define HTS_DEF_FWSTRUCT_find_handle_struct
57
typedef struct find_handle_struct find_handle_struct;
58
#endif
59
#ifdef _WIN32
60
struct find_handle_struct {
61
  WIN32_FIND_DATAA hdata;
62
  HANDLE handle;
63
};
64
#else
65
struct find_handle_struct {
66
  DIR *hdir;
67
  struct dirent *dirp;
68
  STRUCT_STAT filestat;
69
  char path[2048];
70
};
71
#endif
72
73
/* Tools */
74
75
0
static int ehexh(char c) {
76
0
  if ((c >= '0') && (c <= '9'))
77
0
    return c - '0';
78
0
  if ((c >= 'a') && (c <= 'f'))
79
0
    c -= ('a' - 'A');
80
0
  if ((c >= 'A') && (c <= 'F'))
81
0
    return (c - 'A' + 10);
82
0
  return 0;
83
0
}
84
85
0
static int ehex(const char *s) {
86
0
  return 16 * ehexh(*s) + ehexh(*(s + 1));
87
0
}
88
89
0
static void unescapehttp(const char *s, String * tempo) {
90
0
  size_t i;
91
92
  /* A truncated escape has no digits: decoding one runs past the terminator. */
93
0
  for(i = 0; s[i] != '\0'; i++) {
94
0
    if (s[i] == '%' && s[i + 1] == '%') {
95
0
      i++;
96
0
      StringAddchar(*tempo, '%');
97
0
    } else if (s[i] == '%' && s[i + 1] != '\0' && s[i + 2] != '\0') {
98
0
      char hc;
99
100
0
      i++;
101
0
      hc = (char) ehex(s + i);
102
0
      StringAddchar(*tempo, (char) hc);
103
0
      i++;                      // sauter 2 caractères finalement
104
0
    } else if (s[i] == '+') {
105
0
      StringAddchar(*tempo, ' ');
106
0
    } else
107
0
      StringAddchar(*tempo, s[i]);
108
0
  }
109
0
}
110
111
// forme à partir d'un lien et du contexte (origin_fil et origin_adr d'où il est tiré) adr et fil
112
// [adr et fil sont des buffers de 1ko]
113
// 0 : ok
114
// -1 : erreur
115
// -2 : protocole non supporté (ftp)
116
int ident_url_relatif(const char *lien, const char *origin_adr,
117
                      const char *origin_fil,
118
0
                      lien_adrfil* const adrfil) {
119
0
  int ok = 0;
120
0
  int scheme = 0;
121
122
0
  assertf(adrfil != NULL);
123
124
0
  adrfil->adr[0] = '\0';
125
0
  adrfil->fil[0] = '\0';                //effacer buffers
126
127
  // lien non vide!
128
0
  if (strnotempty(lien) == 0)
129
0
    return -1;                  // erreur!
130
131
  // Scheme?
132
0
  {
133
0
    const char *a = lien;
134
135
0
    while(isalpha((unsigned char) *a))
136
0
      a++;
137
0
    if (*a == ':')
138
0
      scheme = 1;
139
0
  }
140
141
  // filtrer les parazites (mailto & cie)
142
  // scheme+authority (//)
143
0
  if ((strfield(lien, "http://"))       // scheme+//
144
0
      || (strfield(lien, "file://"))    // scheme+//
145
0
      || (strncmp(lien, "//", 2) == 0)  // // sans scheme (-> default)
146
0
    ) {
147
0
    if (ident_url_absolute(lien, adrfil) == -1) {
148
0
      ok = -1;                  // erreur URL
149
0
    }
150
0
  } else if (strfield(lien, "ftp://")) {
151
    // Note: ftp:foobar.gif is not valid
152
0
    if (ftp_available()) {      // ftp supporté
153
0
      if (ident_url_absolute(lien, adrfil) == -1) {
154
0
        ok = -1;                // erreur URL
155
0
      }
156
0
    } else {
157
0
      ok = -2;                  // non supporté
158
0
    }
159
#if HTS_USEOPENSSL
160
  } else if (strfield(lien, "https://")) {
161
    // Note: ftp:foobar.gif is not valid
162
    if (ident_url_absolute(lien, adrfil) == -1) {
163
      ok = -1;                // erreur URL
164
    }
165
#endif
166
0
  } else if ((scheme) && ((!strfield(lien, "http:"))
167
0
                          && (!strfield(lien, "https:"))
168
0
                          && (!strfield(lien, "ftp:"))
169
0
             )) {
170
0
    ok = -1;                    // unknown scheme
171
0
  } else {                      // c'est un lien relatif
172
    // On forme l'URL complète à partie de l'url actuelle
173
    // et du chemin actuel si besoin est.
174
175
    // sanity check
176
0
    if (origin_adr == NULL || origin_fil == NULL 
177
0
      || *origin_adr == '\0' || *origin_fil == '\0') {
178
0
      return -1;
179
0
    }
180
181
    // copier adresse
182
0
    if (((int) strlen(origin_adr) < HTS_URLMAXSIZE)
183
0
        && ((int) strlen(origin_fil) < HTS_URLMAXSIZE)
184
0
        && ((int) strlen(lien) < HTS_URLMAXSIZE)) {
185
186
      /* patch scheme if necessary */
187
0
      if (strfield(lien, "http:")) {
188
0
        lien += 5;
189
0
        strcpybuff(adrfil->adr, jump_protocol_const(origin_adr));     // même adresse ; protocole vide (http)
190
0
      } else if (strfield(lien, "https:")) {
191
0
        lien += 6;
192
0
        strcpybuff(adrfil->adr, "https://");    // même adresse forcée en https
193
0
        strcatbuff(adrfil->adr, jump_protocol_const(origin_adr));
194
0
      } else if (strfield(lien, "ftp:")) {
195
0
        lien += 4;
196
0
        strcpybuff(adrfil->adr, "ftp://");      // même adresse forcée en ftp
197
0
        strcatbuff(adrfil->adr, jump_protocol_const(origin_adr));
198
0
      } else {
199
0
        strcpybuff(adrfil->adr, origin_adr);    // même adresse ; et même éventuel protocole
200
0
      }
201
202
0
      if (*lien != '/') {       // sinon c'est un lien absolu
203
0
        if (*lien == '\0') {
204
0
          strcpybuff(adrfil->fil, origin_fil);
205
0
        } else if (*lien == '?') {      // example: a href="?page=2"
206
0
          char *a;
207
208
0
          strcpybuff(adrfil->fil, origin_fil);
209
0
          a = strchr(adrfil->fil, '?');
210
0
          if (a)
211
0
            *a = '\0';
212
0
          strcatbuff(adrfil->fil, lien);
213
0
        } else {
214
0
          const char *a = strchr(origin_fil, '?');
215
216
0
          if (a == NULL)
217
0
            a = origin_fil + strlen(origin_fil);
218
0
          while((*a != '/') && (a > origin_fil))
219
0
            a--;
220
0
          if (*a == '/') {      // ok on a un '/'
221
0
            if ((((int) (a - origin_fil)) + 1 + strlen(lien)) < HTS_URLMAXSIZE) {
222
              // copier chemin
223
0
              strncpy(adrfil->fil, origin_fil, ((int) (a - origin_fil)) + 1);
224
0
              *(adrfil->fil + ((int) (a - origin_fil)) + 1) = '\0';
225
226
              // copier chemin relatif
227
0
              if (((int) strlen(adrfil->fil) + (int) strlen(lien)) < HTS_URLMAXSIZE) {
228
0
                strcatbuff(adrfil->fil, lien + ((*lien == '/') ? 1 : 0));
229
                // simplifier url pour les ../
230
0
                fil_simplifie(adrfil->fil);
231
0
              } else
232
0
                ok = -1;        // erreur
233
0
            } else {            // erreur
234
0
              ok = -1;          // erreur URL
235
0
            }
236
0
          } else {              // erreur
237
0
            ok = -1;            // erreur URL
238
0
          }
239
0
        }
240
0
      } else {                  // chemin absolu
241
        // copier chemin directement
242
0
        strcatbuff(adrfil->fil, lien);
243
0
        fil_simplifie(adrfil->fil);
244
0
      }                         // *lien!='/'
245
0
    } else
246
0
      ok = -1;
247
248
0
  }                             // test news: etc.
249
250
  // case insensitive pour adresse
251
0
  {
252
0
    char *a = jump_identification(adrfil->adr);
253
254
0
    while(*a) {
255
0
      if ((*a >= 'A') && (*a <= 'Z'))
256
0
        *a += 'a' - 'A';
257
0
      a++;
258
0
    }
259
0
  }
260
261
  // IDNA / RFC 3492 (Punycode) handling for HTTP(s)
262
0
  if (!link_has_authority(adrfil->adr) || strfield(adrfil->adr, "https:")) {
263
0
    char *const a = jump_identification(adrfil->adr);
264
    // Non-ASCII characters (theorically forbidden, but browsers are lenient)
265
0
    if (!hts_isStringAscii(a, strlen(a))) {
266
0
      char *const idna = hts_convertStringUTF8ToIDNA(a, strlen(a));
267
0
      if (idna != NULL) {
268
0
        if (strlen(idna) < HTS_URLMAXSIZE) {
269
          /* a points within adrfil->adr; bound by the remaining capacity */
270
0
          strlcpybuff(a, idna,
271
0
                      sizeof(adrfil->adr) - (size_t) (a - adrfil->adr));
272
0
        }
273
0
        free(idna);
274
0
      }
275
0
    }
276
0
  }
277
278
0
  return ok;
279
0
}
280
281
/* Bounded substring search: bodies and archive records carry NUL bytes, so
282
   strstr() would stop at the first one. */
283
const char *hts_memstr(const char *hay, size_t haylen, const char *needle,
284
0
                       size_t nlen) {
285
0
  size_t i;
286
287
0
  if (nlen == 0 || haylen < nlen)
288
0
    return NULL;
289
0
  for (i = 0; i + nlen <= haylen; i++) {
290
0
    if (hay[i] == *needle && memcmp(hay + i, needle, nlen) == 0)
291
0
      return hay + i;
292
0
  }
293
0
  return NULL;
294
0
}
295
296
// créer dans s, à partir du chemin courant curr_fil, le lien vers link (absolu)
297
// un ident_url_relatif a déja été fait avant, pour que link ne soit pas un chemin relatif
298
0
int lienrelatif(char *s, size_t ssize, const char *link, const char *curr_fil) {
299
0
  char BIGSTK _curr[HTS_URLMAXSIZE * 2];
300
0
  char BIGSTK newcurr_fil[HTS_URLMAXSIZE * 2], newlink[HTS_URLMAXSIZE * 2];
301
0
  char *curr;
302
303
0
  char *a;
304
0
  int slash = 0;
305
306
  //
307
0
  newcurr_fil[0] = '\0';
308
0
  newlink[0] = '\0';
309
  //
310
311
  // patch: éliminer les ? (paramètres) sinon bug
312
0
  {
313
0
    const char *a;
314
315
0
    if ((a = strchr(curr_fil, '?'))) {
316
0
      strncatbuff(newcurr_fil, curr_fil, (int) (a - curr_fil));
317
0
      curr_fil = newcurr_fil;
318
0
    }
319
0
    if ((a = strchr(link, '?'))) {
320
0
      strncatbuff(newlink, link, (int) (a - link));
321
0
      link = newlink;
322
0
    }
323
0
  }
324
325
  // copy only the current path
326
0
  curr = _curr;
327
0
  strlcpybuff(curr, curr_fil, sizeof(_curr));
328
0
  if ((a = strchr(curr, '?')) == NULL) { // cut at the ? (query parameters)
329
0
    a = hts_lastcharptr(curr);
330
0
  }
331
0
  while((*a != '/') && (a > curr))
332
0
    a--;                        // chercher dernier / du chemin courant
333
0
  if (*a == '/')
334
0
    *(a + 1) = '\0';            // couper dernier /
335
336
  // "effacer" s
337
0
  s[0] = '\0';
338
339
  // sauter ce qui est commun aux 2 chemins
340
0
  {
341
0
    const char *l;
342
343
0
    if (*link == '/')
344
0
      link++;                   // sauter slash
345
0
    if (*curr == '/')
346
0
      curr++;
347
0
    l = link;
348
    // couper ce qui est commun
349
0
    while((streql(*link, *curr)) && (*link != 0)) {
350
0
      link++;
351
0
      curr++;
352
0
    }
353
    // mais on veut un répertoirer entier!
354
    // si on a /toto/.. et /toto2/.. on ne veut pas sauter /toto !
355
0
    while(((*link != '/') || (*curr != '/')) && (link > l)) {
356
0
      link--;
357
0
      curr--;
358
0
    }
359
0
  }
360
361
  // calculer la profondeur du répertoire courant et remonter
362
  // LES ../ ONT ETE SIMPLIFIES
363
0
  a = curr;
364
0
  if (*a == '/')
365
0
    a++;
366
0
  while(*a)
367
0
    if (*(a++) == '/')
368
0
      strlcatbuff(s, "../", ssize);
369
370
0
  if (slash)
371
0
    strlcatbuff(s, "/", ssize); // keep it absolute!
372
373
  // we are in the starting directory, copy
374
0
  strlcatbuff(s, link + ((*link == '/') ? 1 : 0), ssize);
375
376
  /* Security check */
377
0
  if (strlen(s) >= HTS_URLMAXSIZE)
378
0
    return -1;
379
380
  // on a maintenant une chaine de la forme ../../test/truc.html  
381
0
  return 0;
382
0
}
383
384
/* Is the link absolute (http://www..) or relative (/bar/foo.html) ? */
385
0
int link_has_authority(const char *lien) {
386
0
  const char *a = lien;
387
388
0
  if (isalpha((unsigned char) *a)) {
389
    // Skip scheme?
390
0
    while(isalpha((unsigned char) *a))
391
0
      a++;
392
0
    if (*a == ':')
393
0
      a++;
394
0
    else
395
0
      return 0;
396
0
  }
397
0
  if (strncmp(a, "//", 2) == 0)
398
0
    return 1;
399
0
  return 0;
400
0
}
401
402
0
int link_has_authorization(const char *lien) {
403
0
  const char *adr = jump_protocol_const(lien);
404
0
  const char *firstslash = strchr(adr, '/');
405
0
  const char *detect = strchr(adr, '@');
406
407
0
  if (firstslash) {
408
0
    if (detect) {
409
0
      return (detect < firstslash);
410
0
    }
411
0
  } else {
412
0
    return (detect != NULL);
413
0
  }
414
0
  return 0;
415
0
}
416
417
// conversion chemin de fichier/dossier vers 8-3 ou ISO9660
418
0
void long_to_83(int mode, char *n83, size_t n83size, char *save) {
419
0
  n83[0] = '\0';
420
421
0
  while(*save) {
422
0
    char fn83[256], fnl[256];
423
0
    size_t i, j;
424
425
0
    fn83[0] = fnl[0] = '\0';
426
0
    for(i = j = 0 ; save[i] && save[i] != '/' ; i++) {
427
0
      if (j + 1 < sizeof(fnl)) {
428
0
        fnl[j++] = save[i];
429
0
      }
430
0
    }
431
0
    fnl[j] = '\0';
432
    // conversion
433
0
    longfile_to_83(mode, fn83, sizeof(fn83), fnl);
434
0
    strlcatbuff(n83, fn83, n83size);
435
436
0
    save += i;
437
0
    if (*save == '/') {
438
0
      strlcatbuff(n83, "/", n83size);
439
0
      save++;
440
0
    }
441
0
  }
442
0
}
443
444
// conversion nom de fichier/dossier isolé vers 8-3 ou ISO9660
445
0
void longfile_to_83(int mode, char *n83, size_t n83size, char *save) {
446
0
  int j = 0, max = 0;
447
0
  int i = 0;
448
0
  char nom[256];
449
0
  char ext[256];
450
451
0
  nom[0] = ext[0] = '\0';
452
453
0
  switch (mode) {
454
0
  case 1:
455
0
    max = 8;
456
0
    break;
457
0
  case 2:
458
0
    max = 31;
459
0
    break;
460
0
  default:
461
0
    max = 8;
462
0
    break;
463
0
  }
464
465
  /* No starting . */
466
0
  if (save[0] == '.') {
467
0
    save[0] = '_';
468
0
  }
469
  /* No multiple dots */
470
0
  {
471
0
    char *last_dot = strrchr(save, '.');
472
0
    char *dot;
473
474
0
    while((dot = strchr(save, '.'))) {
475
0
      *dot = '_';
476
0
    }
477
0
    if (last_dot) {
478
0
      *last_dot = '.';
479
0
    }
480
0
  }
481
  /* 
482
     Avoid: (ISO9660, but also suitable for 8-3)
483
     (Thanks to jonat@cellcast.com for te hint)
484
     /:;?\#*~
485
     0x00-0x1f and 0x80-0xff
486
   */
487
0
  for(i = 0, j = 0; save[i] != 0; i++) {
488
0
    char a = save[i];
489
490
0
    if (a >= 'a' && a <= 'z') {
491
0
      a -= 'a' - 'A';
492
0
    } else
493
0
      if (!
494
0
          ((a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || a == '_'
495
0
           || a == '.')) {
496
0
      if (j != 0 && save[j - 1] == '_') {
497
0
        continue;  // avoid __
498
0
      }
499
0
      a = '_';
500
0
    }
501
0
    save[j++] = a;
502
0
  }
503
0
  save[j] = '\0';
504
505
0
  i = j = 0;
506
0
  while((i < max) && (save[j]) && (save[j] != '.')) {
507
0
    if (save[j] != ' ') {
508
0
      nom[i] = save[j];
509
0
      i++;
510
0
    }
511
0
    j++;
512
0
  }                             // recopier nom
513
0
  nom[i] = '\0';
514
0
  if (save[j]) {                // il reste au moins un point
515
0
    i = (int) strlen(save) - 1;
516
0
    while((i > 0) && (save[i] != '.') && (save[i] != '/'))
517
0
      i--;                      // rechercher dernier .
518
0
    if (save[i] == '.') {       // point!
519
0
      int j = 0;
520
521
0
      i++;
522
0
      while((j < 3) && (save[i])) {
523
0
        if (save[i] != ' ') {
524
0
          ext[j] = save[i];
525
0
          j++;
526
0
        }
527
0
        i++;
528
0
      }
529
0
      ext[j] = '\0';
530
0
    }
531
0
  }
532
  // corriger vers 8-3
533
0
  n83[0] = '\0';
534
0
  strlncatbuff(n83, nom, n83size, max);
535
0
  if (strnotempty(ext)) {
536
0
    strlcatbuff(n83, ".", n83size);
537
0
    strlncatbuff(n83, ext, n83size, 3);
538
0
  }
539
0
}
540
541
// écrire backblue.gif
542
/* Note: utf-8 */
543
0
int verif_backblue(httrackp * opt, const char *base) {
544
0
  int ret = 0;
545
546
  //
547
0
  if (!base) {                  // init
548
0
    opt->state.verif_backblue_done = 0;
549
0
    return 0;
550
0
  }
551
0
  if ((!opt->state.verif_backblue_done)
552
0
      || (fsize_utf8(fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), base, "backblue.gif")) !=
553
0
          HTS_DATA_BACK_GIF_LEN)) {
554
0
    FILE *fp =
555
0
      filecreate(&opt->state.strc,
556
0
                 fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), base, "backblue.gif"));
557
0
    opt->state.verif_backblue_done = 1;
558
0
    if (fp) {
559
0
      if (fwrite(HTS_DATA_BACK_GIF, HTS_DATA_BACK_GIF_LEN, 1, fp) !=
560
0
          HTS_DATA_BACK_GIF_LEN)
561
0
        ret = 1;
562
0
      fclose(fp);
563
0
      usercommand(opt, 0, NULL,
564
0
                  fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), base, "backblue.gif"), "", "");
565
0
    } else
566
0
      ret = 1;
567
    //
568
0
    fp =
569
0
      filecreate(&opt->state.strc,
570
0
                 fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), base, "fade.gif"));
571
0
    if (fp) {
572
0
      if (fwrite(HTS_DATA_FADE_GIF, HTS_DATA_FADE_GIF_LEN, 1, fp) !=
573
0
          HTS_DATA_FADE_GIF_LEN)
574
0
        ret = 1;
575
0
      fclose(fp);
576
0
      usercommand(opt, 0, NULL, fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), base, "fade.gif"),
577
0
                  "", "");
578
0
    } else
579
0
      ret = 1;
580
0
  }
581
0
  return ret;
582
0
}
583
584
// flag
585
0
int verif_external(httrackp * opt, int nb, int test) {
586
0
  const int flag = 1 << nb;
587
0
  int *const status = &opt->state.verif_external_status;
588
0
  if (!test)
589
0
    *status &= ~flag;             // reset
590
0
  else if ((*status & flag) == 0) {
591
0
    *status |= flag;
592
0
    return 1;
593
0
  }
594
0
  return 0;
595
0
}
596
597
// recherche chaîne de type truc<espaces>=
598
// renvoi décalage à effectuer ou 0 si non trouvé
599
/* SECTION OPTIMISEE:
600
#define rech_tageq(adr,s) ( \
601
  ( (*(adr-1)=='<') || (is_space(*(adr-1))) ) ? \
602
    ( (streql(*adr,*s)) ? \
603
      (__rech_tageq(adr,s)) \
604
      : 0 \
605
    ) \
606
    : 0\
607
  )
608
*/
609
/*
610
HTS_INLINE int rech_tageq(const char* adr,const char* s) { 
611
  if ( (*(adr-1)=='<') || (is_space(*(adr-1))) ) {   // <tag < tag etc
612
    if (streql(*adr,*s)) {                           // tester premier octet (optimisation)
613
      return __rech_tageq(adr,s);
614
    }
615
  }
616
  return 0;
617
}
618
*/
619
// Deuxième partie
620
0
HTS_INLINE int __rech_tageq(const char *adr, const char *s) {
621
0
  int p;
622
623
0
  p = strfield(adr, s);
624
0
  if (p) {
625
0
    while(is_space(adr[p]))
626
0
      p++;
627
0
    if (adr[p] == '=') {
628
0
      return p + 1;
629
0
    }
630
0
  }
631
0
  return 0;
632
0
}
633
634
0
HTS_INLINE int rech_tageq_all(const char *adr, const char *s) {
635
0
  int p;
636
0
  char quot = 0;
637
0
  const char *token = NULL;
638
0
  int s_len = (int) strlen(s);
639
640
0
  if (adr == NULL) {
641
0
    return 0;
642
0
  }
643
0
  for(p = 0; adr[p] != 0; p++) {
644
0
    if (quot == 0) {
645
0
      if (adr[p] == '"' || adr[p] == '\'') {
646
0
        quot = adr[p];
647
0
      } else if (adr[p] == '=' || is_realspace(adr[p])) {
648
0
        token = NULL;
649
0
      } else if (adr[p] == '>') {
650
0
        break;
651
0
      } else {                  /* note: bogus for bogus foo = bar */
652
0
        if (token == NULL) {
653
0
          if (strncasecmp(&adr[p], s, s_len) == 0
654
0
              && (is_realspace(adr[p + s_len]) || adr[p + s_len] == '=')
655
0
            ) {
656
0
            for(p += s_len; is_realspace(adr[p]) || adr[p] == '='; p++) ;
657
0
            return p;
658
0
          }
659
0
          token = &adr[p];
660
0
        }
661
0
      }
662
0
    } else if (adr[p] == quot) {
663
0
      quot = 0;
664
0
    }
665
0
  }
666
0
  return 0;
667
0
}
668
669
0
HTS_INLINE int rech_endtoken(const char *adr, const char **start) {
670
0
  char quote = '\0';
671
0
  int length = 0;
672
673
0
  while(is_space(*adr))
674
0
    adr++;
675
0
  if (*adr == '"' || *adr == '\'')
676
0
    quote = *adr++;
677
0
  *start = adr;
678
0
  while(*adr != 0 && *adr != quote && (quote != '\0' || !is_space(*adr))) {
679
0
    length++;
680
0
    adr++;
681
0
  }
682
0
  return length;
683
0
}
684
685
// same, but check beginning of adr with s (for <object src="bar.mov" .. hotspot123="foo.html">)
686
0
HTS_INLINE int __rech_tageqbegdigits(const char *adr, const char *s) {
687
0
  int p;
688
689
0
  p = strfield(adr, s);
690
0
  if (p) {
691
0
    while(isdigit((unsigned char) adr[p]))
692
0
      p++;                      // jump digits
693
0
    while(is_space(adr[p]))
694
0
      p++;
695
0
    if (adr[p] == '=') {
696
0
      return p + 1;
697
0
    }
698
0
  }
699
0
  return 0;
700
0
}
701
702
// tag sans =
703
0
HTS_INLINE int rech_sampletag(const char *adr, const char *s) {
704
0
  int p;
705
706
0
  if ((*(adr - 1) == '<') || (is_space(*(adr - 1)))) {  // <tag < tag etc
707
0
    p = strfield(adr, s);
708
0
    if (p) {
709
0
      if (!isalnum((unsigned char) adr[p])) {   // <srcbis n'est pas <src
710
0
        return 1;
711
0
      }
712
0
      return 0;
713
0
    }
714
0
  }
715
0
  return 0;
716
0
}
717
718
// teste si le tag contenu dans from est égal à "tag"
719
0
HTS_INLINE int check_tag(const char *from, const char *tag) {
720
0
  const char *a = from + 1;
721
0
  int i = 0;
722
0
  char s[256];
723
724
0
  while(is_space(*a))
725
0
    a++;
726
0
  for( ; (isalnum((unsigned char) *a) || (*a == '/')) && i + 1 < sizeof(s) ; i++, a++) {
727
0
    s[i] = *a;
728
0
  }
729
0
  s[i] = '\0';
730
0
  return strfield2(s, tag);   // comparer
731
0
}
732
733
// teste si un fichier dépasse le quota
734
int istoobig(httrackp * opt, LLint size, LLint maxhtml, LLint maxnhtml,
735
0
             char *type) {
736
0
  int ok = 1;
737
738
0
  if (size > 0) {
739
0
    if (is_hypertext_mime(opt, type, "")) {
740
0
      if (maxhtml > 0) {
741
0
        if (size > maxhtml)
742
0
          ok = 0;
743
0
      }
744
0
    } else {
745
0
      if (maxnhtml > 0) {
746
0
        if (size > maxnhtml)
747
0
          ok = 0;
748
0
      }
749
0
    }
750
0
  }
751
0
  return (!ok);
752
0
}
753
754
0
static int sortTopIndexFnc(const void *a_, const void *b_) {
755
0
  int cmp;
756
0
  const topindex_chain *const*const a = (const topindex_chain *const*) a_;
757
0
  const topindex_chain *const*const b = (const topindex_chain *const*) b_;
758
759
  /* Category first, then name */
760
0
  if ((cmp = (*a)->level - (*b)->level) == 0) {
761
0
    if ((cmp = strcmpnocase((*a)->category, (*b)->category)) == 0) {
762
0
      cmp = strcmpnocase((*a)->name, (*b)->name);
763
0
    }
764
0
  }
765
0
  return cmp;
766
0
}
767
768
typedef struct hts_template_format_buf {
769
  FILE *fp;
770
  char *buffer;
771
  size_t size;
772
  size_t offset;
773
} hts_template_format_buf;
774
775
// Bounded append to a template buffer (or FILE); returns -1 on overflow/error.
776
0
static int htsfmt_putc(hts_template_format_buf *buf, char c) {
777
0
  if (buf->fp != NULL) {
778
0
    assertf(buf->buffer == NULL);
779
0
    if (fputc(c, buf->fp) < 0)
780
0
      return -1;
781
0
  } else {
782
0
    assertf(buf->buffer != NULL);
783
0
    if (buf->offset + 1 < buf->size)
784
0
      buf->buffer[buf->offset++] = c;
785
0
    else
786
0
      return -1;
787
0
  }
788
0
  return 0;
789
0
}
790
791
0
static int htsfmt_puts(hts_template_format_buf *buf, const char *s) {
792
0
  size_t i;
793
0
  assertf(s != NULL);
794
0
  for (i = 0; s[i] != '\0'; i++) {
795
0
    if (htsfmt_putc(buf, s[i]) < 0)
796
0
      return -1;
797
0
  }
798
0
  return 0;
799
0
}
800
801
// note: upstream arg list MUST be NULL-terminated for safety
802
// returns a negative value upon error
803
static int hts_template_formatv(hts_template_format_buf *buf,
804
0
                                const char *format, va_list args) {
805
0
#undef FPUTC
806
0
#undef FPUTS
807
0
#define FPUTC(C)                                                               \
808
0
  do {                                                                         \
809
0
    if (htsfmt_putc(buf, (C)) < 0)                                             \
810
0
      return -1;                                                               \
811
0
  } while (0)
812
0
#define FPUTS(S)                                                               \
813
0
  do {                                                                         \
814
0
    if (htsfmt_puts(buf, (S)) < 0)                                             \
815
0
      return -1;                                                               \
816
0
  } while (0)
817
818
0
  if (buf != NULL && format != NULL) {
819
0
    const char *arg_expanded[32];
820
0
    size_t i, nbArgs, posArgs;
821
    /* Expand internal code args. */
822
0
    const char *str;
823
0
    for(nbArgs = 0 ; ( str = va_arg(args, const char*) ) != NULL ; nbArgs++) {
824
0
      assertf(nbArgs < sizeof(arg_expanded)/sizeof(arg_expanded[0]));
825
0
      arg_expanded[nbArgs] = str;
826
0
    }
827
    /* Expand user-injected format string. */
828
0
    for(posArgs = 0, i = 0 ; format[i] != '\0' ; i++) {
829
0
      const unsigned char c = format[i];
830
0
      if (c == '%') {
831
0
        const unsigned char cFormat = format[++i];
832
0
        switch(cFormat) {
833
0
        case '%':
834
0
          FPUTC('%');
835
0
          break;
836
0
        case 's':
837
0
          if (posArgs < nbArgs) {
838
0
            assertf(arg_expanded[posArgs] != NULL);
839
0
            FPUTS(arg_expanded[posArgs]);
840
0
            posArgs++;
841
0
          } else {
842
0
            FPUTS("???");  /* error (args overflow) */
843
0
          }
844
0
          break;
845
0
        default:  /* ignored */
846
0
          FPUTC('%');
847
0
          FPUTC(cFormat);
848
0
          break;
849
0
        }
850
0
      } else {
851
0
        FPUTC(c);
852
0
      }
853
0
    }
854
    /* Terminating NULL. */
855
0
    if (buf->buffer != NULL) {
856
0
      buf->buffer[buf->offset] = 0;
857
0
    }
858
0
    return 1;
859
0
  } else {
860
0
    return -1;
861
0
  }
862
0
#undef FPUTC
863
0
#undef FPUTS
864
0
}
865
866
// note: upstream arg list MUST be NULL-terminated for safety
867
// returns a negative value upon error
868
0
int hts_template_format(FILE *const out, const char *format, ...) {
869
0
  int success;
870
0
  hts_template_format_buf buf = { NULL, NULL, 0, 0 };
871
0
  va_list args;
872
0
  buf.fp = out;
873
0
  va_start(args, format);
874
0
  success = hts_template_formatv(&buf, format, args);
875
0
  va_end(args);
876
0
  return success;
877
0
}
878
879
// note: upstream arg list MUST be NULL-terminated for safety
880
// returns a negative value upon error
881
0
int hts_template_format_str(char *buffer, size_t size, const char *format, ...) {
882
0
  int success;
883
0
  hts_template_format_buf buf = { NULL, NULL, 0, 0 };
884
0
  va_list args;
885
0
  buf.buffer = buffer;
886
0
  buf.size = size;
887
0
  va_start(args, format);
888
0
  success = hts_template_formatv(&buf, format, args);
889
0
  va_end(args);
890
0
  return success;
891
0
}
892
893
// Value of the named field, or "" if absent (never NULL, so callers can pass it
894
// straight to a formatter).
895
static const char *footer_field_value(const hts_footer_field *fields,
896
0
                                      size_t nfields, const char *name) {
897
0
  size_t j;
898
0
  for (j = 0; j < nfields; j++) {
899
0
    if (strcmp(fields[j].name, name) == 0)
900
0
      return fields[j].value != NULL ? fields[j].value : "";
901
0
  }
902
0
  return "";
903
0
}
904
905
int hts_footer_format(char *buffer, size_t size, const char *footer,
906
0
                      const hts_footer_field *fields, size_t nfields) {
907
0
  hts_template_format_buf buf = {NULL, buffer, size, 0};
908
0
  size_t i;
909
910
0
  if (footer == NULL || buffer == NULL || size == 0)
911
0
    return -1;
912
  // %s keeps the legacy positional model, byte-for-byte for existing -%F
913
  // strings: addr, path, date, version, looked up by name and
914
  // order-independent.
915
0
  if (strstr(footer, "%s") != NULL)
916
0
    return hts_template_format_str(
917
0
        buffer, size, footer, footer_field_value(fields, nfields, "addr"),
918
0
        footer_field_value(fields, nfields, "path"),
919
0
        footer_field_value(fields, nfields, "date"),
920
0
        footer_field_value(fields, nfields, "version"), /* EOF */ NULL);
921
  // "{{"/"}}" emit a literal brace; an unknown "{...}" is left verbatim so
922
  // typos stay visible.
923
0
  for (i = 0; footer[i] != '\0'; i++) {
924
0
    const char c = footer[i];
925
0
    if (c == '{' && footer[i + 1] == '{') {
926
0
      if (htsfmt_putc(&buf, '{') < 0)
927
0
        return -1;
928
0
      i++;
929
0
    } else if (c == '}' && footer[i + 1] == '}') {
930
0
      if (htsfmt_putc(&buf, '}') < 0)
931
0
        return -1;
932
0
      i++;
933
0
    } else if (c == '{') {
934
0
      const char *const end = strchr(footer + i + 1, '}');
935
0
      int matched = 0;
936
0
      if (end != NULL) {
937
0
        const size_t namelen = (size_t) (end - (footer + i + 1));
938
0
        size_t j;
939
0
        for (j = 0; j < nfields; j++) {
940
0
          if (strlen(fields[j].name) == namelen &&
941
0
              strncmp(fields[j].name, footer + i + 1, namelen) == 0) {
942
0
            if (htsfmt_puts(&buf,
943
0
                            fields[j].value != NULL ? fields[j].value : "") < 0)
944
0
              return -1;
945
0
            i += namelen + 1; // consume the name and its closing '}'
946
0
            matched = 1;
947
0
            break;
948
0
          }
949
0
        }
950
0
      }
951
0
      if (!matched && htsfmt_putc(&buf, '{') < 0)
952
0
        return -1;
953
0
    } else if (htsfmt_putc(&buf, c) < 0) {
954
0
      return -1;
955
0
    }
956
0
  }
957
0
  buffer[buf.offset] = '\0';
958
0
  return 1;
959
0
}
960
961
/* Note: NOT utf-8 */
962
HTSEXT_API int hts_buildtopindex(httrackp * opt, const char *path,
963
0
                                 const char *binpath) {
964
0
  FILE *fpo;
965
0
  int retval = 0;
966
0
  char BIGSTK rpath[1024 * 2];
967
0
  char *toptemplate_header = NULL, *toptemplate_body =
968
0
    NULL, *toptemplate_footer = NULL, *toptemplate_bodycat = NULL;
969
0
  char catbuff[CATBUFF_SIZE];
970
971
  // et templates html
972
0
  toptemplate_header =
973
0
    readfile_or(fconcat(catbuff, sizeof(catbuff), binpath, "templates/topindex-header.html"),
974
0
                HTS_INDEX_HEADER);
975
0
  toptemplate_body =
976
0
    readfile_or(fconcat(catbuff, sizeof(catbuff), binpath, "templates/topindex-body.html"),
977
0
                HTS_INDEX_BODY);
978
0
  toptemplate_bodycat =
979
0
    readfile_or(fconcat(catbuff, sizeof(catbuff), binpath, "templates/topindex-bodycat.html"),
980
0
                HTS_INDEX_BODYCAT);
981
0
  toptemplate_footer =
982
0
    readfile_or(fconcat(catbuff, sizeof(catbuff), binpath, "templates/topindex-footer.html"),
983
0
                HTS_INDEX_FOOTER);
984
985
0
  if (toptemplate_header && toptemplate_body && toptemplate_footer
986
0
      && toptemplate_bodycat) {
987
988
0
    strcpybuff(rpath, path);
989
0
    hts_striplastchar(rpath, '/');
990
991
0
    fpo = fopen(fconcat(catbuff, sizeof(catbuff), rpath, "/index.html"), "wb");
992
0
    if (fpo) {
993
0
      find_handle h;
994
995
      // générer gif. verif_backblue() is utf-8, but our path is the system
996
      // charset, so on Windows convert it or the gifs land in a mangled twin
997
      // dir (#217). Elsewhere the system charset is already utf-8.
998
#ifdef _WIN32
999
      {
1000
        const char *const base = concat(catbuff, sizeof(catbuff), rpath, "/");
1001
        char *const base_utf8 =
1002
            hts_convertStringSystemToUTF8(base, strlen(base));
1003
        verif_backblue(opt, base_utf8 != NULL ? base_utf8 : base);
1004
        free(base_utf8);
1005
      }
1006
#else
1007
0
      verif_backblue(opt, concat(catbuff, sizeof(catbuff), rpath, "/"));
1008
0
#endif
1009
      // Header
1010
0
      hts_template_format(fpo, toptemplate_header,
1011
0
              "<!-- Mirror and index made by HTTrack Website Copier/"
1012
0
              HTTRACK_VERSION " " HTTRACK_AFF_AUTHORS " -->", /* EOF */ NULL);
1013
1014
      /* Find valid project names */
1015
0
      h = hts_findfirst(rpath);
1016
0
      if (h) {
1017
0
        struct topindex_chain *chain = NULL;
1018
0
        struct topindex_chain *startchain = NULL;
1019
0
        String iname = STRING_EMPTY;
1020
0
        int chainSize = 0;
1021
1022
0
        do {
1023
0
          if (hts_findisdir(h)) {
1024
0
            StringCopy(iname, rpath);
1025
0
            StringCat(iname, "/");
1026
0
            StringCat(iname, hts_findgetname(h));
1027
0
            StringCat(iname, "/index.html");
1028
0
            if (fexist(StringBuff(iname))) {
1029
0
              int level = 0;
1030
0
              char *category = NULL;
1031
0
              struct topindex_chain *oldchain = chain;
1032
1033
              /* Check for an existing category */
1034
0
              StringCopy(iname, rpath);
1035
0
              StringCat(iname, "/");
1036
0
              StringCat(iname, hts_findgetname(h));
1037
0
              StringCat(iname, "/hts-cache/winprofile.ini");
1038
0
              if (fexist(StringBuff(iname))) {
1039
0
                category = hts_getcategory(StringBuff(iname));
1040
0
                if (category != NULL) {
1041
0
                  if (*category == '\0') {
1042
0
                    freet(category);
1043
0
                    category = NULL;
1044
0
                  }
1045
#ifdef _WIN32
1046
                  /* category is ANSI-codepage, doc is utf-8: convert (#216) */
1047
                  else {
1048
                    char *cat_utf8 = hts_convertStringSystemToUTF8(
1049
                        category, strlen(category));
1050
                    if (cat_utf8 != NULL) {
1051
                      freet(category);
1052
                      category = cat_utf8;
1053
                    }
1054
                  }
1055
#endif
1056
0
                }
1057
0
              }
1058
0
              if (category == NULL) {
1059
0
                category = strdupt("No categories");
1060
0
                level = 1;
1061
0
              }
1062
1063
0
              chain = calloc(sizeof(struct topindex_chain), 1);
1064
0
              chainSize++;
1065
0
              if (!startchain) {
1066
0
                startchain = chain;
1067
0
              }
1068
0
              if (chain) {
1069
0
                if (oldchain) {
1070
0
                  oldchain->next = chain;
1071
0
                }
1072
0
                chain->next = NULL;
1073
#ifdef _WIN32
1074
                /* name is ANSI-codepage, doc is utf-8: convert (#216) */
1075
                {
1076
                  const char *const name = hts_findgetname(h);
1077
                  char *name_utf8 =
1078
                      hts_convertStringSystemToUTF8(name, strlen(name));
1079
                  strcpybuff(chain->name, name_utf8 != NULL ? name_utf8 : name);
1080
                  freet(name_utf8);
1081
                }
1082
#else
1083
0
                strcpybuff(chain->name, hts_findgetname(h));
1084
0
#endif
1085
0
                chain->category = category;
1086
0
                chain->level = level;
1087
0
              }
1088
0
            }
1089
1090
0
          }
1091
0
        } while(hts_findnext(h));
1092
0
        hts_findclose(h);
1093
0
        StringFree(iname);
1094
1095
        /* Sort chain */
1096
0
        {
1097
0
          struct topindex_chain **sortedElts =
1098
0
            (struct topindex_chain **) calloct(sizeof(topindex_chain *),
1099
0
                                               chainSize);
1100
0
          assertf(sortedElts != NULL);
1101
0
          if (sortedElts != NULL) {
1102
0
            int i;
1103
0
            const char *category = "";
1104
1105
            /* Build array */
1106
0
            struct topindex_chain *chain = startchain;
1107
1108
0
            for(i = 0; i < chainSize; i++) {
1109
0
              assertf(chain != NULL);
1110
0
              sortedElts[i] = chain;
1111
0
              chain = chain->next;
1112
0
            }
1113
0
            qsort(sortedElts, chainSize, sizeof(topindex_chain *),
1114
0
                  sortTopIndexFnc);
1115
1116
            /* Build sorted index */
1117
0
            for(i = 0; i < chainSize; i++) {
1118
0
              char BIGSTK hname[HTS_URLMAXSIZE * 2];
1119
1120
0
              escape_uri_utf(sortedElts[i]->name, hname, sizeof(hname));
1121
1122
              /* Changed category */
1123
0
              if (strcmp(category, sortedElts[i]->category) != 0) {
1124
0
                category = sortedElts[i]->category;
1125
0
                hts_template_format(fpo, toptemplate_bodycat, category, /* EOF */ NULL);
1126
0
              }
1127
0
              hts_template_format(fpo, toptemplate_body, hname, sortedElts[i]->name, /* EOF */ NULL);
1128
0
            }
1129
1130
            /* Wipe elements */
1131
0
            for(i = 0; i < chainSize; i++) {
1132
0
              freet(sortedElts[i]->category);
1133
0
              freet(sortedElts[i]);
1134
0
              sortedElts[i] = NULL;
1135
0
            }
1136
0
            freet(sortedElts);
1137
1138
            /* Return value */
1139
0
            retval = 1;
1140
0
          }
1141
0
        }
1142
1143
0
      }
1144
      // Footer
1145
0
      hts_template_format(fpo, toptemplate_footer,
1146
0
              "<!-- Mirror and index made by HTTrack Website Copier/"
1147
0
              HTTRACK_VERSION " " HTTRACK_AFF_AUTHORS " -->", /* EOF */ NULL);
1148
1149
0
      fclose(fpo);
1150
1151
0
    }
1152
1153
0
  }
1154
1155
0
  if (toptemplate_header)
1156
0
    freet(toptemplate_header);
1157
0
  if (toptemplate_body)
1158
0
    freet(toptemplate_body);
1159
0
  if (toptemplate_footer)
1160
0
    freet(toptemplate_footer);
1161
0
  if (toptemplate_body)
1162
0
    freet(toptemplate_body);
1163
1164
0
  return retval;
1165
0
}
1166
1167
/* Note: NOT utf-8 */
1168
0
HTSEXT_API char *hts_getcategory(const char *filename) {
1169
0
  String categ = STRING_EMPTY;
1170
1171
0
  if (fexist(filename)) {
1172
0
    FILE *fp = fopen(filename, "rb");
1173
1174
0
    if (fp != NULL) {
1175
0
      int done = 0;
1176
1177
0
      while(!feof(fp) && !done) {
1178
0
        char BIGSTK line[1024];
1179
0
        int n = linput(fp, line, sizeof(line) - 2);
1180
1181
0
        if (n > 0) {
1182
0
          if (strfield(line, "category=")) {
1183
0
            unescapehttp(line + 9, &categ);
1184
0
            done = 1;
1185
0
          }
1186
0
        }
1187
0
      }
1188
0
      fclose(fp);
1189
0
    }
1190
0
  }
1191
0
  return StringBuffRW(categ);
1192
0
}
1193
1194
/* Note: NOT utf-8 */
1195
0
HTSEXT_API char *hts_getcategories(char *path, int type) {
1196
0
  String categ = STRING_EMPTY;
1197
0
  String profiles = STRING_EMPTY;
1198
0
  char *rpath = path;
1199
0
  find_handle h;
1200
0
  coucal hashCateg = NULL;
1201
1202
  /* note: patching stored (inhash) value */
1203
0
  hts_striplastchar(rpath, '/');
1204
0
  h = hts_findfirst(rpath);
1205
0
  if (h) {
1206
0
    String iname = STRING_EMPTY;
1207
1208
0
    if (type == 1) {
1209
0
      hashCateg = coucal_new(0);
1210
0
      coucal_set_name(hashCateg, "hashCateg");
1211
0
      StringCat(categ, "Test category 1");
1212
0
      StringCat(categ, "\r\nTest category 2");
1213
0
    }
1214
0
    do {
1215
0
      if (hts_findisdir(h)) {
1216
0
        char BIGSTK line2[1024];
1217
1218
0
        StringCopy(iname, rpath);
1219
0
        StringCat(iname, "/");
1220
0
        StringCat(iname, hts_findgetname(h));
1221
0
        StringCat(iname, "/hts-cache/winprofile.ini");
1222
0
        if (fexist(StringBuff(iname))) {
1223
0
          if (type == 1) {
1224
0
            FILE *fp = fopen(StringBuff(iname), "rb");
1225
1226
0
            if (fp != NULL) {
1227
0
              int done = 0;
1228
1229
0
              while(!feof(fp) && !done) {
1230
0
                int n = linput(fp, line2, sizeof(line2) - 2);
1231
1232
0
                if (n > 0) {
1233
0
                  if (strfield(line2, "category=")) {
1234
0
                    if (*(line2 + 9)) {
1235
0
                      if (!coucal_read(hashCateg, line2 + 9, NULL)) {
1236
0
                        coucal_write(hashCateg, line2 + 9, 0);
1237
0
                        if (StringLength(categ) > 0) {
1238
0
                          StringCat(categ, "\r\n");
1239
0
                        }
1240
0
                        unescapehttp(line2 + 9, &categ);
1241
0
                      }
1242
0
                    }
1243
0
                    done = 1;
1244
0
                  }
1245
0
                }
1246
0
              }
1247
0
              line2[0] = '\0';
1248
0
              fclose(fp);
1249
0
            }
1250
0
          } else {
1251
0
            if (StringLength(profiles) > 0) {
1252
0
              StringCat(profiles, "\r\n");
1253
0
            }
1254
0
            StringCat(profiles, hts_findgetname(h));
1255
0
          }
1256
0
        }
1257
1258
0
      }
1259
0
    } while(hts_findnext(h));
1260
0
    hts_findclose(h);
1261
0
    StringFree(iname);
1262
0
  }
1263
0
  if (hashCateg) {
1264
0
    coucal_delete(&hashCateg);
1265
0
    hashCateg = NULL;
1266
0
  }
1267
0
  if (type == 1)
1268
0
    return StringBuffRW(categ);
1269
0
  else
1270
0
    return StringBuffRW(profiles);
1271
0
}
1272
1273
// Portable directory find functions
1274
/*
1275
// Example:
1276
find_handle h = hts_findfirst("/tmp");
1277
if (h) {
1278
  do {
1279
    if (hts_findisfile(h))
1280
      printf("File: %s (%d octets)\n",hts_findgetname(h),hts_findgetsize(h));
1281
    else if (hts_findisdir(h))
1282
      printf("Dir: %s\n",hts_findgetname(h));
1283
  } while(hts_findnext(h));
1284
  hts_findclose(h);
1285
}
1286
*/
1287
0
HTSEXT_API find_handle hts_findfirst(char *path) {
1288
0
  if (path) {
1289
0
    if (strnotempty(path)) {
1290
0
      find_handle_struct *find =
1291
0
        (find_handle_struct *) calloc(1, sizeof(find_handle_struct));
1292
0
      if (find) {
1293
0
        memset(find, 0, sizeof(find_handle_struct));
1294
#ifdef _WIN32
1295
        {
1296
          char BIGSTK rpath[1024 * 2];
1297
1298
          strcpybuff(rpath, path);
1299
          if (rpath[0]) {
1300
            if (hts_lastchar(rpath) != '\\')
1301
              strcatbuff(rpath, "\\");
1302
          }
1303
          strcatbuff(rpath, "*.*");
1304
          find->handle = FindFirstFileA(rpath, &find->hdata);
1305
          if (find->handle != INVALID_HANDLE_VALUE)
1306
            return find;
1307
        }
1308
#else
1309
0
        strcpybuff(find->path, path);
1310
0
        {
1311
0
          if (find->path[0]) {
1312
0
            if (hts_lastchar(find->path) != '/')
1313
0
              strcatbuff(find->path, "/");
1314
0
          }
1315
0
        }
1316
0
        find->hdir = opendir(path);
1317
0
        if (find->hdir != NULL) {
1318
0
          if (hts_findnext(find) == 1)
1319
0
            return find;
1320
0
        }
1321
0
#endif
1322
0
        free((void *) find);
1323
0
      }
1324
0
    }
1325
0
  }
1326
0
  return NULL;
1327
0
}
1328
1329
0
HTSEXT_API hts_boolean hts_findnext(find_handle find) {
1330
0
  if (find) {
1331
#ifdef _WIN32
1332
    if ((FindNextFileA(find->handle, &find->hdata)))
1333
      return HTS_TRUE;
1334
#else
1335
0
    char catbuff[CATBUFF_SIZE];
1336
1337
0
    memset(&(find->filestat), 0, sizeof(find->filestat));
1338
0
    if ((find->dirp = readdir(find->hdir)))
1339
0
      if (!STAT(
1340
0
              concat(catbuff, sizeof(catbuff), find->path, find->dirp->d_name),
1341
0
              &find->filestat))
1342
0
        return HTS_TRUE;
1343
0
#endif
1344
0
  }
1345
0
  return HTS_FALSE;
1346
0
}
1347
1348
0
HTSEXT_API int hts_findclose(find_handle find) {
1349
0
  if (find) {
1350
#ifdef _WIN32
1351
    if (find->handle) {
1352
      FindClose(find->handle);
1353
      find->handle = NULL;
1354
    }
1355
#else
1356
0
    if (find->hdir) {
1357
0
      closedir(find->hdir);
1358
0
      find->hdir = NULL;
1359
0
    }
1360
0
#endif
1361
0
    free((void *) find);
1362
0
  }
1363
0
  return 0;
1364
0
}
1365
1366
0
HTSEXT_API char *hts_findgetname(find_handle find) {
1367
0
  if (find) {
1368
#ifdef _WIN32
1369
    return find->hdata.cFileName;
1370
#else
1371
0
    if (find->dirp)
1372
0
      return find->dirp->d_name;
1373
0
#endif
1374
0
  }
1375
0
  return NULL;
1376
0
}
1377
1378
0
HTSEXT_API int hts_findgetsize(find_handle find) {
1379
0
  if (find) {
1380
#ifdef _WIN32
1381
    return find->hdata.nFileSizeLow;
1382
#else
1383
0
    return find->filestat.st_size;
1384
0
#endif
1385
0
  }
1386
0
  return -1;
1387
0
}
1388
1389
0
HTSEXT_API hts_boolean hts_findisdir(find_handle find) {
1390
0
  if (find) {
1391
0
    if (!hts_findissystem(find)) {
1392
#ifdef _WIN32
1393
      if (find->hdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1394
        return 1;
1395
#else
1396
0
      if (S_ISDIR(find->filestat.st_mode))
1397
0
        return 1;
1398
0
#endif
1399
0
    }
1400
0
  }
1401
0
  return 0;
1402
0
}
1403
0
HTSEXT_API hts_boolean hts_findisfile(find_handle find) {
1404
0
  if (find) {
1405
0
    if (!hts_findissystem(find)) {
1406
#ifdef _WIN32
1407
      if (!(find->hdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1408
        return 1;
1409
#else
1410
0
      if (S_ISREG(find->filestat.st_mode))
1411
0
        return 1;
1412
0
#endif
1413
0
    }
1414
0
  }
1415
0
  return 0;
1416
0
}
1417
0
HTSEXT_API hts_boolean hts_findissystem(find_handle find) {
1418
0
  if (find) {
1419
#ifdef _WIN32
1420
    if (find->hdata.
1421
        dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN |
1422
                            FILE_ATTRIBUTE_TEMPORARY))
1423
      return 1;
1424
    else if ((!strcmp(find->hdata.cFileName, ".."))
1425
             || (!strcmp(find->hdata.cFileName, ".")))
1426
      return 1;
1427
#else
1428
0
    if ((S_ISCHR(find->filestat.st_mode))
1429
0
        || (S_ISBLK(find->filestat.st_mode))
1430
0
        || (S_ISFIFO(find->filestat.st_mode))
1431
0
        || (S_ISSOCK(find->filestat.st_mode))
1432
0
      )
1433
0
      return 1;
1434
0
    else if ((!strcmp(find->dirp->d_name, ".."))
1435
0
             || (!strcmp(find->dirp->d_name, ".")))
1436
0
      return 1;
1437
0
#endif
1438
0
  }
1439
0
  return 0;
1440
0
}
1441
1442
/* Park cdst under a free sibling name; caside receives it. */
1443
static hts_boolean rename_park_aside(char *caside, size_t size,
1444
0
                                     const char *cdst) {
1445
0
  int i;
1446
1447
0
  for (i = 0; i < 16; i++) {
1448
0
    if (!slprintfbuff(caside, size, "%s.hts-old%d", cdst, i))
1449
0
      return HTS_FALSE;
1450
    /* Skip a name the mirror already holds: POSIX rename() would clobber it
1451
       (#774). A non-regular entry reads as free and the rename refuses it. */
1452
0
    if (fexist_utf8(caside))
1453
0
      continue;
1454
0
    if (RENAME(cdst, caside) == 0)
1455
0
      return HTS_TRUE;
1456
0
  }
1457
0
  return HTS_FALSE;
1458
0
}
1459
1460
/* cdst is in the way of the move: park it, retry, and put it back if the retry
1461
   fails too. Unlinking it instead would leave nothing at all (#790). */
1462
static hts_boolean rename_over_aside(httrackp *opt, const char *csrc,
1463
0
                                     const char *cdst) {
1464
0
  char caside[CATBUFF_SIZE];
1465
0
  int err;
1466
1467
  /* Only a regular file may be parked: a directory in the way is not what the
1468
     caller asked to replace, and parking it orphans it (UNLINK cannot drop). */
1469
0
  if (!fexist_utf8(cdst))
1470
0
    return HTS_FALSE;
1471
0
  if (!rename_park_aside(caside, sizeof(caside), cdst))
1472
0
    return HTS_FALSE;
1473
0
  if (RENAME(csrc, cdst) == 0) {
1474
0
    (void) UNLINK(caside);
1475
0
    return HTS_TRUE;
1476
0
  }
1477
0
  err = errno;
1478
  /* Retry once, then name the parked copy: nothing else on disk or in the log
1479
     points at it, and an --update purge would delete it unnoticed. */
1480
0
  if (RENAME(caside, cdst) != 0 && RENAME(caside, cdst) != 0)
1481
0
    hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
1482
0
                  "could not put %s back; its previous content is now %s", cdst,
1483
0
                  caside);
1484
0
  errno = err;
1485
0
  return HTS_FALSE;
1486
0
}
1487
1488
0
hts_boolean hts_rename_over(httrackp *opt, const char *src, const char *dst) {
1489
0
  char csrc[CATBUFF_SIZE], cdst[CATBUFF_SIZE];
1490
1491
0
  fconv(csrc, sizeof(csrc), src);
1492
0
  fconv(cdst, sizeof(cdst), dst);
1493
0
  if (RENAME(csrc, cdst) == 0)
1494
0
    return HTS_TRUE;
1495
  /* Only a dst in the way is something the fallback can clear, and the CRT maps
1496
     that to EEXIST; it keeps EACCES for a src another process holds, where the
1497
     retry would fail the same way. The src check covers a CRT that reports
1498
     neither. */
1499
0
  const int err = errno;
1500
1501
0
  if (err != EEXIST || !fexist_utf8(src))
1502
0
    return HTS_FALSE;
1503
0
  return rename_over_aside(opt, csrc, cdst);
1504
0
}
1505
1506
hts_boolean hts_rename_over_aside_selftest(httrackp *opt, const char *src,
1507
0
                                           const char *dst) {
1508
0
  char csrc[CATBUFF_SIZE], cdst[CATBUFF_SIZE];
1509
1510
0
  fconv(csrc, sizeof(csrc), src);
1511
0
  fconv(cdst, sizeof(cdst), dst);
1512
0
  return rename_over_aside(opt, csrc, cdst);
1513
0
}
1514
1515
0
hts_boolean hts_stdout_isterminal(void) {
1516
#ifdef _WIN32
1517
  return _isatty(_fileno(stdout)) ? HTS_TRUE : HTS_FALSE;
1518
#else
1519
0
  return isatty(fileno(stdout)) ? HTS_TRUE : HTS_FALSE;
1520
0
#endif
1521
0
}