Coverage Report

Created: 2026-09-01 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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