/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 | } |