/src/httrack/src/htsindex.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: htsindex.c */ |
30 | | /* keyword indexing system (search index) */ |
31 | | /* Author: Xavier Roche */ |
32 | | /* ------------------------------------------------------------ */ |
33 | | |
34 | | /* Internal engine bytecode */ |
35 | | #define HTS_INTERNAL_BYTECODE |
36 | | |
37 | | #include "htsindex.h" |
38 | | #include "htsglobal.h" |
39 | | #include "htslib.h" |
40 | | #include "htsio.h" |
41 | | |
42 | | #if HTS_MAKE_KEYWORD_INDEX |
43 | | #include "htshash.h" |
44 | | #include "coucal.h" |
45 | | |
46 | | /* Keyword Indexer Parameters */ |
47 | | |
48 | | // Maximum length for a keyword |
49 | 0 | #define KEYW_LEN 50 |
50 | | // Minimum length for a keyword - MUST NOT BE NULL!!! |
51 | 0 | #define KEYW_MIN_LEN 3 |
52 | | // What characters to accept? - MUST NOT BE EMPTY AND MUST NOT CONTAIN THE SPACE (32) CHARACTER!!! |
53 | 0 | #define KEYW_ACCEPT "abcdefghijklmnopqrstuvwxyz0123456789-_." |
54 | | // Convert A to a, and so on.. to avoid case problems in indexing |
55 | | // This can be a generic table, containing characters that are in fact not accepted by KEYW_ACCEPT |
56 | | // MUST HAVE SAME SIZES!! |
57 | 0 | #define KEYW_TRANSCODE_FROM (\ |
58 | 0 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ |
59 | 0 | "àâä" \ |
60 | 0 | "ÀÂÄ" \ |
61 | 0 | "éèêë" \ |
62 | 0 | "ÈÈÊË" \ |
63 | 0 | "ìîï" \ |
64 | 0 | "ÌÎÏ" \ |
65 | 0 | "òôö" \ |
66 | 0 | "ÒÔÖ" \ |
67 | 0 | "ùûü" \ |
68 | 0 | "ÙÛÜ" \ |
69 | 0 | "ÿ" \ |
70 | 0 | ) |
71 | 0 | #define KEYW_TRANSCODE_TO ( \ |
72 | 0 | "abcdefghijklmnopqrstuvwxyz" \ |
73 | 0 | "aaa" \ |
74 | 0 | "aaa" \ |
75 | 0 | "eeee" \ |
76 | 0 | "eeee" \ |
77 | 0 | "iii" \ |
78 | 0 | "iii" \ |
79 | 0 | "ooo" \ |
80 | 0 | "ooo" \ |
81 | 0 | "uuu" \ |
82 | 0 | "uuu" \ |
83 | 0 | "y" \ |
84 | 0 | ) |
85 | | // These (accepted) characters will be ignored at beginning of a keyword |
86 | 0 | #define KEYW_IGNORE_BEG "-_." |
87 | | // These (accepted) characters will be stripped if at the end of a keyword |
88 | 0 | #define KEYW_STRIP_END "-_." |
89 | | // Words beginning with these (accepted) characters will be ignored |
90 | 0 | #define KEYW_NOT_BEG "0123456789" |
91 | | // Treat these characters as space characters - MUST NOT BE EMPTY!!! |
92 | 0 | #define KEYW_SPACE " ',;:!?\"\x0d\x0a\x09\x0b\x0c" |
93 | | // Common words (the,for..) detector |
94 | | // If a word represents more than KEYW_USELESS1K (%1000) of total words, then ignore it |
95 | | // 5 (0.5%) |
96 | 0 | #define KEYW_USELESS1K 5 |
97 | | // If a word is present in more than KEYW_USELESS1KPG (%1000) pages, then ignore it |
98 | | // 800 (80%) |
99 | 0 | #define KEYW_USELESS1KPG 800 |
100 | | // This number will be reduced by index hit for sorting purpose |
101 | | // leave it as it is here if you don't REALLY know what you are doing |
102 | | // Yes, I may be the only person, maybe |
103 | 0 | #define KEYW_SORT_MAXCOUNT 999999999 |
104 | | |
105 | | /* End of Keyword Indexer Parameters */ |
106 | | |
107 | | int strcpos(const char *adr, char c); |
108 | | int mystrcmp(const void *_e1, const void *_e2); |
109 | | |
110 | | // Global variables |
111 | | int hts_index_init = 1; |
112 | | int hts_primindex_size = 0; |
113 | | FILE *fp_tmpproject = NULL; |
114 | | int hts_primindex_words = 0; |
115 | | |
116 | | #endif |
117 | | |
118 | | /* |
119 | | Init index |
120 | | */ |
121 | 0 | void index_init(const char *indexpath) { |
122 | 0 | #if HTS_MAKE_KEYWORD_INDEX |
123 | | /* remove(concat(indexpath,"index.txt")); */ |
124 | 0 | hts_index_init = 1; |
125 | 0 | hts_primindex_size = 0; |
126 | 0 | hts_primindex_words = 0; |
127 | 0 | fp_tmpproject = tmpfile(); |
128 | 0 | #endif |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | Indexing system |
133 | | A little bit dirty, (quick'n dirty, in fact) |
134 | | But should be okay on most cases |
135 | | Tags and javascript handled (ignored) |
136 | | */ |
137 | | /* Note: utf-8 */ |
138 | | int index_keyword(const char *html_data, LLint size, const char *mime, |
139 | 0 | const char *filename, const char *indexpath) { |
140 | 0 | #if HTS_MAKE_KEYWORD_INDEX |
141 | 0 | char catbuff[CATBUFF_SIZE]; |
142 | 0 | int intag = 0, inscript = 0, incomment = 0; |
143 | 0 | char keyword[KEYW_LEN + 32]; |
144 | 0 | int i = 0; |
145 | |
|
146 | 0 | coucal WordIndexHash = NULL; |
147 | 0 | FILE *tmpfp = NULL; |
148 | | |
149 | | // |
150 | | |
151 | | // Check parameters |
152 | 0 | if (!html_data) |
153 | 0 | return 0; |
154 | 0 | if (!size) |
155 | 0 | return 0; |
156 | 0 | if (!mime) |
157 | 0 | return 0; |
158 | 0 | if (!filename) |
159 | 0 | return 0; |
160 | | |
161 | | // Init ? |
162 | 0 | if (hts_index_init) { |
163 | 0 | UNLINK(concat(catbuff, sizeof(catbuff), indexpath, "index.txt")); |
164 | 0 | UNLINK(concat(catbuff, sizeof(catbuff), indexpath, "sindex.html")); |
165 | 0 | hts_index_init = 0; |
166 | 0 | } |
167 | | // Check MIME type |
168 | 0 | if (is_html_mime_type(mime)) { |
169 | 0 | inscript = 0; |
170 | 0 | } |
171 | | // FIXME - temporary fix for image/svg+xml (svg) |
172 | | // "IN XML" (html like, in fact :) ) |
173 | 0 | else if ((strfield2(mime, "image/svg+xml")) |
174 | 0 | || (strfield2(mime, "image/svg-xml"))) { |
175 | 0 | inscript = 0; |
176 | 0 | } else if (is_javascript_mime_type(mime) || (strfield2(mime, "text/css"))) { |
177 | 0 | inscript = 1; |
178 | 0 | } else |
179 | 0 | return 0; |
180 | | |
181 | | // Temporary file |
182 | 0 | tmpfp = tmpfile(); |
183 | 0 | if (!tmpfp) |
184 | 0 | return 0; |
185 | | |
186 | | // Create hash structure |
187 | | // Hash tables rulez da world! |
188 | 0 | WordIndexHash = coucal_new(0); |
189 | 0 | if (!WordIndexHash) |
190 | 0 | return 0; |
191 | | |
192 | | // Start indexing this page |
193 | 0 | keyword[0] = '\0'; |
194 | 0 | while(i < size) { |
195 | 0 | if (strfield(html_data + i, "<script")) { |
196 | 0 | inscript = 1; |
197 | 0 | } else if (strfield(html_data + i, "<!--")) { |
198 | 0 | incomment = 1; |
199 | 0 | } else if (strfield(html_data + i, "</script")) { |
200 | 0 | if (!incomment) |
201 | 0 | inscript = 0; |
202 | 0 | } else if (strfield(html_data + i, "-->")) { |
203 | 0 | incomment = 0; |
204 | 0 | } else if (html_data[i] == '<') { |
205 | 0 | if (!inscript) |
206 | 0 | intag = 1; |
207 | 0 | } else if (html_data[i] == '>') { |
208 | 0 | intag = 0; |
209 | 0 | } else { |
210 | | // Okay, parse keywords |
211 | 0 | if ((!inscript) && (!incomment) && (!intag)) { |
212 | 0 | char cchar = html_data[i]; |
213 | 0 | int pos; |
214 | 0 | int len = (int) strlen(keyword); |
215 | | |
216 | | // Replace (ignore case, and so on..) |
217 | 0 | if ((pos = strcpos(KEYW_TRANSCODE_FROM, cchar)) >= 0) |
218 | 0 | cchar = KEYW_TRANSCODE_TO[pos]; |
219 | |
|
220 | 0 | if (strchr(KEYW_ACCEPT, cchar)) { |
221 | | /* Ignore some characters at beginning */ |
222 | 0 | if ((len > 0) || (!strchr(KEYW_IGNORE_BEG, cchar))) { |
223 | 0 | keyword[len++] = cchar; |
224 | 0 | keyword[len] = '\0'; |
225 | 0 | } |
226 | 0 | } else if ((strchr(KEYW_SPACE, cchar)) || (!cchar)) { |
227 | | |
228 | | /* Avoid these words */ |
229 | 0 | if (len > 0) { |
230 | 0 | if (strchr(KEYW_NOT_BEG, keyword[0])) { |
231 | 0 | keyword[(len = 0)] = '\0'; |
232 | 0 | } |
233 | 0 | } |
234 | | |
235 | | /* Strip ending . and so */ |
236 | 0 | { |
237 | 0 | int ok = 0; |
238 | |
|
239 | 0 | while((len = (int) strlen(keyword)) && (!ok)) { |
240 | 0 | if (strchr(KEYW_STRIP_END, keyword[len - 1])) { /* strip it */ |
241 | 0 | keyword[len - 1] = '\0'; |
242 | 0 | } else |
243 | 0 | ok = 1; |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | /* Store it ? */ |
248 | 0 | if (len >= KEYW_MIN_LEN) { |
249 | 0 | hts_primindex_words++; |
250 | 0 | if (coucal_inc(WordIndexHash, keyword)) { /* added new */ |
251 | 0 | fprintf(tmpfp, "%s\n", keyword); |
252 | 0 | } |
253 | 0 | } |
254 | 0 | keyword[(len = 0)] = '\0'; |
255 | 0 | } else /* Invalid */ |
256 | 0 | keyword[(len = 0)] = '\0'; |
257 | |
|
258 | 0 | if (len > KEYW_LEN) { |
259 | 0 | keyword[(len = 0)] = '\0'; |
260 | 0 | } |
261 | 0 | } |
262 | |
|
263 | 0 | } |
264 | |
|
265 | 0 | i++; |
266 | 0 | } |
267 | | |
268 | | // Reset temp file |
269 | 0 | fseek(tmpfp, 0, SEEK_SET); |
270 | | |
271 | | // Process indexing for this page |
272 | 0 | { |
273 | 0 | if (fp_tmpproject) { |
274 | 0 | while(!feof(tmpfp)) { |
275 | 0 | char line[KEYW_LEN + 32]; |
276 | |
|
277 | 0 | linput(tmpfp, line, KEYW_LEN + 2); |
278 | 0 | if (strnotempty(line)) { |
279 | 0 | intptr_t e = 0; |
280 | |
|
281 | 0 | if (coucal_read(WordIndexHash, line, &e)) { |
282 | 0 | char BIGSTK savelst[HTS_URLMAXSIZE * 2]; |
283 | |
|
284 | 0 | e++; /* 0 means "once" */ |
285 | |
|
286 | 0 | if (strncmp((const char *) fslash(catbuff, sizeof(catbuff), (const char *) indexpath), filename, strlen(indexpath)) == 0) // couper |
287 | 0 | strcpybuff(savelst, filename + strlen(indexpath)); |
288 | 0 | else |
289 | 0 | strcpybuff(savelst, filename); |
290 | | |
291 | | // Add entry for this file and word |
292 | 0 | fprintf(fp_tmpproject, "%s %d %s\n", line, |
293 | 0 | (int) (KEYW_SORT_MAXCOUNT - e), savelst); |
294 | 0 | hts_primindex_size++; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | | // Delete temp file |
302 | 0 | fclose(tmpfp); |
303 | 0 | tmpfp = NULL; |
304 | | |
305 | | // Clear hash table |
306 | 0 | coucal_delete(&WordIndexHash); |
307 | 0 | #endif |
308 | 0 | return 1; |
309 | 0 | } |
310 | | |
311 | | /* |
312 | | Sort index! |
313 | | */ |
314 | | /* Note: NOT utf-8 */ |
315 | 0 | void index_finish(const char *indexpath, int mode) { |
316 | 0 | #if HTS_MAKE_KEYWORD_INDEX |
317 | 0 | char catbuff[CATBUFF_SIZE]; |
318 | 0 | char **tab; |
319 | 0 | char *blk; |
320 | 0 | const LLint fs = fpsize(fp_tmpproject); |
321 | | /* fail closed on a size size_t cannot hold: malloct() wraps short while |
322 | | the reader and the terminator keep the 64-bit length */ |
323 | 0 | const size_t size = fs > 0 ? llint_to_size_t(fs) : (size_t) -1; |
324 | |
|
325 | 0 | if (size != (size_t) -1) { |
326 | 0 | if (fp_tmpproject) { |
327 | 0 | tab = (char **) malloct(sizeof(char *) * (hts_primindex_size + 2)); |
328 | 0 | if (tab) { |
329 | 0 | blk = malloct(size + 1); |
330 | 0 | if (blk) { |
331 | 0 | fseek(fp_tmpproject, 0, SEEK_SET); |
332 | 0 | if (hts_fread_exact(blk, size, fp_tmpproject)) { |
333 | 0 | char *a = blk, *b; |
334 | 0 | int index = 0; |
335 | 0 | int i; |
336 | 0 | FILE *fp; |
337 | |
|
338 | 0 | blk[size] = '\0'; |
339 | 0 | while((b = strchr(a, '\n')) && (index < hts_primindex_size)) { |
340 | 0 | tab[index++] = a; |
341 | 0 | *b = '\0'; |
342 | 0 | a = b + 1; |
343 | 0 | } |
344 | | |
345 | | // Sort it! |
346 | 0 | qsort(tab, index, sizeof(char *), mystrcmp); |
347 | | |
348 | | // Delete fp_tmpproject |
349 | 0 | fclose(fp_tmpproject); |
350 | 0 | fp_tmpproject = NULL; |
351 | | |
352 | | // Write new file |
353 | 0 | if (mode == 1) // TEXT |
354 | 0 | fp = FOPEN( |
355 | 0 | concat(catbuff, sizeof(catbuff), indexpath, "index.txt"), |
356 | 0 | "wb"); |
357 | 0 | else // HTML |
358 | 0 | fp = FOPEN( |
359 | 0 | concat(catbuff, sizeof(catbuff), indexpath, "sindex.html"), |
360 | 0 | "wb"); |
361 | 0 | if (fp) { |
362 | 0 | char current_word[KEYW_LEN + 32]; |
363 | 0 | char word[KEYW_LEN + 32]; |
364 | 0 | int hit; |
365 | 0 | int total_hit = 0; |
366 | 0 | int total_line = 0; |
367 | 0 | int last_pos = 0; |
368 | 0 | char word0 = '\0'; |
369 | |
|
370 | 0 | current_word[0] = '\0'; |
371 | |
|
372 | 0 | if (mode == 2) { // HTML |
373 | 0 | for(i = 0; i < index; i++) { |
374 | 0 | if (word0 != tab[i][0]) { |
375 | 0 | word0 = tab[i][0]; |
376 | 0 | fprintf(fp, " <a href=\"#%c\">%c</a>\r\n", word0, word0); |
377 | 0 | } |
378 | 0 | } |
379 | 0 | word0 = '\0'; |
380 | 0 | fprintf(fp, "<br><br>\r\n"); |
381 | 0 | fprintf(fp, |
382 | 0 | "<table width=\"100%%\" border=\"0\">\r\n<tr>\r\n<td>word</td>\r\n<td>location\r\n"); |
383 | 0 | } |
384 | |
|
385 | 0 | for(i = 0; i < index; i++) { |
386 | 0 | if (sscanf(tab[i], "%s %d", word, &hit) == 2) { |
387 | 0 | char *a = strchr(tab[i], ' '); |
388 | |
|
389 | 0 | if (a) |
390 | 0 | a = strchr(a + 1, ' '); |
391 | 0 | if (a++) { /* Yes, a++, not ++a :) */ |
392 | 0 | hit = KEYW_SORT_MAXCOUNT - hit; |
393 | 0 | if (strcmp(word, current_word)) { /* New word */ |
394 | 0 | if (total_hit) { |
395 | 0 | if (mode == 1) // TEXT |
396 | 0 | fprintf(fp, "\t=%d\r\n", total_hit); |
397 | 0 | if ((((total_hit * 1000) / hts_primindex_words) >= |
398 | 0 | KEYW_USELESS1K) |
399 | 0 | || (((total_line * 1000) / index) >= |
400 | 0 | KEYW_USELESS1KPG) |
401 | 0 | ) { |
402 | 0 | fseek(fp, last_pos, SEEK_SET); |
403 | 0 | if (mode == 1) // TEXT |
404 | 0 | fprintf(fp, "\tignored (%d)\r\n", |
405 | 0 | ((total_hit * 1000) / hts_primindex_words)); |
406 | 0 | else |
407 | 0 | fprintf(fp, "(ignored) [%d hits]<br>\r\n", |
408 | 0 | total_hit); |
409 | 0 | } else { |
410 | 0 | if (mode == 1) // TEXT |
411 | 0 | fprintf(fp, "\t(%d)\r\n", |
412 | 0 | ((total_hit * 1000) / hts_primindex_words)); |
413 | 0 | } |
414 | 0 | } |
415 | 0 | if (mode == 1) // TEXT |
416 | 0 | fprintf(fp, "%s\r\n", word); |
417 | 0 | else { // HTML |
418 | 0 | fprintf(fp, "</td></tr>\r\n"); |
419 | 0 | if (word0 != word[0]) { |
420 | 0 | word0 = word[0]; |
421 | 0 | fprintf(fp, "<th>%c</th>\r\n", word0); |
422 | 0 | fprintf(fp, "<a name=\"%c\"></a>\r\n", word0); |
423 | 0 | } |
424 | 0 | fprintf(fp, "<tr>\r\n<td>%s</td>\r\n<td>\r\n", word); |
425 | 0 | } |
426 | 0 | fflush(fp); |
427 | 0 | last_pos = ftell(fp); |
428 | 0 | strcpybuff(current_word, word); |
429 | 0 | total_hit = total_line = 0; |
430 | 0 | } |
431 | 0 | total_hit += hit; |
432 | 0 | total_line++; |
433 | 0 | if (mode == 1) // TEXT |
434 | 0 | fprintf(fp, "\t%d %s\r\n", hit, a); |
435 | 0 | else // HTML |
436 | 0 | fprintf(fp, "<a href=\"%s\">%s</a> [%d hits]<br>\r\n", a, |
437 | 0 | a, hit); |
438 | 0 | } |
439 | 0 | } |
440 | 0 | } |
441 | 0 | if (mode == 2) // HTML |
442 | 0 | fprintf(fp, "</td></tr>\r\n</table>\r\n"); |
443 | 0 | fclose(fp); |
444 | 0 | } |
445 | 0 | } |
446 | 0 | freet(blk); |
447 | 0 | } |
448 | 0 | freet(tab); |
449 | 0 | } |
450 | |
|
451 | 0 | } |
452 | | //qsort |
453 | 0 | } |
454 | 0 | if (fp_tmpproject) |
455 | 0 | fclose(fp_tmpproject); |
456 | 0 | fp_tmpproject = NULL; |
457 | 0 | #endif |
458 | 0 | } |
459 | | |
460 | | /* Subroutines */ |
461 | | |
462 | | #if HTS_MAKE_KEYWORD_INDEX |
463 | 0 | int strcpos(const char *adr, char c) { |
464 | 0 | const char *apos = strchr(adr, c); |
465 | |
|
466 | 0 | if (apos) |
467 | 0 | return (int) (apos - adr); |
468 | 0 | else |
469 | 0 | return -1; |
470 | 0 | } |
471 | | |
472 | 0 | int mystrcmp(const void *_e1, const void *_e2) { |
473 | 0 | const char *const*const e1 = (const char *const*) _e1; |
474 | 0 | const char *const*const e2 = (const char *const*) _e2; |
475 | |
|
476 | 0 | return strcmp(*e1, *e2); |
477 | 0 | } |
478 | | #endif |