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