Coverage Report

Created: 2026-08-31 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htshash.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
/*       hash table system (fast index)                         */
31
/* Author: Xavier Roche                                         */
32
/* ------------------------------------------------------------ */
33
34
/* Internal engine bytecode */
35
#define HTS_INTERNAL_BYTECODE
36
37
#include "htsopt.h"
38
#include "htshash.h"
39
40
/* specific definitions */
41
#include "htsbase.h"
42
#include "htsglobal.h"
43
#include "htsmd5.h"
44
#include "htscore.h"
45
#include "coucal.h"
46
/* END specific definitions */
47
48
/* Specific macros */
49
#ifndef malloct
50
#define malloct malloc
51
#define freet free
52
#define calloct calloc
53
#define strcpybuff strcpy
54
#endif
55
56
// GESTION DES TABLES DE HACHAGE
57
// Méthode à 2 clés (adr+fil), 2e cle facultative
58
// hash[no_enregistrement][pos]->hash est un index dans le tableau général liens
59
// type: numero enregistrement - 0 est case insensitive (sav) 1 (adr+fil) 2 (former_adr+former_fil)
60
// recherche dans la table selon nom1,nom2 et le no d'enregistrement
61
62
/* Key free handler (NOOP) ; addresses are kept */
63
0
static void key_freehandler(void *arg, coucal_key value) {
64
0
}
65
66
/* Key strdup (pointer copy) */
67
0
static coucal_key key_duphandler(void *arg, coucal_key_const name) {
68
0
  union {
69
0
    coucal_key_const roname;
70
0
    coucal_key name;
71
0
  } u;
72
0
  u.roname = name;
73
0
  return u.name;
74
0
}
75
76
/* Key sav hashes are using case-insensitive version */
77
0
static coucal_hashkeys key_sav_hashes(void *arg, coucal_key_const key) {
78
0
  hash_struct *const hash = (hash_struct*) arg;
79
0
  convtolower(hash->catbuff, sizeof(hash->catbuff), (const char *) key);
80
0
  return coucal_hash_string(hash->catbuff);
81
0
}
82
83
/* Key sav comparison is case-insensitive */
84
static int key_sav_equals(void *arg,
85
                          coucal_key_const a_,
86
0
                          coucal_key_const b_) {
87
0
  const char *const a = (const char*) a_;
88
0
  const char *const b = (const char*) b_;
89
0
  return strcasecmp(a, b) == 0;
90
0
}
91
92
static const char* key_sav_debug_print(void *arg,
93
0
                                       coucal_key_const a) {
94
0
  return (const char*) a;
95
0
}
96
97
0
static const char* value_sav_debug_print(void *arg, coucal_value_const a) {
98
0
  return (char*) a.ptr;
99
0
}
100
101
/* Dedup-key host for ADR: --host-alias first, so a rule may name the www. form
102
   the urlhack collapse would otherwise have eaten. */
103
static const char *key_host_alias(const hash_struct *hash, const char *adr,
104
0
                                  char *buf, size_t bufsize) {
105
0
  const char *const canon =
106
0
      hts_host_alias(hash->host_alias, adr, hash->norm_host, buf, bufsize);
107
108
0
  return canon != NULL ? canon : adr;
109
0
}
110
111
/* see htshash.h */
112
const char *hash_url_key(hash_struct *hash, const char *adr, const char *fil,
113
0
                         char *dst, size_t dstsize) {
114
0
  char BIGSTK aliasbuf[HTS_URLMAXSIZE * 2];
115
0
  const char *const adr_canon =
116
0
      adr != NULL ? key_host_alias(hash, adr, aliasbuf, sizeof(aliasbuf))
117
0
                  : NULL;
118
0
  const char *const adr_norm =
119
0
      adr_canon != NULL
120
0
          ? (hash->norm_host ? jump_normalized_const(adr_canon)
121
0
                             : jump_identification_const(adr_canon))
122
0
          : NULL;
123
124
  // copy address
125
0
  assertf(adr_norm != NULL);
126
  /* clip, don't abort: a wire URL feeds this, and the key only feeds the hash,
127
     so a clipped one costs a collision and never a wrong dedup */
128
0
  (void) strclipbuff(dst, dstsize, adr_norm);
129
130
  // copy link
131
0
  assertf(fil != NULL);
132
0
  {
133
    /* resolve the per-URL strip keys; strip applies even when urlhack is off */
134
0
    char BIGSTK keybuf[HTS_URLMAXSIZE];
135
0
    const char *const keys = hts_query_strip_keys(hash->strip_query, adr_canon,
136
0
                                                  fil, keybuf, sizeof(keybuf));
137
0
    const size_t used = strlen(dst);
138
0
    char *const tail = &dst[used];
139
0
    const size_t avail = dstsize - used;
140
141
    /* normalizing never expands, and it takes a path of at most its own
142
       HTS_URLMAXSIZE * 2, so bound on both */
143
0
    if ((hash->norm_slash || hash->norm_query || keys != NULL) &&
144
0
        strlen(fil) < avail && strlen(fil) < HTS_URLMAXSIZE * 2) {
145
0
      fil_normalized_filtered_ex(fil, tail, keys, hash->norm_slash,
146
0
                                 hash->norm_query);
147
0
    } else {
148
0
      (void) strclipbuff(tail, avail, fil);
149
0
    }
150
0
  }
151
0
  return dst;
152
0
}
153
154
/* Pseudo-key (lien_url structure) hash function */
155
static coucal_hashkeys
156
0
key_adrfil_hashes_generic(void *arg, coucal_key_const value, const int former) {
157
0
  hash_struct *const hash = (hash_struct *) arg;
158
0
  const lien_url *const lien = (const lien_url *) value;
159
0
  const char *const adr = !former ? lien->adr : lien->former_adr;
160
0
  const char *const fil = !former ? lien->fil : lien->former_fil;
161
162
0
  return coucal_hash_string(
163
0
      hash_url_key(hash, adr, fil, hash->normfil, sizeof(hash->normfil)));
164
0
}
165
166
/* Pseudo-key (lien_url structure) comparison function */
167
static int key_adrfil_equals_generic(void *arg,
168
                                     coucal_key_const a_,
169
                                     coucal_key_const b_, 
170
0
                                     const int former) {
171
0
  hash_struct *const hash = (hash_struct *) arg;
172
0
  const lien_url*const a = (const lien_url*) a_;
173
0
  const lien_url*const b = (const lien_url*) b_;
174
0
  const char *const a_adr = !former ? a->adr : a->former_adr;
175
0
  const char *const b_adr = !former ? b->adr : b->former_adr;
176
0
  const char *const a_fil = !former ? a->fil : a->former_fil;
177
0
  const char *const b_fil = !former ? b->fil : b->former_fil;
178
0
  char BIGSTK a_aliasbuf[HTS_URLMAXSIZE * 2], b_aliasbuf[HTS_URLMAXSIZE * 2];
179
0
  const char *a_canon, *b_canon;
180
0
  const char *ja;
181
0
  const char *jb;
182
183
  // safety
184
0
  assertf(a_adr != NULL);
185
0
  assertf(b_adr != NULL);
186
0
  assertf(a_fil != NULL);
187
0
  assertf(b_fil != NULL);
188
189
0
  a_canon = key_host_alias(hash, a_adr, a_aliasbuf, sizeof(a_aliasbuf));
190
0
  b_canon = key_host_alias(hash, b_adr, b_aliasbuf, sizeof(b_aliasbuf));
191
192
  // skip scheme and authentication to the domain (possibly without www.)
193
0
  ja = hash->norm_host ? jump_normalized_const(a_canon)
194
0
                       : jump_identification_const(a_canon);
195
0
  jb = hash->norm_host ? jump_normalized_const(b_canon)
196
0
                       : jump_identification_const(b_canon);
197
0
  assertf(ja != NULL);
198
0
  assertf(jb != NULL);
199
0
  if (strcasecmp(ja, jb) != 0) {
200
0
    return 0;
201
0
  }
202
203
  // now compare pathes
204
0
  {
205
0
    char BIGSTK ka[HTS_URLMAXSIZE], kb[HTS_URLMAXSIZE];
206
0
    const char *const keysa =
207
0
        hts_query_strip_keys(hash->strip_query, a_canon, a_fil, ka, sizeof(ka));
208
0
    const char *const keysb =
209
0
        hts_query_strip_keys(hash->strip_query, b_canon, b_fil, kb, sizeof(kb));
210
211
0
    if (hash->norm_slash || hash->norm_query || keysa != NULL ||
212
0
        keysb != NULL) {
213
0
      fil_normalized_filtered_ex(a_fil, hash->normfil, keysa, hash->norm_slash,
214
0
                                 hash->norm_query);
215
0
      fil_normalized_filtered_ex(b_fil, hash->normfil2, keysb, hash->norm_slash,
216
0
                                 hash->norm_query);
217
0
      return strcmp(hash->normfil, hash->normfil2) == 0;
218
0
    } else {
219
0
      return strcmp(a_fil, b_fil) == 0;
220
0
    }
221
0
  }
222
0
}
223
224
static const char* key_adrfil_debug_print_(void *arg,
225
                                           coucal_key_const a_,
226
0
                                           const int former) {
227
0
  hash_struct *const hash = (hash_struct*) arg;
228
0
  const lien_url*const a = (const lien_url*) a_;
229
0
  const char *const a_adr = !former ? a->adr : a->former_adr;
230
0
  const char *const a_fil = !former ? a->fil : a->former_fil;
231
0
  snprintf(hash->normfil, sizeof(hash->normfil), "%s%s", a_adr, a_fil);
232
0
  return hash->normfil;
233
0
}
234
235
static const char* key_adrfil_debug_print(void *arg,
236
0
                                          coucal_key_const a_) {
237
0
  return key_adrfil_debug_print_(arg, a_, 0);
238
0
}
239
240
static const char* key_former_adrfil_debug_print(void *arg,
241
0
                                                 coucal_key_const a_) {
242
0
  return key_adrfil_debug_print_(arg, a_, 1);
243
0
}
244
245
static const char* value_adrfil_debug_print(void *arg,
246
0
                                            coucal_value_const value) {
247
0
  hash_struct *const hash = (hash_struct*) arg;
248
0
  snprintf(hash->normfil2, sizeof(hash->normfil2), "%d", (int) value.intg);
249
0
  return hash->normfil2;
250
0
}
251
252
/* "adr"/"fil" lien_url structure members hashing function */
253
0
static coucal_hashkeys key_adrfil_hashes(void *arg, coucal_key_const value_) {
254
0
  return key_adrfil_hashes_generic(arg, value_, 0);
255
0
}
256
257
/* "adr"/"fil" lien_url structure members comparison function */
258
static int key_adrfil_equals(void *arg,
259
                             coucal_key_const a,
260
0
                             coucal_key_const b) {
261
0
  return key_adrfil_equals_generic(arg, a, b, 0);
262
0
}
263
264
/* "former_adr"/"former_fil" lien_url structure members hashing function */
265
0
static coucal_hashkeys key_former_adrfil_hashes(void *arg, coucal_key_const value_) {
266
0
  return key_adrfil_hashes_generic(arg, value_, 1);
267
0
}
268
269
/* "former_adr"/"former_fil" lien_url structure members comparison function */
270
static int key_former_adrfil_equals(void *arg,
271
                                    coucal_key_const a,
272
0
                                    coucal_key_const b) {
273
0
  return key_adrfil_equals_generic(arg, a, b, 1);
274
0
}
275
276
0
void hash_init(httrackp *opt, hash_struct *hash, hts_boolean normalized) {
277
0
  hash->sav = coucal_new(0);
278
0
  hash->adrfil = coucal_new(0);
279
0
  hash->former_adrfil = coucal_new(0);
280
  /* urlhack is the umbrella; per-feature negatives opt out of each part */
281
0
  hash->norm_host = normalized && !opt->no_www_dedup;
282
0
  hash->norm_slash = normalized && !opt->no_slash_dedup;
283
0
  hash->norm_query = normalized && !opt->no_query_dedup;
284
  /* snapshot the query-strip list (not owned; valid for the hash lifetime) */
285
0
  hash->strip_query =
286
0
      StringNotEmpty(opt->strip_query) ? StringBuff(opt->strip_query) : NULL;
287
  /* same for the host-alias rules: they apply whatever urlhack says */
288
0
  hash->host_alias = hts_host_alias_rules(opt);
289
290
0
  hts_set_hash_handler(hash->sav, opt);
291
0
  hts_set_hash_handler(hash->adrfil, opt);
292
0
  hts_set_hash_handler(hash->former_adrfil, opt);
293
294
0
  coucal_set_name(hash->sav, "hash->sav");
295
0
  coucal_set_name(hash->adrfil, "hash->adrfil");
296
0
  coucal_set_name(hash->former_adrfil, "hash->former_adrfil");
297
298
  /* Case-insensitive comparison ; keys are direct char* filenames */
299
0
  coucal_value_set_key_handler(hash->sav,
300
0
                                key_duphandler,
301
0
                                key_freehandler,
302
0
                                key_sav_hashes,
303
0
                                key_sav_equals,
304
0
                                hash);
305
306
  /* URL-style comparison ; keys are lien_url structure pointers casted 
307
     to char* */
308
0
  coucal_value_set_key_handler(hash->adrfil,
309
0
                                key_duphandler,
310
0
                                key_freehandler,
311
0
                                key_adrfil_hashes,
312
0
                                key_adrfil_equals,
313
0
                                hash);
314
0
  coucal_value_set_key_handler(hash->former_adrfil,
315
0
                                key_duphandler,
316
0
                                key_freehandler,
317
0
                                key_former_adrfil_hashes,
318
0
                                key_former_adrfil_equals,
319
0
                                hash);
320
321
  /* pretty-printing */
322
0
  coucal_set_print_handler(hash->sav,
323
0
                            key_sav_debug_print,
324
0
                            value_sav_debug_print,
325
0
                            NULL);
326
0
  coucal_set_print_handler(hash->adrfil,
327
0
                            key_adrfil_debug_print,
328
0
                            value_adrfil_debug_print,
329
0
                            hash);
330
0
  coucal_set_print_handler(hash->former_adrfil,
331
0
                            key_former_adrfil_debug_print,
332
0
                            value_adrfil_debug_print,
333
0
                            hash);
334
0
}
335
336
0
void hash_free(hash_struct *hash) {
337
0
  if (hash != NULL) {
338
0
    coucal_delete(&hash->sav);
339
0
    coucal_delete(&hash->adrfil);
340
0
    coucal_delete(&hash->former_adrfil);
341
0
  }
342
0
}
343
344
/* Test helper: do the two URLs dedupe to the same key under opt's urlhack
345
   flags? Exercises the live hash compare (norm_host/slash/query resolution). */
346
hts_boolean hash_url_equals(httrackp *opt, const char *adra, const char *fila,
347
0
                            const char *adrb, const char *filb) {
348
0
  hash_struct hash;
349
0
  lien_url la, lb;
350
0
  hts_boolean eq;
351
352
0
  memset(&la, 0, sizeof(la));
353
0
  memset(&lb, 0, sizeof(lb));
354
0
  la.adr = key_duphandler(NULL, adra);
355
0
  la.fil = key_duphandler(NULL, fila);
356
0
  lb.adr = key_duphandler(NULL, adrb);
357
0
  lb.fil = key_duphandler(NULL, filb);
358
0
  hash_init(opt, &hash, opt->urlhack);
359
0
  eq = key_adrfil_equals(&hash, &la, &lb);
360
0
  hash_free(&hash);
361
0
  return eq;
362
0
}
363
364
// retour: position ou -1 si non trouvé
365
int hash_read(const hash_struct * hash, const char *nom1, const char *nom2,
366
0
              hash_struct_type type) {
367
0
  intptr_t intvalue;
368
0
  lien_url lien;
369
370
  /* read */
371
0
  switch(type) {
372
0
  case HASH_STRUCT_FILENAME:
373
0
    if (coucal_read(hash->sav, nom1, &intvalue)) {
374
0
      return (int) intvalue;
375
0
    } else {
376
0
      return -1;
377
0
    }
378
0
    break;
379
0
  case HASH_STRUCT_ADR_PATH:
380
0
    memset(&lien, 0, sizeof(lien));
381
0
    lien.adr = key_duphandler(NULL, nom1);
382
0
    lien.fil = key_duphandler(NULL, nom2);
383
0
    if (coucal_read(hash->adrfil, (char*) &lien, &intvalue)) {
384
0
      return (int) intvalue;
385
0
    } else {
386
0
      return -1;
387
0
    }
388
0
    break;
389
0
  case HASH_STRUCT_ORIGINAL_ADR_PATH:
390
0
    memset(&lien, 0, sizeof(lien));
391
0
    lien.former_adr = key_duphandler(NULL, nom1);
392
0
    lien.former_fil = key_duphandler(NULL, nom2);
393
0
    if (coucal_read(hash->former_adrfil, (char*) &lien, &intvalue)) {
394
0
      return (int) intvalue;
395
0
    } else {
396
0
      return -1;
397
0
    }
398
0
    break;
399
0
  default:
400
0
    assertf(! "unexpected case");
401
0
    return -1;
402
0
    break;
403
0
  }
404
0
}
405
406
// enregistrement lien lpos dans les 3 tables hash1..3
407
0
void hash_write(hash_struct * hash, size_t lpos) {
408
  /* first entry: destination filename (lowercased) */
409
0
  coucal_write(hash->sav, (*hash->liens)[lpos]->sav, lpos);
410
411
  /* second entry: URL address and path */
412
0
  coucal_write(hash->adrfil, (*hash->liens)[lpos], lpos);
413
414
  /* third entry: URL address and path before redirect */
415
0
  if ((*hash->liens)[lpos]->former_adr) {        // former_adr existe?
416
0
    coucal_write(hash->former_adrfil, (*hash->liens)[lpos], lpos);
417
0
  }
418
0
}