Coverage Report

Created: 2026-09-01 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httrack/src/htslib.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: Subroutines                                            */
30
/* Author: Xavier Roche                                         */
31
/* ------------------------------------------------------------ */
32
33
/* Internal engine bytecode */
34
#define HTS_INTERNAL_BYTECODE
35
36
// Fichier librairie .c
37
38
#include "htscore.h"
39
#include "htssitemap.h"
40
#include "htswarc.h"
41
#include "htschanges.h"
42
#include "htssinglefile.h"
43
44
/* specific definitions */
45
#include "htsbase.h"
46
#include "htsio.h"
47
#include "htsnet.h"
48
#include "htsbauth.h"
49
#include "htsthread.h"
50
#include "htsback.h"
51
#include "htsftp.h"
52
#include "htswrap.h"
53
#include "htsmd5.h"
54
#include "htsmodules.h"
55
#include "htscharset.h"
56
#include "htsencoding.h"
57
#include "htscodec.h"
58
59
#ifdef _WIN32
60
#include <direct.h>
61
#else
62
#ifdef HAVE_SYS_TYPES_H
63
#include <sys/types.h>
64
#endif
65
#ifdef HAVE_SYS_STAT_H
66
#include <sys/stat.h>
67
#endif
68
#ifdef HAVE_UNISTD_H
69
#include <unistd.h>
70
#endif
71
#endif /* _WIN32 */
72
#include <stdarg.h>
73
74
#include <ctype.h>
75
#include <string.h>
76
#include <time.h>
77
#include <stdarg.h>
78
79
#ifndef _WIN32
80
#include <sys/time.h>
81
#else
82
#include <sys/timeb.h>
83
#endif
84
#include <fcntl.h>
85
86
// pour utimbuf
87
#ifdef _WIN32
88
#include <sys/utime.h>
89
#else
90
#include <utime.h>
91
#endif /* _WIN32 */
92
93
#include <sys/stat.h>
94
95
#ifdef __ANDROID__
96
#define timezone 0
97
#endif
98
/* END specific definitions */
99
100
/* Windows might be missing va_copy */
101
#ifdef _WIN32
102
#ifndef va_copy
103
#define va_copy(dst, src) ((dst) = (src))
104
#endif
105
#endif
106
107
// Debugging
108
#if _HTS_WIDE
109
FILE *DEBUG_fp = NULL;
110
#endif
111
112
/* variables globales */
113
int _DEBUG_HEAD;
114
FILE *ioinfo;
115
116
#if HTS_USEOPENSSL
117
SSL_CTX *openssl_ctx = NULL;
118
#endif
119
int IPV6_resolver = 0;
120
121
/* détection complémentaire */
122
const char *hts_detect[] = {
123
  "archive",
124
  "background",
125
  "data",                       // OBJECT
126
  "data-src",
127
  "data-srcset",
128
  "dynsrc",
129
  "lowsrc",
130
  "profile",                    // element META
131
  "src",
132
  "srcset",                     // HTML5 responsive images (<img>, <source>)
133
  "swurl",
134
  "url",
135
  "usemap",
136
  "longdesc",                   // accessibility
137
  "xlink:href",                 // xml/svg tag
138
  "poster",                     // HTML5
139
  ""
140
};
141
142
/* détecter début */
143
const char *hts_detectbeg[] = {
144
  "hotspot",                    /* hotspot1=..,hotspot2=.. */
145
  ""
146
};
147
148
/* ne pas détcter de liens dedans */
149
const char *hts_nodetect[] = {
150
  "accept-charset",
151
  "accesskey",
152
  "action",
153
  "align",
154
  "alt",
155
  "axes",
156
  "axis",
157
  "char",
158
  "charset",
159
  "cite",
160
  "class",
161
  "classid",
162
  "code",
163
  "color",
164
  "datetime",
165
  "dir",
166
  "enctype",
167
  "face",
168
  "height",
169
  "id",
170
  "lang",
171
  "language",
172
  "media",
173
  "method",
174
  "name",
175
  "prompt",
176
  "scheme",
177
  "size",
178
  "style",
179
  "target",
180
  "title",
181
  "type",
182
  "valign",
183
  "version",
184
  "width",
185
  ""
186
};
187
188
/* détection de mini-code javascript */
189
/* ALSO USED: detection based on the name: onXXX="<tag>" where XXX starts with upper case letter */
190
const char *hts_detect_js[] = {
191
  "onAbort",
192
  "onBlur",
193
  "onChange",
194
  "onClick",
195
  "onDblClick",
196
  "onDragDrop",
197
  "onError",
198
  "onFocus",
199
  "onKeyDown",
200
  "onKeyPress",
201
  "onKeyUp",
202
  "onLoad",
203
  "onMouseDown",
204
  "onMouseMove",
205
  "onMouseOut",
206
  "onMouseOver",
207
  "onMouseUp",
208
  "onMove",
209
  "onReset",
210
  "onResize",
211
  "onSelect",
212
  "onSubmit",
213
  "onUnload",
214
  "style",                      /* hack for CSS code data */
215
  ""
216
};
217
218
const char *hts_main_mime[] = {
219
  "application",
220
  "audio",
221
  "image",
222
  "message",
223
  "multipart",
224
  "text",
225
  "video",
226
  ""
227
};
228
229
/* détection "...URL=<url>" */
230
const char *hts_detectURL[] = {
231
  "content",
232
  ""
233
};
234
235
/* tags où l'URL doit être réécrite mais non capturée */
236
const char *hts_detectandleave[] = {
237
  "action",
238
  ""
239
};
240
241
/* ne pas renommer les types renvoyés (souvent types inconnus) */
242
const char *hts_mime_keep[] = {
243
  "application/octet-stream",
244
  "text/plain",
245
  "application/xml",
246
  "text/xml",
247
  ""
248
};
249
250
/* bogus servers returns these mime types when the extension is seen within the filename */
251
const char *hts_mime_bogus_multiple[] = {
252
  "application/x-wais-source",  /* src (src.rpm) */
253
  ""
254
};
255
256
/* pas de type mime connu, mais extension connue */
257
const char *hts_ext_dynamic[] = {
258
  "php3",
259
  "php",
260
  "php4",
261
  "php2",
262
  "cgi",
263
  "asp",
264
  "jsp",
265
  "pl",
266
  /*"exe", */
267
  "cfm",
268
  "nsf",                        /* lotus */
269
  ""
270
};
271
272
/* types MIME 
273
   note: application/octet-stream should not be used here
274
*/
275
const char *hts_mime[][2] = {
276
  {"application/acad", "dwg"},
277
  {"application/arj", "arj"},
278
  {"application/clariscad", "ccad"},
279
  {"application/drafting", "drw"},
280
  {"application/dxf", "dxf"},
281
  {"application/excel", "xls"},
282
  {"application/i-deas", "unv"},
283
  {"application/iges", "isg"},
284
  {"application/iges", "iges"},
285
  {"application/mac-binhex40", "hqx"},
286
  {"application/mac-compactpro", "cpt"},
287
  {"application/msword", "doc"},
288
  {"application/msword", "w6w"},
289
  {"application/msword", "word"},
290
  {"application/mswrite", "wri"},
291
  /*{"application/octet-stream","dms"}, */
292
  /*{"application/octet-stream","lzh"}, */
293
  /*{"application/octet-stream","lha"}, */
294
  /*{"application/octet-stream","bin"}, */
295
  {"application/oda", "oda"},
296
  {"application/pdf", "pdf"},
297
  {"application/postscript", "ps"},
298
  {"application/postscript", "ai"},
299
  {"application/postscript", "eps"},
300
  {"application/powerpoint", "ppt"},
301
  {"application/pro_eng", "prt"},
302
  {"application/pro_eng", "part"},
303
  {"application/rtf", "rtf"},
304
  {"application/set", "set"},
305
  {"application/sla", "stl"},
306
  {"application/smil", "smi"},
307
  {"application/smil", "smil"},
308
  {"application/smil", "sml"},
309
  {"application/solids", "sol"},
310
  {"application/STEP", "stp"},
311
  {"application/STEP", "step"},
312
  {"application/vda", "vda"},
313
  {"application/x-authorware-map", "aam"},
314
  {"application/x-authorware-seg", "aas"},
315
  {"application/x-authorware-bin", "aab"},
316
  {"application/x-bzip2", "bz2"},
317
  {"application/x-cocoa", "cco"},
318
  {"application/x-csh", "csh"},
319
  {"application/x-director", "dir"},
320
  {"application/x-director", "dcr"},
321
  {"application/x-director", "dxr"},
322
  {"application/x-mif", "mif"},
323
  {"application/x-dvi", "dvi"},
324
  {"application/x-gzip", "gz"},
325
  {"application/x-gzip", "gzip"},
326
  {"application/x-hdf", "hdf"},
327
  {"application/x-javascript", "js"},
328
  {"application/x-koan", "skp"},
329
  {"application/x-koan", "skd"},
330
  {"application/x-koan", "skt"},
331
  {"application/x-koan", "skm"},
332
  {"application/x-latex", "latex"},
333
  {"application/x-netcdf", "nc"},
334
  {"application/x-netcdf", "cdf"},
335
  /* {"application/x-sh","sh"}, */
336
  /* {"application/x-csh","csh"}, */
337
  /* {"application/x-ksh","ksh"}, */
338
  {"application/x-shar", "shar"},
339
  {"application/x-stuffit", "sit"},
340
  {"application/x-tcl", "tcl"},
341
  {"application/x-tex", "tex"},
342
  {"application/x-texinfo", "texinfo"},
343
  {"application/x-texinfo", "texi"},
344
  {"application/x-troff", "t"},
345
  {"application/x-troff", "tr"},
346
  {"application/x-troff", "roff"},
347
  {"application/x-troff-man", "man"},
348
  {"application/x-troff-me", "ms"},
349
  {"application/x-wais-source", "src"},
350
  {"application/zip", "zip"},
351
  {"application/x-zip-compressed", "zip"},
352
  {"application/x-bcpio", "bcpio"},
353
  {"application/x-cdlink", "vcd"},
354
  {"application/x-cpio", "cpio"},
355
  {"application/x-gtar", "tgz"},
356
  {"application/x-gtar", "gtar"},
357
  {"application/x-shar", "shar"},
358
  {"application/x-shockwave-flash", "swf"},
359
  {"application/x-sv4cpio", "sv4cpio"},
360
  {"application/x-sv4crc", "sv4crc"},
361
  {"application/x-tar", "tar"},
362
  {"application/x-ustar", "ustar"},
363
  {"application/x-winhelp", "hlp"},
364
  {"application/xml", "xml"},
365
  {"audio/midi", "mid"},
366
  {"audio/midi", "midi"},
367
  {"audio/midi", "kar"},
368
  {"audio/mpeg", "mp3"},
369
  {"audio/mpeg", "mpga"},
370
  {"audio/mpeg", "mp2"},
371
  {"audio/basic", "au"},
372
  {"audio/basic", "snd"},
373
  {"audio/x-aiff", "aif"},
374
  {"audio/x-aiff", "aiff"},
375
  {"audio/x-aiff", "aifc"},
376
  {"audio/x-pn-realaudio", "rm"},
377
  {"audio/x-pn-realaudio", "ram"},
378
  {"audio/x-pn-realaudio", "ra"},
379
  {"audio/x-pn-realaudio-plugin", "rpm"},
380
  {"audio/x-wav", "wav"},
381
  {"chemical/x-pdb", "pdb"},
382
  {"chemical/x-pdb", "xyz"},
383
  {"drawing/x-dwf", "dwf"},
384
  {"image/gif", "gif"},
385
  {"image/ief", "ief"},
386
  {"image/jpeg", "jpg"},
387
  {"image/jpeg", "jpe"},
388
  {"image/jpeg", "jpeg"},
389
  {"image/pict", "pict"},
390
  {"image/png", "png"},
391
  {"image/tiff", "tiff"},
392
  {"image/tiff", "tif"},
393
  {"image/svg+xml", "svg"},
394
  {"image/svg-xml", "svg"},
395
  {"image/x-cmu-raster", "ras"},
396
  {"image/x-freehand", "fh4"},
397
  {"image/x-freehand", "fh7"},
398
  {"image/x-freehand", "fh5"},
399
  {"image/x-freehand", "fhc"},
400
  {"image/x-freehand", "fh"},
401
  {"image/x-portable-anymap", "pnm"},
402
  {"image/x-portable-bitmap", "pgm"},
403
  {"image/x-portable-pixmap", "ppm"},
404
  {"image/x-rgb", "rgb"},
405
  {"image/x-xbitmap", "xbm"},
406
  {"image/x-xpixmap", "xpm"},
407
  {"image/x-xwindowdump", "xwd"},
408
  {"model/mesh", "msh"},
409
  {"model/mesh", "mesh"},
410
  {"model/mesh", "silo"},
411
  {"multipart/x-zip", "zip"},
412
  {"multipart/x-gzip", "gzip"},
413
  {"text/css", "css"},
414
  {"text/html", "html"},
415
  {"text/html", "htm"},
416
  {"text/plain", "txt"},
417
  {"text/plain", "g"},
418
  {"text/plain", "h"},
419
  {"text/plain", "c"},
420
  {"text/plain", "cc"},
421
  {"text/plain", "hh"},
422
  {"text/plain", "m"},
423
  {"text/plain", "f90"},
424
  {"text/richtext", "rtx"},
425
  {"text/tab-separated-values", "tsv"},
426
  {"text/x-setext", "etx"},
427
  {"text/x-sgml", "sgml"},
428
  {"text/x-sgml", "sgm"},
429
  {"text/xml", "xml"},
430
  {"text/xml", "dtd"},
431
  {"video/mpeg", "mpeg"},
432
  {"video/mpeg", "mpg"},
433
  {"video/mpeg", "mpe"},
434
  {"video/quicktime", "qt"},
435
  {"video/quicktime", "mov"},
436
  {"video/x-msvideo", "avi"},
437
  {"video/x-sgi-movie", "movie"},
438
  {"x-conference/x-cooltalk", "ice"},
439
  /*{"application/x-httpd-cgi","cgi"}, */
440
  {"x-world/x-vrml", "wrl"},
441
442
  /* More from w3schools.com */
443
  {"application/envoy", "evy"},
444
  {"application/fractals", "fif"},
445
  {"application/futuresplash", "spl"},
446
  {"application/hta", "hta"},
447
  {"application/internet-property-stream", "acx"},
448
  {"application/msword", "dot"},
449
  {"application/olescript", "axs"},
450
  {"application/pics-rules", "prf"},
451
  {"application/pkcs10", "p10"},
452
  {"application/pkix-crl", "crl"},
453
  {"application/set-payment-initiation", "setpay"},
454
  {"application/set-registration-initiation", "setreg"},
455
  {"application/vnd.ms-excel", "xls"},
456
  {"application/vnd.ms-excel", "xla"},
457
  {"application/vnd.ms-excel", "xlc"},
458
  {"application/vnd.ms-excel", "xlm"},
459
  {"application/vnd.ms-excel", "xlt"},
460
  {"application/vnd.ms-excel", "xlw"},
461
  {"application/vnd.ms-pkicertstore", "sst"},
462
  {"application/vnd.ms-pkiseccat", "cat"},
463
  {"application/vnd.ms-powerpoint", "ppt"},
464
  {"application/vnd.ms-powerpoint", "pot"},
465
  {"application/vnd.ms-powerpoint", "pps"},
466
  {"application/vnd.ms-project", "mpp"},
467
  {"application/vnd.ms-works", "wcm"},
468
  {"application/vnd.ms-works", "wdb"},
469
  {"application/vnd.ms-works", "wks"},
470
  {"application/vnd.ms-works", "wps"},
471
  {"application/vnd.oasis.opendocument.chart", "odc"},
472
  {"application/vnd.oasis.opendocument.database", "odb"},
473
  {"application/vnd.oasis.opendocument.formula", "odf"},
474
  {"application/vnd.oasis.opendocument.graphics", "odg"},
475
  {"application/vnd.oasis.opendocument.graphics-template", "otg"},
476
  {"application/vnd.oasis.opendocument.image", "odi"},
477
  {"application/vnd.oasis.opendocument.presentation", "odp"},
478
  {"application/vnd.oasis.opendocument.presentation-template", "otp"},
479
  {"application/vnd.oasis.opendocument.spreadsheet", "ods"},
480
  {"application/vnd.oasis.opendocument.spreadsheet-template", "ots"},
481
  {"application/vnd.oasis.opendocument.text", "odt"},
482
  {"application/vnd.oasis.opendocument.text-master", "odm"},
483
  {"application/vnd.oasis.opendocument.text-template", "ott"},
484
  {"application/vnd.oasis.opendocument.text-web", "oth"},
485
  {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"},
486
  {"application/vnd.openxmlformats-officedocument.presentationml.slide", "sldx"},
487
  {"application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx"},
488
  {"application/vnd.openxmlformats-officedocument.presentationml.template", "potx"},
489
  {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"},
490
  {"application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx"},
491
  {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"},
492
  {"application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx"},
493
  {"application/x-compress", "z"},
494
  {"application/x-compressed", "tgz"},
495
  {"application/x-internet-signup", "ins"},
496
  {"application/x-internet-signup", "isp"},
497
  {"application/x-iphone", "iii"},
498
  {"application/x-javascript", "js"},
499
  {"application/x-msaccess", "mdb"},
500
  {"application/x-mscardfile", "crd"},
501
  {"application/x-msclip", "clp"},
502
  {"application/x-msmediaview", "m13"},
503
  {"application/x-msmediaview", "m14"},
504
  {"application/x-msmediaview", "mvb"},
505
  {"application/x-msmetafile", "wmf"},
506
  {"application/x-msmoney", "mny"},
507
  {"application/x-mspublisher", "pub"},
508
  {"application/x-msschedule", "scd"},
509
  {"application/x-msterminal", "trm"},
510
  {"application/x-perfmon", "pma"},
511
  {"application/x-perfmon", "pmc"},
512
  {"application/x-perfmon", "pml"},
513
  {"application/x-perfmon", "pmr"},
514
  {"application/x-perfmon", "pmw"},
515
  {"application/x-pkcs12", "p12"},
516
  {"application/x-pkcs12", "pfx"},
517
  {"application/x-pkcs7-certificates", "p7b"},
518
  {"application/x-pkcs7-certificates", "spc"},
519
  {"application/x-pkcs7-certreqresp", "p7r"},
520
  {"application/x-pkcs7-mime", "p7c"},
521
  {"application/x-pkcs7-mime", "p7m"},
522
  {"application/x-pkcs7-signature", "p7s"},
523
  {"application/x-troff-me", "me"},
524
  {"application/x-x509-ca-cert", "cer"},
525
  {"application/x-x509-ca-cert", "crt"},
526
  {"application/x-x509-ca-cert", "der"},
527
  {"application/ynd.ms-pkipko", "pko"},
528
  {"audio/mid", "mid"},
529
  {"audio/mid", "rmi"},
530
  {"audio/mpeg", "mp3"},
531
  {"audio/x-mpegurl", "m3u"},
532
  {"image/bmp", "bmp"},
533
  {"image/cis-cod", "cod"},
534
  {"image/pipeg", "jfif"},
535
  {"image/x-cmx", "cmx"},
536
  {"image/x-icon", "ico"},
537
  {"image/x-portable-bitmap", "pbm"},
538
  {"message/rfc822", "mht"},
539
  {"message/rfc822", "mhtml"},
540
  {"message/rfc822", "nws"},
541
  {"text/css", "css"},
542
  {"text/h323", "323"},
543
  {"text/html", "stm"},
544
  {"text/iuls", "uls"},
545
  {"text/plain", "bas"},
546
  {"text/scriptlet", "sct"},
547
  {"text/webviewhtml", "htt"},
548
  {"text/x-component", "htc"},
549
  {"text/x-vcard", "vcf"},
550
  {"video/mpeg", "mp2"},
551
  {"video/mpeg", "mpa"},
552
  {"video/mpeg", "mpv2"},
553
  {"video/x-la-asf", "lsf"},
554
  {"video/x-la-asf", "lsx"},
555
  {"video/x-ms-asf", "asf"},
556
  {"video/x-ms-asf", "asr"},
557
  {"video/x-ms-asf", "asx"},
558
  {"video/x-ms-wmv", "wmv"},
559
  {"x-world/x-vrml", "flr"},
560
  {"x-world/x-vrml", "vrml"},
561
  {"x-world/x-vrml", "wrz"},
562
  {"x-world/x-vrml", "xaf"},
563
  {"x-world/x-vrml", "xof"},
564
565
  /* Various */
566
  {"application/ogg", "ogg"},
567
568
  {"application/x-java-vm", "class"},
569
  {"application/x-bittorrent","torrent"},
570
571
  {"", ""}
572
};
573
574
/* Modern web formats (post-2010), kept in their own table: appending to the
575
   legacy hts_mime[] above makes clang-format reflow its whole initializer.
576
   Scanned after hts_mime[], so it never shadows a legacy mapping. */
577
static const char *hts_mime_modern[][2] = {
578
    {"image/webp", "webp"},
579
    {"image/avif", "avif"},
580
    {"image/heic", "heic"},
581
    {"font/woff", "woff"},
582
    {"font/woff2", "woff2"},
583
    {"font/ttf", "ttf"},
584
    {"font/otf", "otf"},
585
    {"application/json", "json"},
586
    {"application/ld+json", "jsonld"},
587
    {"application/manifest+json", "webmanifest"},
588
    {"application/wasm", "wasm"},
589
    {"text/javascript", "js"},
590
    {"text/javascript", "mjs"},
591
    {"text/markdown", "md"},
592
    {"video/mp4", "mp4"},
593
    {"video/webm", "webm"},
594
    {"video/ogg", "ogv"},
595
    {"video/mp2t", "ts"},
596
    {"audio/mp4", "m4a"},
597
    {"audio/aac", "aac"},
598
    {"audio/ogg", "oga"},
599
    {"audio/opus", "opus"},
600
    {"audio/flac", "flac"},
601
    {"audio/webm", "weba"},
602
    {"application/x-7z-compressed", "7z"},
603
    {"application/x-rar-compressed", "rar"},
604
    {"application/zstd", "zst"},
605
    {"", ""}};
606
607
// Reserved (RFC2396)
608
547k
#define CIS(c,ch) ( ((unsigned char)(c)) == (ch) )
609
#define CHAR_RESERVED(c)                                                       \
610
26.2k
  (CIS(c, ';') || CIS(c, '/') || CIS(c, '?') || CIS(c, ':') || CIS(c, '@') ||  \
611
13.1k
   CIS(c, '&') || CIS(c, '=') || CIS(c, '+') || CIS(c, '$') || CIS(c, ','))
612
// Delimiters (RFC2396)
613
#define CHAR_DELIM(c)                                                          \
614
23.7k
  (CIS(c, '<') || CIS(c, '>') || CIS(c, '#') || CIS(c, '%') || CIS(c, '\"'))
615
// Unwise (RFC2396)
616
#define CHAR_UNWISE(c)                                                         \
617
22.7k
  (CIS(c, '{') || CIS(c, '}') || CIS(c, '|') || CIS(c, '\\') || CIS(c, '^') || \
618
9.57k
   CIS(c, '[') || CIS(c, ']') || CIS(c, '`'))
619
// Special (escape chars) (RFC2396 + >127 )
620
21.1k
#define CHAR_LOW(c)       ( ((unsigned char)(c) <= 31) )
621
3.10k
#define CHAR_HIG(c)       ( ((unsigned char)(c) >= 127) )
622
0
#define CHAR_SPECIAL(c)   ( CHAR_LOW(c) || CHAR_HIG(c) )
623
// We try to avoid them and encode them instead
624
#define CHAR_XXAVOID(c)                                                        \
625
15.3k
  (CIS(c, ' ') || CIS(c, '*') || CIS(c, '\'') || CIS(c, '\"') ||               \
626
7.66k
   CIS(c, '&') || CIS(c, '!'))
627
#define CHAR_MARK(c)                                                           \
628
0
  (CIS(c, '-') || CIS(c, '_') || CIS(c, '.') || CIS(c, '!') || CIS(c, '~') ||  \
629
0
   CIS(c, '*') || CIS(c, '\'') || CIS(c, '(') || CIS(c, ')'))
630
631
// conversion éventuelle / vers antislash
632
#ifdef _WIN32
633
char *antislash(char *catbuff, const char *s) {
634
  char *a;
635
636
  strcpybuff(catbuff, s);
637
  while(a = strchr(catbuff, '/'))
638
    *a = '\\';
639
  return catbuff;
640
}
641
#endif
642
643
// Initialize a htsblk structure
644
0
void hts_init_htsblk(htsblk * r) {
645
0
  memset(r, 0, sizeof(htsblk)); // effacer
646
0
  r->soc = INVALID_SOCKET;
647
0
  r->msg[0] = '\0';
648
0
  r->statuscode = STATUSCODE_INVALID;
649
0
  r->totalsize = -1;
650
0
}
651
652
// ouverture d'une liaison http, envoi d'une requète
653
// mode: 0 GET  1 HEAD  [2 POST]
654
// treat: traiter header?
655
// waitconnect: attendre le connect()
656
// note: dans retour, on met les params du proxy
657
T_SOC http_xfopen(httrackp *opt, int mode, int treat, int waitconnect,
658
                  const char *xsend, const char *adr, const char *fil,
659
0
                  htsblk *retour) {
660
0
  T_SOC soc = INVALID_SOCKET;
661
0
  char BIGSTK tempo_fil[HTS_URLMAXSIZE * 2];
662
663
  // retour prédéfini: erreur
664
0
  if (retour) {
665
0
    retour->adr = NULL;
666
0
    retour->size = 0;
667
0
    retour->msg[0] = '\0';
668
0
    retour->statuscode = STATUSCODE_NON_FATAL;  // a priori erreur non fatale
669
0
  }
670
#if HDEBUG
671
  printf("adr=%s\nfichier=%s\n", adr, fil);
672
#endif
673
674
  // ouvrir liaison
675
#if HDEBUG
676
  printf("Création d'une socket sur %s\n", adr);
677
#endif
678
679
#if CNXDEBUG
680
  printf("..newhttp\n");
681
#endif
682
683
  /* connexion */
684
0
  if (retour) {
685
    /* no proxy, or proxy not usable here (local file) */
686
0
    if ((!(retour->req.proxy.active)) || (strcmp(adr, "file://") == 0)) {
687
0
      soc = newhttp(opt, adr, retour, -1, waitconnect);
688
0
    } else {
689
      // to the proxy; https tunnels to the origin via CONNECT in back_wait
690
      // (#85)
691
0
      soc = newhttp(opt, retour->req.proxy.name, retour, retour->req.proxy.port,
692
0
                    waitconnect);
693
0
    }
694
0
  } else {
695
0
    soc = newhttp(opt, adr, NULL, -1, waitconnect);
696
0
  }
697
698
  // copier index socket retour
699
0
  if (retour)
700
0
    retour->soc = soc;
701
702
  /* Check for errors */
703
0
  if (soc == INVALID_SOCKET) {
704
0
    if (retour) {
705
0
      if (!strnotempty(retour->msg)) {
706
#ifdef _WIN32
707
        int last_errno = WSAGetLastError();
708
709
        htsblk_failf(retour, "Connect error: %s", strerror(last_errno));
710
#else
711
0
        int last_errno = errno;
712
713
0
        htsblk_failf(retour, "Connect error: %s", strerror(last_errno));
714
0
#endif
715
0
      }
716
0
    }
717
0
  }
718
  // --------------------
719
  // court-circuit (court circuite aussi le proxy..)
720
  // LOCAL_SOCKET_ID est une pseudo-socket locale
721
0
  if (soc == LOCAL_SOCKET_ID) {
722
0
    retour->is_file = 1;        // fichier local
723
0
    if (mode == 0) {            // GET
724
725
      // Test en cas de file:///C|...
726
0
      if (!fexist
727
0
          (fconv(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
728
0
          unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), fil))))
729
0
        if (fexist
730
0
            (fconv
731
0
             (OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
732
0
             unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), fil + 1)))) {
733
0
          strcpybuff(tempo_fil, fil + 1);
734
0
          fil = tempo_fil;
735
0
        }
736
      // Ouvrir
737
0
      retour->totalsize = fsize(fconv(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), 
738
0
        unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), fil)));       // taille du fichier
739
0
      retour->msg[0] = '\0';
740
0
      soc = INVALID_SOCKET;
741
0
      if (retour->totalsize < 0)
742
0
        strcpybuff(retour->msg, "Unable to open local file");
743
0
      else {
744
        // Note: On passe par un FILE* (plus propre)
745
0
        retour->fp = FOPEN(fconv(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), 
746
0
          unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), fil)), "rb");      // ouvrir
747
0
        if (retour->fp == NULL)
748
0
          soc = INVALID_SOCKET;
749
0
        else
750
0
          soc = LOCAL_SOCKET_ID;
751
0
      }
752
0
      retour->soc = soc;
753
0
      if (soc != INVALID_SOCKET) {
754
0
        retour->statuscode = HTTP_OK;   // OK
755
0
        strcpybuff(retour->msg, "OK");
756
0
        guess_httptype_sized(opt, retour->contenttype,
757
0
                             sizeof(retour->contenttype), fil);
758
0
      } else if (strnotempty(retour->msg) == 0)
759
0
        strcpybuff(retour->msg, "Unable to open local file");
760
0
      return soc;               // renvoyer
761
0
    } else {                    // HEAD ou POST : interdit sur un local!!!! (c'est idiot!)
762
0
      strcpybuff(retour->msg, "Unexpected Head/Post local request");
763
0
      soc = INVALID_SOCKET;     // erreur
764
0
      retour->soc = soc;
765
0
      return soc;
766
0
    }
767
0
  }
768
  // --------------------
769
770
0
  if (soc != INVALID_SOCKET) {
771
0
    char rcvd[1100];
772
773
0
    rcvd[0] = '\0';
774
#if HDEBUG
775
    printf("Ok, connexion réussie, id=%d\n", soc);
776
#endif
777
778
    // connecté?
779
0
    if (waitconnect) {
780
0
      http_sendhead(opt, NULL, mode, xsend, adr, fil, NULL, NULL, retour);
781
0
    }
782
783
0
    if (soc != INVALID_SOCKET) {
784
785
#if HDEBUG
786
      printf("Attente de la réponse:\n");
787
#endif
788
789
      // si GET (réception d'un fichier), réceptionner en-tête d'abord,
790
      // et ensuite le corps
791
      // si POST on ne réceptionne rien du tout, c'est après que l'on fera
792
      // une réception standard pour récupérer l'en tête
793
0
      if ((treat) && (waitconnect)) {   // traiter (attendre!) en-tête
794
        // Réception de la status line et de l'en-tête (norme RFC1945)
795
796
        // status-line à récupérer
797
0
        finput_line(soc, rcvd, 1024);
798
        // some buggy servers send a leading \n (RFC)
799
0
        if (strnotempty(rcvd) == 0)
800
0
          finput_line(soc, rcvd, 1024);
801
802
        // traiter status-line
803
0
        treatfirstline(retour, rcvd);
804
805
#if HDEBUG
806
        printf("Status-Code=%d\n", retour->statuscode);
807
#endif
808
809
        // en-tête
810
811
        // header // ** !attention! HTTP/0.9 non supporté
812
0
        do {
813
0
          const hts_boolean cut = finput_line(soc, rcvd, 1024);
814
815
#if HDEBUG
816
          printf(">%s\n", rcvd);
817
#endif
818
0
          if (cut)
819
0
            hts_log_print(NULL, LOG_WARNING, "Over-long header dropped");
820
0
          else if (strnotempty(rcvd))
821
0
            treathead(NULL, NULL, NULL, retour, rcvd);
822
823
0
        } while(strnotempty(rcvd));
824
825
0
      } else { // si GET, on recevra l'en tête APRES
826
0
        if (retour)
827
0
          retour->totalsize = -1;
828
0
      }
829
0
    }
830
831
0
  }
832
833
0
  return soc;
834
0
}
835
836
/* Buffer printing */
837
typedef struct buff_struct {
838
  /** Buffer **/
839
  char *buffer;
840
  /** Buffer capacity in bytes **/
841
  size_t capacity;
842
  /** Buffer write position ; MUST point to a valid \0. **/
843
  size_t pos;
844
} buff_struct;
845
846
static void print_buffer(buff_struct*const str, const char *format, ...)
847
  HTS_PRINTF_FUN(2, 3);
848
849
/* Prints on a static buffer. asserts in case of overflow. */
850
0
static void print_buffer(buff_struct*const str, const char *format, ...) {
851
0
  size_t result;
852
0
  va_list args;
853
0
  size_t remaining;
854
0
  char *position;
855
856
  /* Security check. */
857
0
  assertf(str != NULL);
858
0
  assertf(str->pos < str->capacity);
859
860
  /* Print */
861
0
  position = &str->buffer[str->pos];
862
0
  remaining = str->capacity - str->pos;
863
0
  va_start(args, format);
864
0
  result = (size_t) vsnprintf(position, remaining, format, args);
865
0
  va_end(args);
866
0
  assertf(result < remaining);
867
868
  /* Increment. */
869
0
  str->pos += strlen(position);
870
0
  assertf(str->pos < str->capacity);
871
0
}
872
873
/* Append the request "Cookie:" header line for every stored cookie matching
874
   domain/path. RFC 6265 form: bare "name=value" pairs joined by "; ", no
875
   $Version/$Path attributes (those are RFC 2965 syntax that modern servers
876
   reject, issue #151). Returns the number of cookies emitted. */
877
static int append_cookie_header(buff_struct *bstr, t_cookie *cookie,
878
0
                                const char *domain, const char *path) {
879
0
  char buffer[8192];
880
0
  char *b;
881
0
  int cook = 0;
882
0
  int max_cookies = 8;
883
884
0
  if (cookie == NULL)
885
0
    return 0;
886
0
  b = cookie->data;
887
0
  do {
888
0
    b = cookie_find(b, "", domain, path); // next matching cookie
889
0
    if (b != NULL) {
890
0
      max_cookies--;
891
0
      if (!cook) {
892
0
        print_buffer(bstr, "Cookie: ");
893
0
        cook = 1;
894
0
      } else
895
0
        print_buffer(bstr, "; ");
896
0
      print_buffer(bstr, "%s", cookie_get(buffer, b, 5));
897
0
      print_buffer(bstr, "=%s", cookie_get(buffer, b, 6));
898
0
      b = cookie_nextfield(b);
899
0
    }
900
0
  } while (b != NULL && max_cookies > 0);
901
0
  if (cook)
902
0
    print_buffer(bstr, H_CRLF);
903
0
  return cook;
904
0
}
905
906
/* Build the request Cookie line for domain/path into dst (always
907
   NUL-terminated). Returns the number of cookies emitted. */
908
int http_cookie_header(t_cookie *cookie, const char *domain, const char *path,
909
0
                       char *dst, size_t dst_size) {
910
0
  buff_struct bstr = {dst, dst_size, 0};
911
912
0
  assertf(dst != NULL && dst_size > 0);
913
0
  dst[0] = '\0';
914
0
  return append_cookie_header(&bstr, cookie, domain, path);
915
0
}
916
917
0
hts_boolean hts_body_missing_unexpectedly(const htsblk *r) {
918
0
  if (r->adr != NULL || r->is_write != 0)
919
0
    return HTS_FALSE;
920
0
  if (HTTP_IS_REDIRECT(r->statuscode) ||
921
0
      r->statuscode == HTTP_PRECONDITION_FAILED ||
922
0
      r->statuscode == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE)
923
0
    return HTS_FALSE;
924
0
  return HTS_TRUE;
925
0
}
926
927
hts_boolean http_headers_have_field(const char *headers,
928
0
                                    const char *field_name) {
929
0
  size_t len;
930
0
  const char *line;
931
932
0
  if (headers == NULL || field_name == NULL)
933
0
    return HTS_FALSE;
934
0
  len = strlen(field_name);
935
0
  if (len != 0 && field_name[len - 1] == ':') /* the tree spells it both ways */
936
0
    len--;
937
0
  if (len == 0)
938
0
    return HTS_FALSE;
939
  /* anchored, so a folded line or a name inside a value cannot match */
940
0
  for (line = headers; *line != '\0';) {
941
0
    size_t i = 0;
942
943
0
    while (i < len && streql(line[i], field_name[i]))
944
0
      i++;
945
    /* RFC 7230 forbids a space before the colon: tolerating one would drop our
946
       line for a malformed one the server then ignores */
947
0
    if (i == len && line[len] == ':')
948
0
      return HTS_TRUE;
949
0
    while (*line != '\0' && *line != '\n' && *line != '\r')
950
0
      line++;
951
0
    while (*line == '\n' || *line == '\r')
952
0
      line++;
953
0
  }
954
0
  return HTS_FALSE;
955
0
}
956
957
/* Dropping a line drops its folded continuations too: kept, that value would
958
   attach to whichever header precedes it in the request. */
959
0
static void append_custom_headers(buff_struct *bstr, const char *headers) {
960
0
  const size_t base = bstr->pos;
961
0
  hts_boolean keep = HTS_TRUE;
962
0
  const char *line;
963
964
0
  for (line = headers; *line != '\0';) {
965
0
    const char *eol = line;
966
0
    const char *next;
967
968
0
    while (*eol != '\0' && *eol != '\r' && *eol != '\n')
969
0
      eol++;
970
0
    next = eol;
971
0
    if (next[0] == '\r' && next[1] == '\n')
972
0
      next += 2;
973
0
    else if (*next != '\0')
974
0
      next++;
975
976
0
    if (*line != ' ' && *line != '\t') { /* not a folded continuation */
977
0
      const char *colon = line;
978
0
      char field[128];
979
980
0
      keep = HTS_TRUE;
981
0
      while (colon < eol && *colon != ':')
982
0
        colon++;
983
0
      if (colon > line && colon < eol &&
984
0
          (size_t) (colon - line) < sizeof(field)) {
985
0
        const size_t len = (size_t) (colon - line);
986
0
        const char cut = bstr->buffer[base];
987
988
0
        memcpy(field, line, len);
989
0
        field[len] = '\0';
990
        /* only what the engine wrote, so the box may repeat its own fields */
991
0
        bstr->buffer[base] = '\0';
992
0
        keep = !http_headers_have_field(bstr->buffer, field);
993
0
        bstr->buffer[base] = cut;
994
0
      }
995
0
    }
996
0
    if (keep)
997
0
      print_buffer(bstr, "%.*s", (int) (next - line), line);
998
0
    line = next;
999
0
  }
1000
0
}
1001
1002
void http_append_custom_headers(char *dst, size_t dst_size,
1003
0
                                const char *headers) {
1004
0
  buff_struct bstr = {dst, dst_size, 0};
1005
1006
0
  assertf(dst != NULL && dst_size > 0);
1007
0
  bstr.pos = strlen(dst);
1008
0
  append_custom_headers(&bstr, headers);
1009
0
}
1010
1011
// envoi d'une requète
1012
int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
1013
                  const char *xsend, const char *adr, const char *fil,
1014
                  const char *referer_adr, const char *referer_fil,
1015
0
                  htsblk * retour) {
1016
0
  char BIGSTK buffer_head_request[16384];
1017
0
  buff_struct bstr = { buffer_head_request, sizeof(buffer_head_request), 0 };
1018
1019
0
  int direct_url = 0;           // ne pas analyser l'url (exemple: ftp://)
1020
0
  const char *search_tag = NULL;
1021
1022
  /* the extra-headers box wins: skip the engine's own line (#1337, #1340) */
1023
0
  const char *const custom = retour->req.headers;
1024
1025
  /* adr and the referer come off the network and can carry raw CR/LF; capped
1026
     at their own source buffers, so the worst case emitted does not grow. */
1027
0
  char BIGSTK esc[HTS_URLMAXSIZE * 2];
1028
1029
  // Initialize buffer
1030
0
  buffer_head_request[0] = '\0';
1031
1032
  // possibilité non documentée: >post: et >postfile:
1033
  // si présence d'un tag >post: alors executer un POST
1034
  // exemple: http://www.example.com/test.cgi?foo>post:posteddata=10&foo=5
1035
  // si présence d'un tag >postfile: alors envoyer en tête brut contenu dans le fichier en question
1036
  // exemple: http://www.example.com/test.cgi?foo>postfile:post0.txt
1037
0
  search_tag = strstr(fil, POSTTOK ":");
1038
0
  if (!search_tag) {
1039
0
    search_tag = strstr(fil, POSTTOK "file:");
1040
0
    if (search_tag) {           // postfile
1041
0
      if (mode == 0) {          // GET!
1042
0
        FILE *fp =
1043
0
          FOPEN(unescape_http(OPT_GET_BUFF(opt), 
1044
0
                OPT_GET_BUFF_SIZE(opt), search_tag + strlen(POSTTOK) + 5), "rb");
1045
0
        if (fp) {
1046
0
          char BIGSTK line[1100];
1047
0
          char BIGSTK protocol[256], url[HTS_URLMAXSIZE * 2], method[256];
1048
1049
0
          linput(fp, line, 1000);
1050
          /* widths bound method[256], url[HTS_URLMAXSIZE*2], protocol[256] */
1051
0
          if (sscanf(line, "%255s %2047s %255s", method, url, protocol) == 3) {
1052
0
            size_t ret;
1053
            // http proxy: absolute-URI; socks/CONNECT tunnel: origin-form
1054
0
            if (retour->req.proxy.active &&
1055
0
                !hts_proxy_is_socks(retour->req.proxy.name) &&
1056
0
                !hts_proxy_is_connect(retour->req.proxy.name)) {
1057
              /* only adr is raw here: the other three are %s-scanned */
1058
0
              print_buffer(&bstr, "%s http://%s%s %s\r\n", method,
1059
0
                           escape_check_url_addr(adr, esc, sizeof(esc)), url,
1060
0
                           protocol);
1061
0
            } else {
1062
0
              print_buffer(&bstr,
1063
0
                       "%s %s %s\r\n", method, url, protocol);
1064
0
            }
1065
            // lire le reste en brut
1066
0
            ret = fread(&bstr.buffer[bstr.pos],
1067
0
                        bstr.capacity - bstr.pos, 1, fp);
1068
0
            if ((int) ret < 0) {
1069
0
              return -1;
1070
0
            }
1071
0
            bstr.pos += strlen(&bstr.buffer[bstr.pos]);
1072
0
          }
1073
0
          fclose(fp);
1074
0
        }
1075
0
      }
1076
0
    }
1077
0
  }
1078
  // Fin postfile
1079
1080
0
  if (bstr.pos == 0) { // PAS POSTFILE
1081
    // Type de requète?
1082
0
    if ((search_tag) && (mode == 0)) {
1083
0
      print_buffer(&bstr, "POST ");
1084
0
    } else if (mode == 0) {     // GET
1085
0
      print_buffer(&bstr, "GET ");
1086
0
    } else {                    // if (mode==1) {
1087
0
      if (!retour->req.http11)  // forcer HTTP/1.0
1088
0
        print_buffer(&bstr, "GET ");       // certains serveurs (cgi) buggent avec HEAD
1089
0
      else
1090
0
        print_buffer(&bstr, "HEAD ");
1091
0
    }
1092
1093
    // an http proxy needs an absolute URI; a socks or CONNECT tunnel does not
1094
0
    if (retour->req.proxy.active &&
1095
0
        !hts_proxy_is_socks(retour->req.proxy.name) &&
1096
0
        !hts_proxy_is_connect(retour->req.proxy.name) &&
1097
0
        (strncmp(adr, "https://", 8) != 0)) {
1098
0
      if (!link_has_authority(adr)) {   // default http
1099
#if HDEBUG
1100
        printf("Proxy Use: for %s%s proxy %d port %d\n", adr, fil,
1101
               retour->req.proxy.name, retour->req.proxy.port);
1102
#endif
1103
0
        print_buffer(&bstr, "http://%s",
1104
0
                     escape_check_url_addr(jump_identification_const(adr), esc,
1105
0
                                           sizeof(esc)));
1106
0
      } else {                  // ftp:// en proxy http
1107
#if HDEBUG
1108
        printf("Proxy Use for ftp: for %s%s proxy %d port %d\n", adr, fil,
1109
               retour->req.proxy.name, retour->req.proxy.port);
1110
#endif
1111
0
        direct_url = 1;         // ne pas analyser user/pass
1112
0
        print_buffer(&bstr, "%s", escape_check_url_addr(adr, esc, sizeof(esc)));
1113
0
      }
1114
0
    }
1115
    // NOM DU FICHIER
1116
    // on slash doit être présent en début, sinon attention aux bad request! (400)
1117
0
    if (*fil != '/')
1118
0
      print_buffer(&bstr, "/");
1119
1120
0
    {
1121
0
      char BIGSTK tempo[HTS_URLMAXSIZE * 2];
1122
1123
0
      tempo[0] = '\0';
1124
0
      if (search_tag)
1125
0
        strncatbuff(tempo, fil, (int) (search_tag - fil));
1126
0
      else
1127
0
        strcpybuff(tempo, fil);
1128
0
      inplace_escape_check_url(tempo, sizeof(tempo));
1129
0
      print_buffer(&bstr, "%s", tempo);  // avec échappement
1130
0
    }
1131
1132
    // protocole
1133
0
    if (!retour->req.http11) { // forcer HTTP/1.0
1134
0
      print_buffer(&bstr, " HTTP/1.0\x0d\x0a");
1135
0
    } else { // Requète 1.1
1136
0
      print_buffer(&bstr, " HTTP/1.1\x0d\x0a");
1137
0
    }
1138
1139
    /* supplemental data */
1140
0
    if (xsend)
1141
0
      print_buffer(&bstr, "%s", xsend);  // éventuelles autres lignes
1142
1143
    // https/connect://: auth rides the CONNECT; socks: the handshake
1144
0
    if (retour->req.proxy.active &&
1145
0
        !hts_proxy_is_socks(retour->req.proxy.name) &&
1146
0
        !hts_proxy_is_connect(retour->req.proxy.name) &&
1147
0
        strncmp(adr, "https://", 8) != 0) {
1148
0
      if (link_has_authorization(retour->req.proxy.name) &&
1149
0
          !http_headers_have_field(custom, "Proxy-Authorization")) {
1150
0
        const char *a = jump_identification_const(retour->req.proxy.name);
1151
0
        const char *astart = jump_protocol_const(retour->req.proxy.name);
1152
0
        char autorisation[1100];
1153
0
        char user_pass[256];
1154
1155
0
        autorisation[0] = user_pass[0] = '\0';
1156
        //
1157
0
        strncatbuff(user_pass, astart, (int) (a - astart) - 1);
1158
0
        strcpybuff(user_pass, unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), user_pass));
1159
0
        code64((unsigned char *) user_pass, (int) strlen(user_pass),
1160
0
               (unsigned char *) autorisation, 0);
1161
0
        print_buffer(&bstr, "Proxy-Authorization: Basic %s"H_CRLF,
1162
0
                     autorisation);
1163
#if HDEBUG
1164
        printf("Proxy-Authenticate, %s (code: %s)\n", user_pass, autorisation);
1165
#endif
1166
0
      }
1167
0
    }
1168
    // Referer?
1169
0
    if (referer_adr != NULL && referer_fil != NULL && strnotempty(referer_adr)
1170
0
        && strnotempty(referer_fil)
1171
0
      ) {                       // non vide
1172
0
      if ((strcmp(referer_adr, "file://") != 0) &&
1173
0
          (/* no https referer to http urls */
1174
0
           (strncmp(referer_adr, "https://", 8) != 0) /* referer is not https */
1175
0
           || (strncmp(adr, "https://", 8) ==
1176
0
               0) /* or referer AND addresses are https */
1177
0
           ) &&
1178
0
          !http_headers_have_field(custom, "Referer")) { // not file://
1179
        /* one escape per piece, so neither can outgrow its own source buffer */
1180
0
        print_buffer(
1181
0
            &bstr, "Referer: http://%s",
1182
0
            escape_check_url_addr(jump_identification_const(referer_adr), esc,
1183
0
                                  sizeof(esc)));
1184
0
        print_buffer(&bstr, "%s" H_CRLF,
1185
0
                     escape_check_url_addr(referer_fil, esc, sizeof(esc)));
1186
0
      }
1187
0
    }
1188
    // HTTP field: referer
1189
0
    else if (strnotempty(retour->req.referer) &&
1190
0
             !http_headers_have_field(custom, "Referer")) {
1191
0
      print_buffer(&bstr, "Referer: %s"H_CRLF, retour->req.referer);
1192
0
    }
1193
    // POST?
1194
0
    if (mode == 0) {            // GET!
1195
0
      if (search_tag) {
1196
0
        print_buffer(&bstr, "Content-length: %d" H_CRLF,
1197
0
                (int) (strlen
1198
0
                       (unescape_http
1199
0
                        (OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
1200
0
                         search_tag + strlen(POSTTOK) + 1))));
1201
0
      }
1202
0
    }
1203
    // send stored cookies matching this host/path
1204
0
    if (cookie != NULL && !http_headers_have_field(custom, "Cookie")) {
1205
0
      append_cookie_header(&bstr, cookie, jump_identification_const(adr), fil);
1206
0
    }
1207
    // gérer le keep-alive (garder socket)
1208
0
    if (retour->req.http11 && !retour->req.nokeepalive) {
1209
0
      print_buffer(&bstr, "Connection: keep-alive" H_CRLF);
1210
0
    } else {
1211
0
      print_buffer(&bstr, "Connection: close" H_CRLF);
1212
0
    }
1213
1214
0
    {
1215
0
      const char *real_adr = jump_identification_const(adr);
1216
1217
      // Mandatory per RFC2616
1218
0
      if (!direct_url &&
1219
0
          !http_headers_have_field(custom, "Host")) { // not ftp:// for example
1220
0
        print_buffer(&bstr, "Host: %s" H_CRLF,
1221
0
                     escape_check_url_addr(real_adr, esc, sizeof(esc)));
1222
0
      }
1223
1224
      // HTTP field: from
1225
0
      if (strnotempty(retour->req.from) &&
1226
0
          !http_headers_have_field(custom, "From")) { // HTTP from
1227
0
        print_buffer(&bstr, "From: %s" H_CRLF, retour->req.from);
1228
0
      }
1229
1230
0
      if (retour->req.user_agent_send && strnotempty(retour->req.user_agent) &&
1231
0
          !http_headers_have_field(custom, "User-Agent")) {
1232
0
        print_buffer(&bstr, "User-Agent: %s" H_CRLF, retour->req.user_agent);
1233
0
      }
1234
1235
      // Accept
1236
0
      if (strnotempty(retour->req.accept) &&
1237
0
          !http_headers_have_field(custom, "Accept")) {
1238
0
        print_buffer(&bstr, "Accept: %s" H_CRLF, retour->req.accept);
1239
0
      }
1240
1241
      // Accept-language
1242
0
      if (strnotempty(retour->req.lang_iso) &&
1243
0
          !http_headers_have_field(custom, "Accept-Language")) {
1244
0
        print_buffer(&bstr, "Accept-Language: %s"H_CRLF, retour->req.lang_iso);
1245
0
      }
1246
1247
      // Compression accepted ?
1248
0
      if (retour->req.http11 &&
1249
0
          !http_headers_have_field(custom, "Accept-Encoding")) {
1250
0
        hts_boolean compressible =
1251
0
            (!retour->req.range_used && !retour->req.nocompression);
1252
0
        hts_boolean secure = HTS_FALSE;
1253
1254
#if HTS_USEOPENSSL
1255
        secure = retour->ssl ? HTS_TRUE : HTS_FALSE;
1256
#endif
1257
0
        print_buffer(&bstr, "Accept-Encoding: %s" H_CRLF,
1258
0
                     hts_acceptencoding(compressible, secure));
1259
0
      }
1260
1261
      /* Authentification */
1262
0
      {
1263
0
        char autorisation[1100];
1264
0
        const char *a;
1265
1266
0
        autorisation[0] = '\0';
1267
0
        if (link_has_authorization(adr)) {      // ohh une authentification!
1268
0
          const char *a = jump_identification_const(adr);
1269
0
          const char *astart = jump_protocol_const(adr);
1270
1271
0
          if (!direct_url) {    // pas ftp:// par exemple
1272
0
            char user_pass[256];
1273
1274
0
            user_pass[0] = '\0';
1275
0
            strncatbuff(user_pass, astart, (int) (a - astart) - 1);
1276
0
            strcpybuff(user_pass, 
1277
0
              unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), user_pass));
1278
0
            code64((unsigned char *) user_pass, (int) strlen(user_pass),
1279
0
                   (unsigned char *) autorisation, 0);
1280
0
            if (strcmp(fil, "/robots.txt"))     /* pas robots.txt */
1281
0
              bauth_add(cookie, astart, fil, autorisation);
1282
0
          }
1283
0
        } else if ((a = bauth_check(cookie, real_adr, fil)))
1284
0
          strcpybuff(autorisation, a);
1285
0
        if (strnotempty(autorisation) &&
1286
0
            !http_headers_have_field(custom, "Authorization")) {
1287
0
          print_buffer(&bstr, "Authorization: Basic %s"H_CRLF, autorisation);
1288
0
        }
1289
0
      }
1290
0
    }
1291
1292
    // Custom header(s), minus the fields the request already carries
1293
0
    if (strnotempty(custom)) {
1294
0
      append_custom_headers(&bstr, custom);
1295
0
    }
1296
1297
    // CRLF de fin d'en tête
1298
0
    print_buffer(&bstr, H_CRLF);
1299
1300
    // données complémentaires?
1301
0
    if (search_tag)
1302
0
      if (mode == 0)            // GET!
1303
0
        print_buffer(&bstr, "%s",
1304
0
                   unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
1305
0
                                 search_tag + strlen(POSTTOK) + 1));
1306
0
  }
1307
#if HDEBUG
1308
#endif
1309
0
  if (_DEBUG_HEAD) {
1310
0
    if (ioinfo) {
1311
0
      fprintf(ioinfo, "[%d] request for %s%s:\r\n", retour->debugid,
1312
0
              jump_identification_const(adr), fil);
1313
0
      fprintfio(ioinfo, bstr.buffer, "<<< ");
1314
0
      fprintf(ioinfo, "\r\n");
1315
0
      fflush(ioinfo);
1316
0
    }
1317
0
  }                             // Fin test pas postfile
1318
  //
1319
1320
  // Stash the raw request for the WARC request record (freed at
1321
  // back_clear_entry)
1322
0
  if (StringNotEmpty(opt->warc_file))
1323
0
    warc_stash_request(retour, bstr.buffer);
1324
1325
  // Callback
1326
0
  {
1327
0
    int test_head =
1328
0
      RUN_CALLBACK6(opt, sendhead, bstr.buffer, adr, fil, referer_adr, referer_fil,
1329
0
                    retour);
1330
0
    if (test_head != 1) {
1331
0
      deletesoc_r(retour);
1332
0
      strcpybuff(retour->msg, "Header refused by external wrapper");
1333
0
      retour->soc = INVALID_SOCKET;
1334
0
    }
1335
0
  }
1336
1337
  // Envoi
1338
0
  HTS_STAT.last_request = mtime_local();
1339
0
  if (sendc(retour, bstr.buffer) < 0) {        // ERREUR, socket rompue?...
1340
0
    deletesoc_r(retour);        // fermer tout de même
1341
    // et tenter de reconnecter
1342
1343
0
    strcpybuff(retour->msg, "Write error");
1344
0
    retour->soc = INVALID_SOCKET;
1345
0
  }
1346
1347
  // RX'98
1348
0
  return 0;
1349
0
}
1350
1351
// traiter 1ere ligne d'en tête
1352
0
void treatfirstline(htsblk * retour, const char *rcvd) {
1353
0
  const char *a = rcvd;
1354
1355
  // exemple:
1356
  // HTTP/1.0 200 OK
1357
0
  if (*a) {
1358
    // note: certains serveurs buggés renvoient HTTP/1.0\n200 OK ou " HTTP/1.0 200 OK"
1359
0
    while((*a == ' ') || (*a == 10) || (*a == 13) || (*a == 9))
1360
0
      a++;                      // épurer espaces au début
1361
0
    if (strfield(a, "HTTP/")) {
1362
      // sauter HTTP/1.x
1363
0
      while((*a != ' ') && (*a != '\0') && (*a != 10) && (*a != 13)
1364
0
            && (*a != 9))
1365
0
        a++;
1366
0
      if (*a != '\0') {
1367
0
        while((*a == ' ') || (*a == 10) || (*a == 13) || (*a == 9))
1368
0
          a++;                  // épurer espaces
1369
0
        if ((*a >= '0') && (*a <= '9')) {
1370
0
          sscanf(a, "%d", &(retour->statuscode));
1371
          // sauter 200
1372
0
          while((*a != ' ') && (*a != '\0') && (*a != 10) && (*a != 13)
1373
0
                && (*a != 9))
1374
0
            a++;
1375
0
          while((*a == ' ') || (*a == 10) || (*a == 13) || (*a == 9))
1376
0
            a++;                // épurer espaces
1377
0
          if ((strlen(a) > 1) && (strlen(a) < 64))      // message retour
1378
0
            strcpybuff(retour->msg, a);
1379
0
          else
1380
0
            infostatuscode(retour->msg, retour->statuscode);
1381
          // type MIME par défaut2
1382
0
          strcpybuff(retour->contenttype, HTS_UNKNOWN_MIME);
1383
0
        } else {                // pas de code!
1384
0
          retour->statuscode = STATUSCODE_INVALID;
1385
0
          strcpybuff(retour->msg, "Unknown response structure");
1386
0
        }
1387
0
      } else {                  // euhh??
1388
0
        retour->statuscode = STATUSCODE_INVALID;
1389
0
        strcpybuff(retour->msg, "Unknown response structure");
1390
0
      }
1391
0
    } else {
1392
0
      if (*a == '<') {
1393
        /* This is dirty .. */
1394
0
        retour->statuscode = HTTP_OK;
1395
0
        retour->keep_alive = 0;
1396
0
        strcpybuff(retour->msg, "Unknown, assuming junky server");
1397
0
        strcpybuff(retour->contenttype, HTS_UNKNOWN_MIME);
1398
0
      } else if (strnotempty(a)) {
1399
0
        retour->statuscode = STATUSCODE_INVALID;
1400
0
        strcpybuff(retour->msg, "Unknown (not HTTP/xx) response structure");
1401
0
      } else {
1402
        /* This is dirty .. */
1403
0
        retour->statuscode = HTTP_OK;
1404
0
        retour->keep_alive = 0;
1405
0
        strcpybuff(retour->msg, "Unknown, assuming junky server");
1406
0
        strcpybuff(retour->contenttype, HTS_UNKNOWN_MIME);
1407
0
      }
1408
0
    }
1409
0
  } else {                      // vide!
1410
    /*
1411
       retour->statuscode=STATUSCODE_INVALID;
1412
       strcpybuff(retour->msg,"Empty reponse or internal error");
1413
     */
1414
    /* This is dirty .. */
1415
0
    retour->statuscode = HTTP_OK;
1416
0
    strcpybuff(retour->msg, "Unknown, assuming junky server");
1417
0
    strcpybuff(retour->contenttype, HTS_UNKNOWN_MIME);
1418
0
  }
1419
0
}
1420
1421
// traiter ligne par ligne l'en tête
1422
// gestion des cookies
1423
void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * retour,
1424
0
               char *rcvd) {
1425
0
  int p;
1426
1427
0
  if ((p = strfield(rcvd, "Content-length:")) != 0) {
1428
#if HDEBUG
1429
    printf("ok, Content-length: détecté\n");
1430
#endif
1431
0
    if (sscanf(rcvd + p, LLintP, &(retour->totalsize)) == 1) {
1432
0
      if (retour->totalsize == 0) {
1433
0
        retour->empty = 1;
1434
0
      }
1435
0
    }
1436
0
  } else if ((p = strfield(rcvd, "Content-Disposition:")) != 0) {
1437
0
    while(is_realspace(*(rcvd + p)))
1438
0
      p++;                      // sauter espaces
1439
0
    if ((int) strlen(rcvd + p) < 250) { // pas trop long?
1440
0
      char tmp[256];
1441
0
      char *a = NULL;
1442
1443
0
      strcpybuff(tmp, rcvd + p);
1444
0
      a = strstr(tmp, "filename=");
1445
0
      if (a) {
1446
0
        a += strlen("filename=");
1447
0
        while(is_space(*a))
1448
0
          a++;
1449
0
        if (a) {
1450
0
          char *c = NULL;
1451
1452
0
          while((c = strchr(a, '/')))   /* skip all / (see RFC2616) */
1453
0
            a = c + 1;
1454
0
          hts_rtrim(a, HTS_SPACES);
1455
0
          if ((int) strlen(a) < 200) { // pas trop long?
1456
0
            strcpybuff(retour->cdispo, a);
1457
0
          }
1458
0
        }
1459
0
      }
1460
0
    }
1461
0
  } else if ((p = strfield(rcvd, "Last-Modified:")) != 0) {
1462
0
    while(is_realspace(*(rcvd + p)))
1463
0
      p++;                      // sauter espaces
1464
0
    if ((int) strlen(rcvd + p) < 64) { // pas trop long?
1465
0
      strcpybuff(retour->lastmodified, rcvd + p);
1466
0
    }
1467
0
  } else if ((p = strfield(rcvd, "Date:")) != 0) {
1468
0
    if (strnotempty(retour->lastmodified) == 0) {       /* pas encore de last-modified */
1469
0
      while(is_realspace(*(rcvd + p)))
1470
0
        p++;                    // sauter espaces
1471
0
      if ((int) strlen(rcvd + p) < 64) { // pas trop long?
1472
0
        strcpybuff(retour->lastmodified, rcvd + p);
1473
0
      }
1474
0
    }
1475
0
  } else if ((p = strfield(rcvd, "Etag:")) != 0) {      /* Etag */
1476
0
    if (retour) {
1477
0
      while(is_realspace(*(rcvd + p)))
1478
0
        p++;                    // sauter espaces
1479
0
      if ((int) strlen(rcvd + p) < 64)  // pas trop long?
1480
0
        strcpybuff(retour->etag, rcvd + p);
1481
0
      else                      // erreur.. ignorer
1482
0
        retour->etag[0] = '\0';
1483
0
    }
1484
0
  } else if ((p = strfield(rcvd, "Transfer-Encoding:")) != 0) { // chunk!
1485
0
    while(is_realspace(*(rcvd + p)))
1486
0
      p++;                      // sauter espaces
1487
0
    if (strfield(rcvd + p, "chunked")) {
1488
0
      retour->is_chunk = 1; // chunked
1489
#if HDEBUG
1490
      printf("ok, Transfer-Encoding: détecté\n");
1491
#endif
1492
0
    }
1493
0
  } else if ((p = strfield(rcvd, "Content-type:")) != 0) {
1494
0
    if (retour) {
1495
0
      char tempo[1100];
1496
1497
      // éviter les text/html; charset=foo
1498
0
      {
1499
0
        char *a = strchr(rcvd + p, ';');
1500
1501
0
        if (a) {                // extended information
1502
0
          *a = '\0';
1503
0
          a++;
1504
0
          while(is_space(*a))
1505
0
            a++;
1506
0
          if (strfield(a, "charset")) {
1507
0
            a += 7;
1508
0
            while(is_space(*a))
1509
0
              a++;
1510
0
            if (*a == '=') {
1511
0
              a++;
1512
0
              while(is_space(*a))
1513
0
                a++;
1514
0
              if (*a == '\"')
1515
0
                a++;
1516
0
              while(is_space(*a))
1517
0
                a++;
1518
0
              if (*a) {
1519
0
                char *chs = a;
1520
1521
0
                while(*a && !is_space(*a) && *a != '\"' && *a != ';')
1522
0
                  a++;
1523
0
                *a = '\0';
1524
0
                if (*chs) {
1525
0
                  if (strlen(chs) < sizeof(retour->charset) - 2) {
1526
0
                    strcpybuff(retour->charset, chs);
1527
0
                  }
1528
0
                }
1529
0
              }
1530
0
            }
1531
0
          }
1532
0
        }
1533
0
      }
1534
      // An empty/whitespace Content-Type value yields no token: keep the
1535
      // sentinel default rather than reading an uninitialized tempo.
1536
0
      if (sscanf(rcvd + p, "%1099s", tempo) == 1) { // tempo[1100], server data
1537
0
        if (strlen(tempo) < sizeof(retour->contenttype) - 2) // pas trop long!!
1538
0
          strcpybuff(retour->contenttype, tempo);
1539
0
        else
1540
0
          strcpybuff(retour->contenttype,
1541
0
                     "application/octet-stream-unknown"); // erreur
1542
0
      }
1543
0
    }
1544
0
  } else if ((p = strfield(rcvd, "Content-Range:")) != 0) {
1545
    // Content-Range: bytes 0-70870/70871
1546
0
    const char *a;
1547
1548
0
    for(a = rcvd + p; is_space(*a); a++) ;
1549
0
    if (strncasecmp(a, "bytes ", 6) == 0) {
1550
0
      for(a += 6; is_space(*a); a++) ;
1551
0
      if (sscanf
1552
0
          (a, LLintP "-" LLintP "/" LLintP, &retour->crange_start,
1553
0
           &retour->crange_end, &retour->crange) != 3) {
1554
0
        retour->crange_start = 0;
1555
0
        retour->crange_end = 0;
1556
0
        retour->crange = 0;
1557
0
        a = strchr(rcvd + p, '/');
1558
0
        if (a != NULL) {
1559
0
          a++;
1560
0
          if (sscanf(a, LLintP, &retour->crange) == 1 && retour->crange >= 0) {
1561
0
            retour->crange_start = 0;
1562
0
            retour->crange_end = retour->crange - 1;
1563
0
          } else {
1564
0
            retour->crange = 0;
1565
0
          }
1566
0
        }
1567
0
      }
1568
      // A valid Content-Range has no negative field; reject hostile values so
1569
      // the crange +/- 1 arithmetic downstream cannot sign-overflow (UB).
1570
0
      if (retour->crange_start < 0 || retour->crange_end < 0 ||
1571
0
          retour->crange < 0) {
1572
0
        retour->crange_start = retour->crange_end = retour->crange = 0;
1573
0
      }
1574
0
    }
1575
0
  } else if ((p = strfield(rcvd, "Connection:")) != 0) {
1576
0
    char *a = rcvd + p;
1577
1578
0
    while(is_space(*a))
1579
0
      a++;
1580
0
    if (*a) {
1581
0
      if (strfield(a, "Keep-Alive")) {
1582
0
        if (!retour->keep_alive) {
1583
0
          retour->keep_alive_max = 10;
1584
0
          retour->keep_alive_t = 15;
1585
0
        }
1586
0
        retour->keep_alive = 1;
1587
0
      } else {
1588
0
        retour->keep_alive = 0;
1589
0
      }
1590
0
    }
1591
0
  } else if ((p = strfield(rcvd, "Keep-Alive:")) != 0) {
1592
0
    char *a = rcvd + p;
1593
1594
0
    while(is_space(*a))
1595
0
      a++;
1596
0
    if (*a) {
1597
0
      char *p;
1598
1599
0
      retour->keep_alive = 1;
1600
0
      retour->keep_alive_max = 10;
1601
0
      retour->keep_alive_t = 15;
1602
0
      if ((p = strstr(a, "timeout="))) {
1603
0
        p += strlen("timeout=");
1604
0
        sscanf(p, "%d", &retour->keep_alive_t);
1605
0
      }
1606
0
      if ((p = strstr(a, "max="))) {
1607
0
        p += strlen("max=");
1608
0
        sscanf(p, "%d", &retour->keep_alive_max);
1609
0
      }
1610
0
      if (retour->keep_alive_max <= 1 || retour->keep_alive_t < 1) {
1611
0
        retour->keep_alive = 0;
1612
0
      }
1613
0
    }
1614
0
  } else if ((p = strfield(rcvd, "TE:")) != 0) {
1615
0
    char *a = rcvd + p;
1616
1617
0
    while(is_space(*a))
1618
0
      a++;
1619
0
    if (*a) {
1620
0
      if (strfield(a, "trailers")) {
1621
0
        retour->keep_alive_trailers = 1;
1622
0
      }
1623
0
    }
1624
0
  } else if ((p = strfield(rcvd, "Content-Encoding:")) != 0) {
1625
0
    if (retour) {
1626
0
      char tempo[1100];
1627
0
      char *a = rcvd + p;
1628
1629
0
      while(is_space(*a))
1630
0
        a++;
1631
0
      {
1632
0
        char *a = strchr(rcvd + p, ';');
1633
1634
0
        if (a)
1635
0
          *a = '\0';
1636
0
      }
1637
      // Bound to tempo[1100]; no token => leave empty, don't read uninit tempo.
1638
0
      if (sscanf(a, "%1099s", tempo) == 1 && strlen(tempo) < 64)
1639
0
        strcpybuff(retour->contentencoding, tempo);
1640
0
      else
1641
0
        retour->contentencoding[0] = '\0';
1642
      /* A coding to undo (or one we can not undo, which must fail the fetch
1643
         rather than save the coded bytes as the page) */
1644
0
      if (hts_codec_parse(retour->contentencoding) != HTS_CODEC_IDENTITY)
1645
0
        retour->compressed = 1;
1646
0
    }
1647
0
  } else if ((p = strfield(rcvd, "Location:")) != 0) {
1648
0
    if (retour) {
1649
0
      if (retour->location) {
1650
0
        while(is_realspace(*(rcvd + p)))
1651
0
          p++; // skip spaces
1652
0
        if (strlen(rcvd + p) < HTS_LOCATION_SIZE)
1653
0
          strlcpybuff(retour->location, rcvd + p, HTS_LOCATION_SIZE);
1654
0
        else {
1655
          /* no opt here, so this only reaches a registered log callback */
1656
0
          hts_log_print(NULL, LOG_WARNING,
1657
0
                        "Location header too long (%d bytes), redirect ignored",
1658
0
                        (int) strlen(rcvd + p));
1659
0
          retour->location[0] = '\0';
1660
0
        }
1661
0
      }
1662
0
    }
1663
0
  } else if (((p = strfield(rcvd, "Set-Cookie:")) != 0) &&
1664
0
             (cookie)) {        // ohh un cookie
1665
0
    char *a = rcvd + p;         // pointeur
1666
0
    char domain[256];           // domaine cookie (.netscape.com)
1667
0
    char path[256];             // chemin (/)
1668
0
    char cook_name[256];        // nom cookie (MYCOOK)
1669
0
    char BIGSTK cook_value[8192];       // valeur (ID=toto,S=1234)
1670
1671
#if DEBUG_COOK
1672
    printf("set-cookie detected\n");
1673
#endif
1674
    /* Over-long host overflows the fail-safe domain[] copy (abort); drop it. */
1675
0
    if (adr && strlen(jump_identification_const(adr)) >= sizeof(domain))
1676
0
      return;
1677
0
    while(*a) {
1678
0
      char *token_st, *token_end;
1679
0
      char *value_st, *value_end;
1680
0
      char name[256];
1681
0
      char BIGSTK value[8192];
1682
0
      int next = 0;
1683
1684
0
      name[0] = value[0] = '\0';
1685
      //
1686
1687
      // initialiser cookie lu actuellement
1688
0
      if (adr)
1689
0
        strcpybuff(domain, jump_identification_const(adr));   // domaine
1690
0
      strcpybuff(path, "/");    // chemin (/)
1691
0
      strcpybuff(cook_name, "");        // nom cookie (MYCOOK)
1692
0
      strcpybuff(cook_value, "");       // valeur (ID=toto,S=1234)
1693
      // boucler jusqu'au prochain cookie ou la fin
1694
0
      do {
1695
0
        char *start_loop = a;
1696
1697
0
        while(is_space(*a))
1698
0
          a++;                  // sauter espaces
1699
0
        token_st = a;           // départ token
1700
0
        while((!is_space(*a)) && (*a) && (*a != ';') && (*a != '='))
1701
0
          a++;                  // arrêter si espace, point virgule
1702
0
        token_end = a;
1703
0
        while(is_space(*a))
1704
0
          a++;                  // sauter espaces
1705
0
        if (*a == '=') {        // name=value
1706
0
          a++;
1707
0
          while(is_space(*a))
1708
0
            a++;                // sauter espaces
1709
0
          value_st = a;
1710
0
          while((*a != ';') && (*a))
1711
0
            a++; // prochain ;
1712
0
          value_end = a;
1713
          // vérifier débordements
1714
0
          if ((((int) (token_end - token_st)) < 200)
1715
0
              && (((int) (value_end - value_st)) < 8000)
1716
0
              && (((int) (token_end - token_st)) > 0)
1717
0
              && (((int) (value_end - value_st)) > 0)) {
1718
0
            int name_len = (int) (token_end - token_st);
1719
0
            int value_len = (int) (value_end - value_st);
1720
1721
0
            name[0] = '\0';
1722
0
            value[0] = '\0';
1723
0
            strncatbuff(name, token_st, name_len);
1724
0
            strncatbuff(value, value_st, value_len);
1725
#if DEBUG_COOK
1726
            printf("detected cookie-av: name=\"%s\" value=\"%s\"\n", name,
1727
                   value);
1728
#endif
1729
0
            if (strfield2(name, "domain")) {
1730
0
              if (value_len < sizeof(domain) - 1) {
1731
0
                strcpybuff(domain, value);
1732
0
              } else {
1733
0
                cook_name[0] = 0;
1734
0
                break;
1735
0
              }
1736
0
            } else if (strfield2(name, "path")) {
1737
0
              if (value_len < sizeof(path) - 1) {
1738
0
                strcpybuff(path, value);
1739
0
              } else {
1740
0
                cook_name[0] = 0;
1741
0
                break;
1742
0
              }
1743
0
            } else if (strfield2(name, "max-age")) {
1744
              // ignoré..
1745
0
            } else if (strfield2(name, "expires")) {
1746
              // ignoré..
1747
0
            } else if (strfield2(name, "version")) {
1748
              // ignoré..
1749
0
            } else if (strfield2(name, "comment")) {
1750
              // ignoré
1751
0
            } else if (strfield2(name, "secure")) {     // ne devrait pas arriver ici
1752
              // ignoré
1753
0
            } else {
1754
0
              if (value_len < sizeof(cook_value) - 1
1755
0
                  && name_len < sizeof(cook_name) - 1) {
1756
0
                if (strnotempty(cook_name) == 0) {      // noter premier: nom et valeur cookie
1757
0
                  strcpybuff(cook_name, name);
1758
0
                  strcpybuff(cook_value, value);
1759
0
                } else {        // prochain cookie
1760
0
                  a = start_loop;       // on devra recommencer à cette position
1761
0
                  next = 1;     // enregistrer
1762
0
                }
1763
0
              } else {
1764
0
                cook_name[0] = 0;
1765
0
                break;
1766
0
              }
1767
0
            }
1768
0
          }
1769
0
        }
1770
0
        if (!next) {
1771
0
          while((*a != ';') && (*a))
1772
0
            a++;                // prochain
1773
0
          while(*a == ';')
1774
0
            a++;                // sauter ;
1775
0
        }
1776
0
      } while((*a) && (!next));
1777
0
      if (strnotempty(cook_name)) {     // cookie?
1778
#if DEBUG_COOK
1779
        printf
1780
          ("new cookie: name=\"%s\" value=\"%s\" domain=\"%s\" path=\"%s\"\n",
1781
           cook_name, cook_value, domain, path);
1782
#endif
1783
0
        cookie_add(cookie, cook_name, cook_value, domain, path);
1784
0
      }
1785
0
    }
1786
0
  }
1787
0
}
1788
1789
// HTTP status code -> reason phrase (per RFC), or NULL if unknown.
1790
0
HTSEXT_API const char *infostatuscode_const(int statuscode) {
1791
  // O(1) dispatch (the compiler builds a jump table); the phrases are static.
1792
0
  switch (statuscode) {
1793
0
  case 100:
1794
0
    return "Continue";
1795
0
  case 101:
1796
0
    return "Switching Protocols";
1797
0
  case 200:
1798
0
    return "OK";
1799
0
  case 201:
1800
0
    return "Created";
1801
0
  case 202:
1802
0
    return "Accepted";
1803
0
  case 203:
1804
0
    return "Non-Authoritative Information";
1805
0
  case 204:
1806
0
    return "No Content";
1807
0
  case 205:
1808
0
    return "Reset Content";
1809
0
  case 206:
1810
0
    return "Partial Content";
1811
0
  case 300:
1812
0
    return "Multiple Choices";
1813
0
  case 301:
1814
0
    return "Moved Permanently";
1815
0
  case 302:
1816
0
    return "Moved Temporarily";
1817
0
  case 303:
1818
0
    return "See Other";
1819
0
  case 304:
1820
0
    return "Not Modified";
1821
0
  case 305:
1822
0
    return "Use Proxy";
1823
0
  case 306:
1824
0
    return "Undefined 306 error";
1825
0
  case 307:
1826
0
    return "Temporary Redirect";
1827
0
  case 400:
1828
0
    return "Bad Request";
1829
0
  case 401:
1830
0
    return "Unauthorized";
1831
0
  case 402:
1832
0
    return "Payment Required";
1833
0
  case 403:
1834
0
    return "Forbidden";
1835
0
  case 404:
1836
0
    return "Not Found";
1837
0
  case 405:
1838
0
    return "Method Not Allowed";
1839
0
  case 406:
1840
0
    return "Not Acceptable";
1841
0
  case 407:
1842
0
    return "Proxy Authentication Required";
1843
0
  case 408:
1844
0
    return "Request Time-out";
1845
0
  case 409:
1846
0
    return "Conflict";
1847
0
  case 410:
1848
0
    return "Gone";
1849
0
  case 411:
1850
0
    return "Length Required";
1851
0
  case 412:
1852
0
    return "Precondition Failed";
1853
0
  case 413:
1854
0
    return "Request Entity Too Large";
1855
0
  case 414:
1856
0
    return "Request-URI Too Large";
1857
0
  case 415:
1858
0
    return "Unsupported Media Type";
1859
0
  case 416:
1860
0
    return "Requested Range Not Satisfiable";
1861
0
  case 417:
1862
0
    return "Expectation Failed";
1863
0
  case 429:
1864
0
    return "Too Many Requests";
1865
0
  case 451:
1866
0
    return "Unavailable For Legal Reasons";
1867
0
  case 500:
1868
0
    return "Internal Server Error";
1869
0
  case 501:
1870
0
    return "Not Implemented";
1871
0
  case 502:
1872
0
    return "Bad Gateway";
1873
0
  case 503:
1874
0
    return "Service Unavailable";
1875
0
  case 504:
1876
0
    return "Gateway Time-out";
1877
0
  case 505:
1878
0
    return "HTTP Version Not Supported";
1879
0
  default:
1880
0
    return NULL;
1881
0
  }
1882
0
}
1883
1884
// Write the status code's reason phrase into msg. For an unknown code, keep any
1885
// caller-provided message, otherwise fall back to a default. Callers provide a
1886
// buffer of at least 64 bytes (the longest reason phrase is 31).
1887
0
HTSEXT_API void infostatuscode(char *msg, int statuscode) {
1888
0
  const char *const text = infostatuscode_const(statuscode);
1889
1890
0
  if (text != NULL) {
1891
0
    strlcpybuff(msg, text, 64);
1892
0
  } else if (strnotempty(msg) == 0) {
1893
0
    strlcpybuff(msg, "Unknown error", 64);
1894
0
  }
1895
0
}
1896
1897
// check if data is available
1898
0
int check_readinput(htsblk * r) {
1899
0
  if (r->soc != INVALID_SOCKET) {
1900
0
    fd_set fds;                 // poll structures
1901
0
    struct timeval tv;          // structure for select
1902
0
    const int soc = (int) r->soc;
1903
1904
0
    assertf(soc == r->soc);
1905
0
    FD_ZERO(&fds);
1906
0
    FD_SET(soc, &fds);
1907
0
    tv.tv_sec = 0;
1908
0
    tv.tv_usec = 0;
1909
0
    select(soc + 1, &fds, NULL, NULL, &tv);
1910
0
    if (FD_ISSET(soc, &fds))
1911
0
      return 1;
1912
0
    else
1913
0
      return 0;
1914
0
  } else
1915
0
    return 0;
1916
0
}
1917
1918
// check if data is available
1919
0
int check_readinput_t(T_SOC soc, int timeout) {
1920
0
  if (soc != INVALID_SOCKET) {
1921
0
    fd_set fds;                 // poll structures
1922
0
    struct timeval tv;          // structure for select
1923
0
    const int isoc = (int) soc;
1924
1925
0
    assertf(isoc == soc);
1926
0
    FD_ZERO(&fds);
1927
0
    FD_SET(isoc, &fds);
1928
0
    tv.tv_sec = timeout;
1929
0
    tv.tv_usec = 0;
1930
0
    select(isoc + 1, &fds, NULL, NULL, &tv);
1931
0
    if (FD_ISSET(isoc, &fds))
1932
0
      return 1;
1933
0
    else
1934
0
      return 0;
1935
0
  } else
1936
0
    return 0;
1937
0
}
1938
1939
// wait until the socket is writable, up to timeout seconds
1940
0
int check_writeinput_t(T_SOC soc, int timeout) {
1941
0
  if (soc != INVALID_SOCKET) {
1942
0
    fd_set fds;
1943
0
    struct timeval tv;
1944
0
    const int isoc = (int) soc;
1945
1946
0
    assertf(isoc == soc);
1947
0
    FD_ZERO(&fds);
1948
0
    FD_SET(isoc, &fds);
1949
0
    tv.tv_sec = timeout;
1950
0
    tv.tv_usec = 0;
1951
0
    select(isoc + 1, NULL, &fds, NULL, &tv);
1952
0
    return FD_ISSET(isoc, &fds) ? 1 : 0;
1953
0
  } else
1954
0
    return 0;
1955
0
}
1956
1957
/* Class the live errno now: back_wait() sees it after free() and fflush() and
1958
   would rewrite this as a retryable connection error. */
1959
0
static void classify_write_error(htsblk *r) {
1960
0
  r->statuscode =
1961
0
      check_fatal_io_errno() ? STATUSCODE_IO_FATAL : STATUSCODE_IO_ERROR;
1962
0
  strcpybuff(r->msg, "Write error on disk");
1963
0
}
1964
1965
// Read one block: bufl is a byte count, or one of the HTS_XFREAD_* line modes.
1966
// Note: the +1 in the mallocs is the trailing NUL appended to the data.
1967
0
LLint http_xfread1(htsblk * r, int bufl) {
1968
0
  int nl = -1;
1969
1970
  // EOF
1971
0
  if (r->totalsize >= 0 && r->size == r->totalsize) {
1972
0
    return READ_EOF;
1973
0
  }
1974
1975
0
  if (bufl > 0) {
1976
0
    if (!r->is_write) {         // stocker en mémoire
1977
      // In-memory content must fit a 32-bit index (allocs below add 1, reads
1978
      // use int offsets): reject a hostile Content-Length or endless stream.
1979
0
      const LLint inmem_want =
1980
0
          (r->totalsize >= 0) ? r->totalsize : (r->size + bufl);
1981
0
      if (inmem_want >= INT32_MAX) {
1982
0
        r->statuscode = STATUSCODE_INVALID;
1983
0
        strcpybuff(r->msg, "In-memory content too large");
1984
0
        return READ_ERROR;
1985
0
      }
1986
0
      if (r->totalsize >= 0) {  // totalsize déterminé ET ALLOUE
1987
0
        if (r->adr == NULL) {
1988
0
          r->adr = (char *) malloct((size_t) r->totalsize + 1);
1989
0
          r->size = 0;
1990
0
        }
1991
0
        if (r->adr != NULL) {
1992
          // lecture
1993
          /* Signed first: a body longer than its announced totalsize makes
1994
             this negative, and as a size_t it would drive a read past adr. */
1995
0
          const LLint remaining = r->totalsize - r->size;
1996
0
          const size_t req_size = remaining > 0 ? (size_t) remaining : 0;
1997
1998
0
          nl = req_size > 0 ? hts_read(r, r->adr + ((int) r->size), (int) req_size) : 0;        /* NO 32 bit overlow possible here (no 4GB html!) */
1999
          // nouvelle taille
2000
0
          if (nl >= 0)
2001
0
            r->size += nl;
2002
2003
          /*
2004
             if (r->size >= r->totalsize)
2005
             nl = -1;  // break
2006
           */
2007
2008
0
          r->adr[r->size] = '\0';       // caractère NULL en fin au cas où l'on traite des HTML
2009
0
        }
2010
2011
0
      } else {                  // inconnu..
2012
        // réserver de la mémoire?
2013
0
        if (r->adr == NULL) {
2014
#if HDEBUG
2015
          printf("..alloc xfread\n");
2016
#endif
2017
0
          r->adr = (char *) malloct(bufl + 1);
2018
0
          r->size = 0;
2019
0
        } else {
2020
#if HDEBUG
2021
          printf("..realloc xfread1\n");
2022
#endif
2023
0
          r->adr = (char *) realloct(r->adr, (int) r->size + bufl + 1);
2024
0
        }
2025
2026
0
        if (r->adr != NULL) {
2027
          // lecture
2028
0
          nl = hts_read(r, r->adr + (int) r->size, bufl);
2029
0
          if (nl > 0) {
2030
            // resize
2031
0
            r->adr = (char *) realloct(r->adr, (int) r->size + nl + 1);
2032
            // nouvelle taille
2033
0
            r->size += nl;
2034
            // octet nul
2035
0
            if (r->adr)
2036
0
              r->adr[r->size] = '\0';
2037
2038
0
          }                     // sinon on a fini
2039
#if HDEBUG
2040
          else if (nl < 0)
2041
            printf("..end read (%d)\n", nl);
2042
#endif
2043
0
        }
2044
#if HDEBUG
2045
        else
2046
          printf("..-> error\n");
2047
#endif
2048
0
      }
2049
2050
      // pas de adr=erreur
2051
0
      if (r->adr == NULL)
2052
0
        nl = READ_ERROR;
2053
2054
0
    } else {                    // stocker sur disque
2055
0
      char *buff;
2056
2057
0
      buff = (char *) malloct(bufl);
2058
0
      if (buff != NULL) {
2059
        // lecture
2060
0
        nl = hts_read(r, buff, bufl);
2061
        // nouvelle taille
2062
0
        if (nl > 0) {
2063
0
          r->size += nl;
2064
0
          if (!hts_fwrite_exact(buff, (size_t) nl, r->out)) {
2065
0
            classify_write_error(r);
2066
0
            nl = READ_ERROR;
2067
0
          }
2068
0
        }
2069
2070
        // libérer bloc tempo
2071
0
        freet(buff);
2072
0
      } else
2073
0
        nl = READ_ERROR;
2074
2075
      /* The tail stdio held is written here, and glibc's later fclose() returns
2076
         0 once this flush has taken the error. */
2077
0
      if ((nl < 0) && (r->out != NULL) && fflush(r->out) != 0 &&
2078
0
          !statuscode_is_write_error(r->statuscode)) {
2079
0
        classify_write_error(r);
2080
0
        nl = READ_ERROR;
2081
0
      }
2082
2083
0
    }                           // stockage disque ou mémoire
2084
2085
0
  } else { // line modes: byte by byte, CR dropped
2086
0
    int count = 256;
2087
0
    int tot_nl = 0;
2088
0
    int lf_detected = 0;
2089
0
    int at_beginning = 1;
2090
2091
0
    do {
2092
0
      nl = READ_INTERNAL_ERROR;
2093
0
      count--;
2094
0
      if (r->adr == NULL) {
2095
0
        r->adr = (char *) malloct(HTS_LINE_BLOCK_SIZE);
2096
0
        r->size = 0;
2097
0
      }
2098
0
      if (r->adr != NULL) {
2099
0
        if (r->size < HTS_LINE_BLOCK_SIZE - 2) {
2100
          // lecture
2101
0
          nl = hts_read(r, r->adr + r->size, 1);
2102
0
          if (nl > 0) {
2103
            // exit on a blank line (LF seen twice, or LF as the first byte),
2104
            // or on the first LF in HTS_XFREAD_LINE mode
2105
0
            if (*(r->adr + r->size) == 10) {
2106
0
              if (lf_detected || (at_beginning) || (bufl < 0))
2107
0
                count = -1;
2108
0
              lf_detected = 1;
2109
0
            }
2110
0
            if (*(r->adr + r->size) != 13) {    // sauter caractères 13
2111
0
              if ((*(r->adr + r->size) != 10)
2112
0
                  && (*(r->adr + r->size) != 13)
2113
0
                ) {
2114
                // restart for new line
2115
0
                lf_detected = 0;
2116
0
              }
2117
0
              (r->size)++;
2118
0
              at_beginning = 0;
2119
0
            }
2120
0
            *(r->adr + r->size) = '\0'; // terminer par octet nul
2121
0
          }
2122
0
        }
2123
0
      }
2124
0
      if (nl >= 0) {
2125
0
        tot_nl += nl;
2126
0
        if (!check_readinput(r))
2127
0
          count = -1;
2128
0
      }
2129
0
    } while((nl >= 0) && (count > 0));
2130
0
    if (nl >= 0) {
2131
0
      nl = tot_nl;
2132
0
    }
2133
0
  }
2134
  /* Ahead of the EOF test below: r->size is advanced above the fwrite that
2135
     failed, so a body completed by that very read would report a clean EOF. */
2136
0
  if (nl == READ_ERROR && statuscode_is_write_error(r->statuscode)) {
2137
0
    return READ_ERROR;
2138
0
  }
2139
  // EOF
2140
0
  if (r->totalsize >= 0 && r->size == r->totalsize) {
2141
0
    return READ_EOF;
2142
0
  } else {
2143
0
    return nl;
2144
0
  }
2145
0
}
2146
2147
0
hts_boolean socket_set_nonblocking(T_SOC soc, hts_boolean nonblocking) {
2148
#ifdef _WIN32
2149
  unsigned long p = nonblocking ? 1 : 0;
2150
2151
  return ioctlsocket(soc, FIONBIO, &p) == 0 ? HTS_TRUE : HTS_FALSE;
2152
#else
2153
0
  const int flags = fcntl(soc, F_GETFL, 0);
2154
0
  const int wanted = nonblocking ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
2155
2156
0
  if (flags == -1 || fcntl(soc, F_SETFL, wanted) == -1)
2157
0
    return HTS_FALSE;
2158
0
  return HTS_TRUE;
2159
0
#endif
2160
0
}
2161
2162
0
int connect_socket_error(T_SOC soc) {
2163
0
  int soerr = 0;
2164
0
  socklen_t len = (socklen_t) sizeof(soerr);
2165
2166
0
  if (getsockopt(soc, SOL_SOCKET, SO_ERROR, (char *) &soerr, &len) != 0)
2167
0
    return -1;
2168
0
  return soerr;
2169
0
}
2170
2171
// Crée un lien (http) vers une adresse internet iadr
2172
// retour: structure (adresse, taille, message si erreur (si !adr))
2173
// peut ouvrir avec des connect() non bloquants: waitconnect=0/1
2174
T_SOC newhttp(httrackp * opt, const char *_iadr, htsblk * retour, int port,
2175
0
              int waitconnect) {
2176
0
  return newhttp_addr(opt, _iadr, retour, port, waitconnect, 0, NULL);
2177
0
}
2178
2179
T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
2180
0
                   int waitconnect, int addr_index, int *addr_count) {
2181
0
  T_SOC soc;                    // descipteur de la socket
2182
2183
0
  if (addr_count != NULL) {
2184
0
    *addr_count = 0;
2185
0
  }
2186
2187
0
  if (strcmp(_iadr, "file://") != 0) {  /* non fichier */
2188
0
    SOCaddr server;
2189
0
    SOCaddr addrs[HTS_MAXADDRNUM];
2190
0
    int naddr;
2191
0
    const char *error = "unknown error";
2192
2193
    // tester un éventuel id:pass et virer id:pass@ si détecté
2194
0
    const char *const iadr = jump_identification_const(_iadr);
2195
0
    const char *resolve_host = iadr;
2196
0
    char BIGSTK iadr2[HTS_URLMAXSIZE * 2];
2197
2198
0
    SOCaddr_clear(server);
2199
2200
#if HDEBUG
2201
    printf("gethostbyname\n");
2202
#endif
2203
2204
    // tester un éventuel port
2205
0
    if (port == -1) {
2206
0
      const char *a = jump_toport_const(iadr);
2207
2208
#if HTS_USEOPENSSL
2209
      if (retour->ssl)
2210
        port = 443;
2211
      else
2212
        port = 80;              // port par défaut
2213
#else
2214
0
      port = 80;                // port par défaut
2215
0
#endif
2216
2217
0
      if (a != NULL) {
2218
        // folding a nonsense port into 0..65535 crawls one neither the link nor
2219
        // a port filter named; an empty "host:" just means the default (#614)
2220
0
        if (a[1] != '\0' && !hts_parse_url_port(a + 1, &port)) {
2221
0
          if (retour != NULL) {
2222
0
            snprintf(retour->msg, sizeof(retour->msg), "Invalid port: %s",
2223
0
                     a + 1);
2224
0
          }
2225
0
          return INVALID_SOCKET;
2226
0
        }
2227
2228
0
        iadr2[0] = '\0';
2229
        // the address itself, without the ":port"
2230
0
        strncatbuff(iadr2, iadr, (int) (a - iadr));
2231
0
        resolve_host = iadr2;
2232
0
      }
2233
0
    }
2234
2235
    // resolve the full address list and pick the requested candidate; the
2236
    // scheduler retries the next index when a connect fails (dead IPv6 etc.)
2237
0
    naddr =
2238
0
        hts_dns_resolve_all(opt, resolve_host, addrs, HTS_MAXADDRNUM, &error);
2239
0
    if (addr_count != NULL) {
2240
0
      *addr_count = naddr;
2241
0
    }
2242
0
    if (addr_index >= 0 && addr_index < naddr) {
2243
0
      SOCaddr_copy_SOCaddr(server, addrs[addr_index]);
2244
0
    }
2245
2246
0
    if (!SOCaddr_is_valid(server)) {
2247
#if DEBUG
2248
      printf("erreur gethostbyname\n");
2249
#endif
2250
0
      if (retour != NULL) {
2251
#ifdef _WIN32
2252
        snprintf(retour->msg, sizeof(retour->msg),
2253
                 "Unable to get server's address: %s", error);
2254
#else
2255
0
        snprintf(retour->msg, sizeof(retour->msg),
2256
0
                 "Unable to get server's address: %s", error);
2257
0
#endif
2258
0
      }
2259
0
      return INVALID_SOCKET;
2260
0
    }
2261
2262
    // make a copy for external clients
2263
0
    SOCaddr_copy_SOCaddr(retour->address, server);
2264
0
    retour->address_size = SOCaddr_size(retour->address);
2265
2266
    // créer ("attachement") une socket (point d'accès) internet,en flot
2267
#if HDEBUG
2268
    printf("socket\n");
2269
#endif
2270
#if HTS_WIDE_DEBUG
2271
    DEBUG_W("socket\n");
2272
#endif
2273
0
    soc = (T_SOC) socket(SOCaddr_sinfamily(server), SOCK_STREAM, 0);
2274
0
    if (retour != NULL) {
2275
0
      retour->debugid = HTS_STAT.stat_sockid++;
2276
0
    }
2277
#if HTS_WIDE_DEBUG
2278
    DEBUG_W("socket()=%d\n" _(int) soc);
2279
#endif
2280
0
    if (soc == INVALID_SOCKET) {
2281
0
      if (retour != NULL) {
2282
#ifdef _WIN32
2283
        int last_errno = WSAGetLastError();
2284
2285
        htsblk_failf(retour, "Unable to create a socket: %s",
2286
                     strerror(last_errno));
2287
#else
2288
0
        int last_errno = errno;
2289
2290
0
        htsblk_failf(retour, "Unable to create a socket: %s",
2291
0
                     strerror(last_errno));
2292
0
#endif
2293
0
      }
2294
0
      return INVALID_SOCKET;    // erreur création socket impossible
2295
0
    }
2296
    // bind this address
2297
0
    if (retour != NULL && strnotempty(retour->req.proxy.bindhost)) {
2298
0
      const char *error = "unknown error";
2299
0
      SOCaddr bind_addr;
2300
2301
0
      if (hts_dns_resolve2(opt, retour->req.proxy.bindhost,
2302
0
                             &bind_addr, &error) == NULL
2303
0
          || bind(soc, &SOCaddr_sockaddr(bind_addr), 
2304
0
                  SOCaddr_size(bind_addr)) != 0) {
2305
0
        snprintf(retour->msg, sizeof(retour->msg),
2306
0
                 "Unable to bind the specificied server address: %s", error);
2307
0
        deletesoc(soc);
2308
0
        return INVALID_SOCKET;
2309
0
      }
2310
0
    }
2311
    // structure: connexion au domaine internet, port 80 (ou autre)
2312
0
    SOCaddr_initport(server, port);
2313
#if HDEBUG
2314
    printf("==%d\n", soc);
2315
#endif
2316
2317
    // connexion non bloquante?
2318
0
    if (!waitconnect) {
2319
0
      if (!socket_set_nonblocking(soc, HTS_TRUE)) {
2320
#ifdef _WIN32
2321
        const int last_errno = WSAGetLastError();
2322
#else
2323
0
        const int last_errno = errno;
2324
0
#endif
2325
0
        snprintf(retour->msg, sizeof(retour->msg),
2326
0
                 "Non-blocking socket failed: %s", strerror(last_errno));
2327
0
        deletesoc(soc);
2328
0
        return INVALID_SOCKET;
2329
0
      }
2330
0
    }
2331
    // Connexion au serveur lui même
2332
#if HDEBUG
2333
    printf("connect\n");
2334
#endif
2335
0
    HTS_STAT.last_connect = mtime_local();
2336
2337
#if HTS_WIDE_DEBUG
2338
    DEBUG_W("connect\n");
2339
#endif
2340
0
    if (connect(soc, &SOCaddr_sockaddr(server), SOCaddr_size(server)) != 0) {
2341
      // bloquant
2342
0
      if (waitconnect) {
2343
#if HDEBUG
2344
        printf("unable to connect!\n");
2345
#endif
2346
0
        if (retour != NULL) {
2347
#ifdef _WIN32
2348
          const int last_errno = WSAGetLastError();
2349
2350
          htsblk_failf(retour, "Unable to connect to the server: %s",
2351
                       strerror(last_errno));
2352
#else
2353
0
          const int last_errno = errno;
2354
2355
0
          htsblk_failf(retour, "Unable to connect to the server: %s",
2356
0
                       strerror(last_errno));
2357
0
#endif
2358
0
        }
2359
        /* Close the socket and notify the error!!! */
2360
0
        deletesoc(soc);
2361
0
        return INVALID_SOCKET;
2362
0
      }
2363
0
    }
2364
#if HTS_WIDE_DEBUG
2365
    DEBUG_W("connect done\n");
2366
#endif
2367
2368
#if HDEBUG
2369
    printf("connexion établie\n");
2370
#endif
2371
2372
    // A partir de maintenant, on peut envoyer et recevoir des données
2373
    // via le flot identifié par soc (socket): write(soc,adr,taille) et
2374
    // read(soc,adr,taille)
2375
2376
0
  } else {                      // on doit ouvrir un fichier local!
2377
    // il sera géré de la même manière qu'une socket (c'est idem!)
2378
2379
0
    soc = LOCAL_SOCKET_ID;      // pseudo-socket locale..
2380
    // soc sera remplacé lors d'un http_fopen() par un handle véritable!
2381
2382
0
  }                             // teste fichier local ou http
2383
2384
0
  return soc;
2385
0
}
2386
2387
// couper http://www.truc.fr/pub/index.html -> www.truc.fr /pub/index.html
2388
// retour=-1 si erreur.
2389
// si file://... alors adresse=file:// (et coupe le ?query dans ce cas)
2390
0
int ident_url_absolute(const char *url, lien_adrfil *adrfil) {
2391
0
  int pos = 0;
2392
0
  int scheme = 0;
2393
2394
  // effacer adrfil->adr et adrfil->fil
2395
0
  adrfil->adr[0] = adrfil->fil[0] = '\0';
2396
2397
  // Reject an over-long URL: a root-relative path is copied whole into fil[].
2398
0
  if (strlen(url) >= HTS_URLMAXSIZE * 2)
2399
0
    return -1;
2400
2401
#if HDEBUG
2402
  printf("protocol: %s\n", url);
2403
#endif
2404
2405
  // Scheme?
2406
0
  {
2407
0
    const char *a = url;
2408
2409
0
    while(isalpha((unsigned char) *a))
2410
0
      a++;
2411
0
    if (*a == ':')
2412
0
      scheme = 1;
2413
0
  }
2414
2415
  // 1. optional scheme ":"
2416
0
  if ((pos = strfield(url, "file:"))) { // fichier local!! (pour les tests)
2417
    //!!p+=3;
2418
0
    strcpybuff(adrfil->adr, "file://");
2419
0
  } else if ((pos = strfield(url, "http:"))) {  // HTTP
2420
    //!!p+=3;
2421
0
  } else if ((pos = strfield(url, "ftp:"))) {   // FTP
2422
0
    strcpybuff(adrfil->adr, "ftp://");  // FTP!!
2423
    //!!p+=3;
2424
#if HTS_USEOPENSSL
2425
  } else if ((pos = strfield(url, "https:"))) {     // HTTPS
2426
    strcpybuff(adrfil->adr, "https://");
2427
#endif
2428
0
  } else if (scheme) {
2429
0
    return -1;                  // erreur non reconnu
2430
0
  } else
2431
0
    pos = 0;
2432
2433
  // 2. optional "//" authority
2434
0
  if (strncmp(url + pos, "//", 2) == 0)
2435
0
    pos += 2;
2436
2437
  // (url+pos) now points to the path (not net path)
2438
2439
  //## if (adrfil->adr[0]!=lOCAL_CHAR) {    // adrfil->adresse normale http
2440
0
  if (!strfield(adrfil->adr, "file:")) {        // PAS adrfil->file://
2441
0
    const char *p, *q;
2442
2443
0
    p = url + pos;
2444
2445
    // p pointe sur le début de l'adrfil->adresse, ex: www.truc.fr/sommaire/index.html
2446
0
    q = strchr(jump_identification_const(p), '/');
2447
0
    if (q == 0)
2448
0
      q = strchr(jump_identification_const(p), '?');  // http://www.foo.com?bar=1
2449
0
    if (q == 0)
2450
0
      q = p + strlen(p);        // pointe sur \0
2451
    // q pointe sur le chemin, ex: index.html?query=recherche
2452
2453
    // chemin www... trop long!!
2454
0
    if ((((int) (q - p))) > HTS_URLMAXSIZE) {
2455
0
      return -1;                // erreur
2456
0
    }
2457
    // recopier adrfil->adresse www..
2458
0
    strncatbuff(adrfil->adr, p, ((int) (q - p)));
2459
    // *( adrfil->adr+( ((int) q) - ((int) p) ) )=0;  // faut arrêter la fumette!
2460
2461
    // fil[] holds the path plus a possible leading '/' the top strlen() misses.
2462
0
    if (strlen(q) >= sizeof(adrfil->fil) - (q[0] != '/' ? 1 : 0))
2463
0
      return -1;
2464
2465
    // recopier chemin /pub/..
2466
0
    if (q[0] != '/')            // page par défaut (/)
2467
0
      strcatbuff(adrfil->fil, "/");
2468
0
    strcatbuff(adrfil->fil, q);
2469
    // SECURITE:
2470
    // simplifier url pour les ../
2471
0
    fil_simplifie(adrfil->fil);
2472
0
  } else {                      // localhost adrfil->file://
2473
0
    const char *p;
2474
0
    size_t i;
2475
0
    char *a;
2476
2477
0
    p = url + pos;
2478
0
    if (*p == '/' || *p == '\\') {      /* adrfil->file:///.. */
2479
0
      strcatbuff(adrfil->fil, p);       // fichier local ; adrfil->adr="#"
2480
0
    } else {
2481
0
      if (*p == '\0' || p[1] != ':') {  /* empty path: not a DOS drive letter */
2482
0
        strcatbuff(adrfil->fil, "//");  /* adrfil->file://server/foo */
2483
0
        strcatbuff(adrfil->fil, p);
2484
0
      } else {
2485
0
        strcatbuff(adrfil->fil, p);     // adrfil->file://C:\..
2486
0
      }
2487
0
    }
2488
2489
0
    a = strchr(adrfil->fil, '?');
2490
0
    if (a)
2491
0
      *a = '\0';                /* couper query (inutile pour adrfil->file:// lors de la requête) */
2492
    // adrfil->filtrer les \\ -> / pour les fichiers DOS
2493
0
    for(i = 0; adrfil->fil[i] != '\0'; i++)
2494
0
      if (adrfil->fil[i] == '\\')
2495
0
        adrfil->fil[i] = '/';
2496
    // collapse ../ like the http branch above (path-traversal safety)
2497
0
    fil_simplifie(adrfil->fil);
2498
0
  }
2499
2500
  // no hostname
2501
0
  if (!strnotempty(adrfil->adr))
2502
0
    return -1;                  // erreur non reconnu
2503
2504
  // nommer au besoin.. (non utilisé normalement)
2505
0
  if (!strnotempty(adrfil->fil))
2506
0
    strcpybuff(adrfil->fil, "default-index.html");
2507
2508
  // case insensitive pour adrfil->adresse
2509
0
  {
2510
0
    char *a = jump_identification(adrfil->adr);
2511
2512
0
    while(*a) {
2513
0
      if ((*a >= 'A') && (*a <= 'Z'))
2514
0
        *a += 'a' - 'A';
2515
0
      a++;
2516
0
    }
2517
0
  }
2518
2519
0
  return 0;
2520
0
}
2521
2522
/* simplify ../ and ./ */
2523
0
void fil_simplifie(char *f) {
2524
0
  char *a, *b;
2525
0
  char *rollback[128];
2526
0
  int rollid = 0;
2527
0
  char lc = '/';
2528
0
  int query = 0;
2529
0
  int wasAbsolute = (*f == '/');
2530
2531
0
  for(a = b = f; *a != '\0';) {
2532
0
    if (*a == '?')
2533
0
      query = 1;
2534
0
    if (query == 0 && lc == '/' && a[0] == '.' && a[1] == '/') {        /* foo/./bar or ./foo  */
2535
0
      a += 2;
2536
0
    } else if (query == 0 && lc == '/' && a[0] == '.' && a[1] == '.' && (a[2] == '/' || a[2] == '\0')) {        /* foo/../bar or ../foo or .. */
2537
0
      if (a[2] == '\0')
2538
0
        a += 2;
2539
0
      else
2540
0
        a += 3;
2541
0
      if (rollid > 1) {
2542
0
        rollid--;
2543
0
        b = rollback[rollid - 1];
2544
0
      } else {                  /* too many ../ */
2545
0
        rollid = 0;
2546
0
        b = f;
2547
0
        if (wasAbsolute)
2548
0
          b++;                  /* after the / */
2549
0
      }
2550
0
    } else {
2551
0
      *b++ = lc = *a;
2552
0
      if (*a == '/') {
2553
0
        rollback[rollid++] = b;
2554
0
        if (rollid >= 127) {
2555
0
          *f = '\0';            /* ERROR */
2556
0
          break;
2557
0
        }
2558
0
      }
2559
0
      a++;
2560
0
    }
2561
0
  }
2562
0
  *b = '\0';
2563
0
  if (*f == '\0') {
2564
0
    if (wasAbsolute) {
2565
0
      f[0] = '/';
2566
0
      f[1] = '\0';
2567
0
    } else {
2568
0
      f[0] = '.';
2569
0
      f[1] = '/';
2570
0
      f[2] = '\0';
2571
0
    }
2572
0
  }
2573
0
}
2574
2575
// fermer liaison fichier ou socket
2576
0
void deletehttp(htsblk * r) {
2577
#if HTS_DEBUG_CLOSESOCK
2578
  DEBUG_W("deletehttp: (htsblk*) 0x%p\n" _(void *)r);
2579
#endif
2580
#if HTS_USEOPENSSL
2581
  /* Free OpenSSL structures */
2582
  if (r->ssl_con) {
2583
    SSL_shutdown(r->ssl_con);
2584
    SSL_free(r->ssl_con);
2585
    r->ssl_con = NULL;
2586
  }
2587
#endif
2588
0
  if (r->soc != INVALID_SOCKET) {
2589
0
    if (r->is_file) {
2590
0
      if (r->fp)
2591
0
        fclose(r->fp);
2592
0
      r->fp = NULL;
2593
0
    } else {
2594
0
      if (r->soc != LOCAL_SOCKET_ID)
2595
0
        deletesoc_r(r);
2596
0
    }
2597
0
    r->soc = INVALID_SOCKET;
2598
0
  }
2599
0
}
2600
2601
// free the addr buffer
2602
// always returns 1
2603
0
int deleteaddr(htsblk * r) {
2604
0
  if (r->adr != NULL) {
2605
0
    freet(r->adr);
2606
0
    r->adr = NULL;
2607
0
  }
2608
0
  if (r->headers != NULL) {
2609
0
    freet(r->headers);
2610
0
    r->headers = NULL;
2611
0
  }
2612
0
  return 1;
2613
0
}
2614
2615
// fermer une socket
2616
0
void deletesoc(T_SOC soc) {
2617
0
  if (soc != INVALID_SOCKET && soc != LOCAL_SOCKET_ID) {
2618
#if HTS_WIDE_DEBUG
2619
    DEBUG_W("close %d\n" _(int) soc);
2620
#endif
2621
#ifdef _WIN32
2622
    if (closesocket(soc) != 0) {
2623
      int err = WSAGetLastError();
2624
2625
      fprintf(stderr, "* error closing socket " T_SOCP ": %s\n", soc,
2626
              strerror(err));
2627
    }
2628
#else
2629
0
    if (close(soc) != 0) {
2630
0
      const int err = errno;
2631
2632
0
      fprintf(stderr, "* error closing socket " T_SOCP ": %s\n", soc,
2633
0
              strerror(err));
2634
0
    }
2635
0
#endif
2636
#if HTS_WIDE_DEBUG
2637
    DEBUG_W(".. done\n");
2638
#endif
2639
0
  }
2640
0
}
2641
2642
/* Will also clean other things */
2643
0
void deletesoc_r(htsblk * r) {
2644
#if HTS_USEOPENSSL
2645
  if (r->ssl_con) {
2646
    SSL_shutdown(r->ssl_con);
2647
    SSL_free(r->ssl_con);
2648
    r->ssl_con = NULL;
2649
  }
2650
#endif
2651
0
  if (r->soc != INVALID_SOCKET) {
2652
0
    deletesoc(r->soc);
2653
0
    r->soc = INVALID_SOCKET;
2654
0
  }
2655
0
}
2656
2657
// renvoi le nombre de secondes depuis 1970
2658
0
TStamp time_local(void) {
2659
0
  return ((TStamp) time(NULL));
2660
0
}
2661
2662
// number of millisec since 1970
2663
0
HTSEXT_API TStamp mtime_local(void) {
2664
0
#ifndef _WIN32
2665
0
  struct timeval tv;
2666
0
  if (gettimeofday(&tv, NULL) != 0) {
2667
0
    assert(! "gettimeofday");
2668
0
  }
2669
2670
0
  return (TStamp) (((TStamp) tv.tv_sec * (TStamp) 1000) +
2671
0
                   ((TStamp) tv.tv_usec / (TStamp) 1000));
2672
#else
2673
  struct timeb B;
2674
  ftime(&B);
2675
  return (TStamp) (((TStamp) B.time * (TStamp) 1000)
2676
                   + ((TStamp) B.millitm));
2677
#endif
2678
0
}
2679
2680
// convertit un nombre de secondes en temps (chaine)
2681
0
void sec2str(char *st, TStamp t) {
2682
0
  int j, h, m, s;
2683
2684
0
  j = (int) (t / (3600 * 24));
2685
0
  t -= ((TStamp) j) * (3600 * 24);
2686
0
  h = (int) (t / (3600));
2687
0
  t -= ((TStamp) h) * 3600;
2688
0
  m = (int) (t / 60);
2689
0
  t -= ((TStamp) m) * 60;
2690
0
  s = (int) t;
2691
2692
0
  if (j > 0)
2693
0
    sprintf(st, "%d days, %d hours %d minutes %d seconds", j, h, m, s);
2694
0
  else if (h > 0)
2695
0
    sprintf(st, "%d hours %d minutes %d seconds", h, m, s);
2696
0
  else if (m > 0)
2697
0
    sprintf(st, "%d minutes %d seconds", m, s);
2698
0
  else
2699
0
    sprintf(st, "%d seconds", s);
2700
0
}
2701
2702
// idem, plus court (chaine)
2703
0
HTSEXT_API void qsec2str(char *st, TStamp t) {
2704
0
  int j, h, m, s;
2705
2706
0
  j = (int) (t / (3600 * 24));
2707
0
  t -= ((TStamp) j) * (3600 * 24);
2708
0
  h = (int) (t / (3600));
2709
0
  t -= ((TStamp) h) * 3600;
2710
0
  m = (int) (t / 60);
2711
0
  t -= ((TStamp) m) * 60;
2712
0
  s = (int) t;
2713
2714
0
  if (j > 0)
2715
0
    sprintf(st, "%dd,%02dh,%02dmin%02ds", j, h, m, s);
2716
0
  else if (h > 0)
2717
0
    sprintf(st, "%dh,%02dmin%02ds", h, m, s);
2718
0
  else if (m > 0)
2719
0
    sprintf(st, "%dmin%02ds", m, s);
2720
0
  else
2721
0
    sprintf(st, "%ds", s);
2722
0
}
2723
2724
// heure actuelle, GMT, format rfc (taille buffer 256o)
2725
0
void time_gmt_rfc822(char *s) {
2726
0
  time_t tt;
2727
0
  struct tm tmv;
2728
2729
0
  tt = time(NULL);
2730
  /* no local-time fallback: it would format local time and still label it GMT
2731
   * (#806) */
2732
0
  if (hts_gmtime(tt, &tmv))
2733
0
    time_rfc822(s, &tmv);
2734
0
  else
2735
0
    s[0] = '\0';
2736
0
}
2737
2738
0
void hts_now_iso8601(char out[32]) {
2739
0
  time_t t = time(NULL);
2740
0
  struct tm tmv;
2741
2742
0
  if (!hts_gmtime(t, &tmv))
2743
0
    memset(&tmv, 0, sizeof(tmv));
2744
0
  strftime(out, 32, "%Y-%m-%dT%H:%M:%SZ", &tmv);
2745
0
}
2746
2747
// heure actuelle, format rfc (taille buffer 256o)
2748
0
void time_local_rfc822(char *s) {
2749
0
  time_t tt;
2750
0
  struct tm tmv;
2751
2752
0
  tt = time(NULL);
2753
0
  if (hts_localtime(tt, &tmv))
2754
0
    time_rfc822_local(s, &tmv);
2755
0
  else
2756
0
    s[0] = '\0';
2757
0
}
2758
2759
/* convertir une chaine en temps */
2760
0
struct tm *convert_time_rfc822(struct tm *result, const char *s) {
2761
0
  char months[] = "jan feb mar apr may jun jul aug sep oct nov dec";
2762
0
  char str[256];
2763
0
  char *a;
2764
2765
  /* */
2766
0
  int result_mm = -1;
2767
0
  int result_dd = -1;
2768
0
  int result_n1 = -1;
2769
0
  int result_n2 = -1;
2770
0
  int result_n3 = -1;
2771
0
  int result_n4 = -1;
2772
2773
  /* */
2774
2775
0
  if ((int) strlen(s) > 200)
2776
0
    return NULL;
2777
0
  strcpybuff(str, s);
2778
0
  hts_lowcase(str);
2779
  /* éliminer :,- */
2780
0
  while((a = strchr(str, '-')))
2781
0
    *a = ' ';
2782
0
  while((a = strchr(str, ':')))
2783
0
    *a = ' ';
2784
0
  while((a = strchr(str, ',')))
2785
0
    *a = ' ';
2786
  /* tokeniser */
2787
0
  a = str;
2788
0
  while(*a) {
2789
0
    char *first, *last;
2790
0
    char tok[256];
2791
2792
    /* découper mot */
2793
0
    while(*a == ' ')
2794
0
      a++;                      /* sauter espaces */
2795
0
    first = a;
2796
0
    while((*a) && (*a != ' '))
2797
0
      a++;
2798
0
    last = a;
2799
0
    tok[0] = '\0';
2800
0
    if (first != last) {
2801
0
      char *pos;
2802
2803
0
      strncatbuff(tok, first, (int) (last - first));
2804
      /* analyser */
2805
0
      if ((pos = strstr(months, tok))) {        /* month always in letters */
2806
0
        result_mm = ((int) (pos - months)) / 4;
2807
0
      } else {
2808
0
        int number;
2809
2810
0
        if (sscanf(tok, "%d", &number) == 1) {  /* number token */
2811
0
          if (result_dd < 0)    /* day always first number */
2812
0
            result_dd = number;
2813
0
          else if (result_n1 < 0)
2814
0
            result_n1 = number;
2815
0
          else if (result_n2 < 0)
2816
0
            result_n2 = number;
2817
0
          else if (result_n3 < 0)
2818
0
            result_n3 = number;
2819
0
          else if (result_n4 < 0)
2820
0
            result_n4 = number;
2821
0
        }                       /* sinon, bruit de fond(+1GMT for exampel) */
2822
0
      }
2823
0
    }
2824
0
  }
2825
0
  if ((result_n1 >= 0) && (result_mm >= 0) && (result_dd >= 0)
2826
0
      && (result_n2 >= 0) && (result_n3 >= 0) && (result_n4 >= 0)) {
2827
0
    if (result_n4 >= 1000) {    /* Sun Nov  6 08:49:37 1994 */
2828
0
      result->tm_year = result_n4 - 1900;
2829
0
      result->tm_hour = result_n1;
2830
0
      result->tm_min = result_n2;
2831
0
      result->tm_sec = max(result_n3, 0);
2832
0
    } else {                    /* Sun, 06 Nov 1994 08:49:37 GMT or Sunday, 06-Nov-94 08:49:37 GMT */
2833
0
      result->tm_hour = result_n2;
2834
0
      result->tm_min = result_n3;
2835
0
      result->tm_sec = max(result_n4, 0);
2836
0
      if (result_n1 <= 50)      /* 00 means 2000 */
2837
0
        result->tm_year = result_n1 + 100;
2838
0
      else if (result_n1 < 1000)        /* 99 means 1999 */
2839
0
        result->tm_year = result_n1;
2840
0
      else                      /* 2000 */
2841
0
        result->tm_year = result_n1 - 1900;
2842
0
    }
2843
0
    result->tm_isdst = 0;       /* assume GMT */
2844
0
    result->tm_yday = -1;       /* don't know */
2845
0
    result->tm_wday = -1;       /* don't know */
2846
0
    result->tm_mon = result_mm;
2847
0
    result->tm_mday = result_dd;
2848
0
    return result;
2849
0
  }
2850
0
  return NULL;
2851
0
}
2852
2853
0
static time_t getGMT(struct tm *tm) {
2854
0
  time_t t = timegm(tm);
2855
2856
0
  if (t != (time_t) - 1 && t != (time_t) 0) {
2857
0
    return (time_t) t;
2858
0
  }
2859
0
  return (time_t) -1;
2860
0
}
2861
2862
/* sets file time. -1 if error */
2863
/* Note: utf-8 */
2864
0
int set_filetime(const char *file, struct tm *tm_time) {
2865
0
  time_t t = getGMT(tm_time);
2866
2867
0
  if (t != (time_t) - 1) {
2868
0
    STRUCT_UTIMBUF tim;
2869
2870
0
    memset(&tim, 0, sizeof(tim));
2871
0
    tim.actime = tim.modtime = t;
2872
0
    return UTIME(file, &tim);
2873
0
  }
2874
0
  return -1;
2875
0
}
2876
2877
/* sets file time from RFC822 date+time, -1 if error*/
2878
/* Note: utf-8 */
2879
0
int set_filetime_rfc822(const char *file, const char *date) {
2880
0
  struct tm buffer;
2881
0
  struct tm *tm_s = convert_time_rfc822(&buffer, date);
2882
2883
0
  if (tm_s) {
2884
0
    return set_filetime(file, tm_s);
2885
0
  } else
2886
0
    return -1;
2887
0
}
2888
2889
/* Note: utf-8 */
2890
0
time_t get_filetime(const char *file) {
2891
0
  STRUCT_STAT buf;
2892
2893
0
  if (STAT(file, &buf) != 0)
2894
0
    return (time_t) -1;
2895
0
  return (time_t) buf.st_mtime;
2896
0
}
2897
2898
/* Note: utf-8 */
2899
0
int get_filetime_rfc822(const char *file, char *date) {
2900
0
  STRUCT_STAT buf;
2901
2902
0
  date[0] = '\0';
2903
0
  if (STAT(file, &buf) == 0) {
2904
0
    struct tm tmv;
2905
0
    time_t tt = buf.st_mtime;
2906
2907
    /* no local-time fallback: it would format local time and still label it GMT
2908
     * (#806) */
2909
0
    if (hts_gmtime(tt, &tmv)) {
2910
0
      time_rfc822(date, &tmv);
2911
0
      return 1;
2912
0
    }
2913
0
  }
2914
0
  return 0;
2915
0
}
2916
2917
// heure au format rfc (taille buffer 256o)
2918
0
void time_rfc822(char *s, struct tm *A) {
2919
0
  if (A == NULL) {
2920
0
    int localtime_returned_null = 0;
2921
2922
0
    assertf(localtime_returned_null);
2923
0
  }
2924
0
  strftime(s, 256, "%a, %d %b %Y %H:%M:%S GMT", A);
2925
0
}
2926
2927
// heure locale au format rfc (taille buffer 256o)
2928
0
void time_rfc822_local(char *s, struct tm *A) {
2929
0
  if (A == NULL) {
2930
0
    int localtime_returned_null = 0;
2931
2932
0
    assertf(localtime_returned_null);
2933
0
  }
2934
0
  strftime(s, 256, "%a, %d %b %Y %H:%M:%S", A);
2935
0
}
2936
2937
// conversion en b,Kb,Mb
2938
0
HTSEXT_API char *int2bytes(strc_int2bytes2 * strc, LLint n) {
2939
0
  char **a = int2bytes2(strc, n);
2940
2941
0
  strcpybuff(strc->catbuff, a[0]);
2942
0
  strcatbuff(strc->catbuff, a[1]);
2943
0
  return strc->catbuff;
2944
0
}
2945
2946
// conversion en b/s,Kb/s,Mb/s
2947
0
HTSEXT_API char *int2bytessec(strc_int2bytes2 * strc, long int n) {
2948
0
  char buff[256];
2949
0
  char **a = int2bytes2(strc, n);
2950
2951
0
  strcpybuff(buff, a[0]);
2952
0
  strcatbuff(buff, a[1]);
2953
0
  return concat(strc->catbuff, sizeof(strc->catbuff), buff, "/s");
2954
0
}
2955
0
HTSEXT_API char *int2char(strc_int2bytes2 * strc, int n) {
2956
0
  sprintf(strc->buff2, "%d", n);
2957
0
  return strc->buff2;
2958
0
}
2959
2960
// conversion en b,Kb,Mb, nombre et type séparés
2961
// limite: 2.10^9.10^6B
2962
2963
/* See http://physics.nist.gov/cuu/Units/binary.html */
2964
0
#define ToLLint(a) ((LLint)(a))
2965
0
#define ToLLintKiB (ToLLint(1024))
2966
0
#define ToLLintMiB (ToLLintKiB * ToLLintKiB)
2967
0
#define ToLLintGiB (ToLLintKiB * ToLLintKiB * ToLLintKiB)
2968
0
#define ToLLintTiB (ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB)
2969
#define ToLLintPiB                                                             \
2970
0
  (ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB)
2971
0
HTSEXT_API char **int2bytes2(strc_int2bytes2 * strc, LLint n) {
2972
0
  if (n < ToLLintKiB) {
2973
0
    sprintf(strc->buff1, "%d", (int) (LLint) n);
2974
0
    strcpybuff(strc->buff2, "B");
2975
0
  } else if (n < ToLLintMiB) {
2976
0
    sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / ToLLintKiB)),
2977
0
            (int) ((LLint) ((n % ToLLintKiB) * 100) / ToLLintKiB));
2978
0
    strcpybuff(strc->buff2, "KiB");
2979
0
  } else if (n < ToLLintGiB) {
2980
0
    sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintMiB))),
2981
0
            (int) ((LLint) (((n % (ToLLintMiB)) * 100) / (ToLLintMiB))));
2982
0
    strcpybuff(strc->buff2, "MiB");
2983
0
  } else if (n < ToLLintTiB) {
2984
0
    sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintGiB))),
2985
0
            (int) ((LLint) (((n % (ToLLintGiB)) * 100) / (ToLLintGiB))));
2986
0
    strcpybuff(strc->buff2, "GiB");
2987
0
  } else if (n < ToLLintPiB) {
2988
0
    sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintTiB))),
2989
0
            (int) ((LLint) (((n % (ToLLintTiB)) * 100) / (ToLLintTiB))));
2990
0
    strcpybuff(strc->buff2, "TiB");
2991
0
  } else {
2992
0
    sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintPiB))),
2993
0
            (int) ((LLint) (((n % (ToLLintPiB)) * 100) / (ToLLintPiB))));
2994
0
    strcpybuff(strc->buff2, "PiB");
2995
0
  }
2996
0
  strc->buffadr[0] = strc->buff1;
2997
0
  strc->buffadr[1] = strc->buff2;
2998
0
  return strc->buffadr;
2999
0
}
3000
3001
#ifdef _WIN32
3002
#else
3003
// ignore sigpipe?
3004
0
int sig_ignore_flag(int setflag) {      // flag ignore
3005
0
  static int flag = 0;          /* YES, this one is true static */
3006
3007
0
  if (setflag >= 0)
3008
0
    flag = setflag;
3009
0
  return flag;
3010
0
}
3011
#endif
3012
3013
// envoi de texte (en têtes généralement) sur la socket soc
3014
0
int sendc(htsblk * r, const char *s) {
3015
0
  int n, ssz = (int) strlen(s);
3016
3017
#ifdef _WIN32
3018
#else
3019
0
  sig_ignore_flag(1);
3020
0
#endif
3021
#if HDEBUG
3022
  write(0, s, ssz);
3023
#endif
3024
3025
#if HTS_USEOPENSSL
3026
  if (r->ssl) {
3027
    n = SSL_write(r->ssl_con, s, ssz);
3028
  } else
3029
#endif
3030
0
    n = send(r->soc, s, ssz, 0);
3031
3032
#ifdef _WIN32
3033
#else
3034
0
  sig_ignore_flag(0);
3035
0
#endif
3036
3037
0
  return (n == ssz) ? n : -1;
3038
0
}
3039
3040
// Like linput, but in memory (optimized). A line too long for `s` is clipped
3041
// but consumed whole: resuming inside it reads its tail back as another line.
3042
0
int binput(const char *buff, char *s, int max) {
3043
0
  int count = 0;
3044
0
  int destCount = 0;
3045
3046
  // Note: \0 will return 1
3047
0
  while (buff != NULL && buff[count] != '\0' && buff[count] != '\n') {
3048
0
    if (buff[count] != '\r' && destCount < max) {
3049
0
      s[destCount++] = buff[count];
3050
0
    }
3051
0
    count++;
3052
0
  }
3053
0
  s[destCount] = '\0';
3054
3055
  // then return the supplemental jump offset
3056
0
  return count + 1;
3057
0
}
3058
3059
hts_boolean binput_line(const char *buff, const char *end, char *s, int max,
3060
0
                        int *offset) {
3061
0
  hts_boolean cut = HTS_FALSE;
3062
0
  const char *p;
3063
0
  int j = 0;
3064
3065
0
  *offset = 0;
3066
0
  s[0] = '\0';
3067
0
  if (buff == NULL || buff >= end)
3068
0
    return HTS_FALSE;
3069
0
  for (p = buff; p < end && *p != '\0' && *p != '\n'; p++) {
3070
0
    if (*p == '\r')
3071
0
      continue;
3072
0
    if (j < max - 1)
3073
0
      s[j++] = *p;
3074
0
    else
3075
0
      cut = HTS_TRUE; /* keep walking: the tail must not become more headers */
3076
0
  }
3077
0
  s[j] = '\0';
3078
0
  *offset = (int) (p - buff) + 1; /* binput's convention: step over the \n */
3079
0
  return cut;
3080
0
}
3081
3082
0
hts_boolean finput_line(T_SOC fd, char *s, int max) {
3083
0
  hts_boolean cut = HTS_FALSE;
3084
0
  int j = 0;
3085
0
  char c;
3086
3087
0
  for (;;) {
3088
0
    if (read((int) fd, &c, 1) <= 0 || c == 10)
3089
0
      break;
3090
0
    if (c == 13)
3091
0
      continue;
3092
0
    if (j < max - 1)
3093
0
      s[j++] = c;
3094
0
    else
3095
0
      cut = HTS_TRUE; /* keep draining: the tail must not become more headers */
3096
0
  }
3097
0
  s[j] = '\0';
3098
0
  return cut;
3099
0
}
3100
3101
// Lecture d'une ligne (peut être unicode à priori)
3102
0
int linput(FILE * fp, char *s, int max) {
3103
0
  int c;
3104
0
  int j = 0;
3105
3106
0
  do {
3107
0
    c = fgetc(fp);
3108
0
    if (c != EOF) {
3109
0
      switch (c) {
3110
0
      case 13:
3111
0
        break;                  // sauter CR
3112
0
      case 10:
3113
0
        c = -1;
3114
0
        break;
3115
0
      case 9:
3116
0
      case 12:
3117
0
        break;                  // sauter ces caractères
3118
0
      default:
3119
0
        s[j++] = (char) c;
3120
0
        break;
3121
0
      }
3122
0
    }
3123
0
  } while((c != -1) && (c != EOF) && (j < (max - 1)));
3124
0
  s[j] = '\0';
3125
0
  return j;
3126
0
}
3127
0
int linputsoc(T_SOC soc, char *s, int max) {
3128
0
  int c;
3129
0
  int j = 0;
3130
3131
0
  do {
3132
0
    unsigned char ch;
3133
3134
0
    if (recv(soc, &ch, 1, 0) == 1) {
3135
0
      c = ch;
3136
0
    } else {
3137
0
      c = EOF;
3138
0
    }
3139
0
    if (c != EOF) {
3140
0
      switch (c) {
3141
0
      case 13:
3142
0
        break;                  // sauter CR
3143
0
      case 10:
3144
0
        c = -1;
3145
0
        break;
3146
0
      case 9:
3147
0
      case 12:
3148
0
        break;                  // sauter ces caractères
3149
0
      default:
3150
0
        s[j++] = (char) c;
3151
0
        break;
3152
0
      }
3153
0
    }
3154
0
  } while((c != -1) && (c != EOF) && (j < (max - 1)));
3155
0
  s[j] = '\0';
3156
0
  return j;
3157
0
}
3158
0
int linputsoc_t(T_SOC soc, char *s, int max, int timeout) {
3159
0
  if (check_readinput_t(soc, timeout)) {
3160
0
    return linputsoc(soc, s, max);
3161
0
  }
3162
0
  return -1;
3163
0
}
3164
0
int linput_trim(FILE * fp, char *s, int max) {
3165
0
  int rlen = 0;
3166
0
  char *ls = (char *) malloct(max + 1);
3167
3168
0
  s[0] = '\0';
3169
0
  if (ls) {
3170
0
    char *a;
3171
3172
    // lire ligne
3173
0
    rlen = linput(fp, ls, max);
3174
0
    if (rlen) {
3175
      // sauter espaces et tabs en fin
3176
0
      while((rlen > 0)
3177
0
            && ((ls[max(rlen - 1, 0)] == ' ')
3178
0
                || (ls[max(rlen - 1, 0)] == '\t')))
3179
0
        ls[--rlen] = '\0';
3180
      // sauter espaces en début
3181
0
      a = ls;
3182
0
      while((rlen > 0) && ((*a == ' ') || (*a == '\t'))) {
3183
0
        a++;
3184
0
        rlen--;
3185
0
      }
3186
0
      if (rlen > 0) {
3187
0
        memcpy(s, a, rlen);     // can copy \0 chars
3188
0
        s[rlen] = '\0';
3189
0
      }
3190
0
    }
3191
    //
3192
0
    freet(ls);
3193
0
  }
3194
0
  return rlen;
3195
0
}
3196
0
int linput_cpp(FILE * fp, char *s, int max) {
3197
0
  int rlen = 0;
3198
3199
0
  s[0] = '\0';
3200
0
  do {
3201
0
    int ret;
3202
3203
0
    if (rlen > 0)
3204
0
      if (s[rlen - 1] == '\\')
3205
0
        s[--rlen] = '\0';       // couper \ final
3206
    // lire ligne
3207
0
    ret = linput_trim(fp, s + rlen, max - rlen);
3208
0
    if (ret > 0)
3209
0
      rlen += ret;
3210
0
  } while((s[max(rlen - 1, 0)] == '\\') && (rlen < max));
3211
0
  return rlen;
3212
0
}
3213
3214
// idem avec les car spéciaux
3215
0
void rawlinput(FILE * fp, char *s, int max) {
3216
0
  int c;
3217
0
  int j = 0;
3218
3219
0
  do {
3220
0
    c = fgetc(fp);
3221
0
    if (c != EOF) {
3222
0
      switch (c) {
3223
0
      case 13:
3224
0
        break;                  // sauter CR
3225
0
      case 10:
3226
0
        c = -1;
3227
0
        break;
3228
0
      default:
3229
0
        s[j++] = (char) c;
3230
0
        break;
3231
0
      }
3232
0
    }
3233
0
  } while((c != -1) && (c != EOF) && (j < (max - 1)));
3234
0
  s[j++] = '\0';
3235
0
}
3236
3237
//cherche chaine, case insensitive
3238
0
const char *strstrcase(const char *s, const char *o) {
3239
0
  while(*s && strfield(s, o) == 0)
3240
0
    s++;
3241
0
  if (*s == '\0')
3242
0
    return NULL;
3243
0
  return s;
3244
0
}
3245
3246
// Unicode detector
3247
// See http://www.unicode.org/unicode/reports/tr28/
3248
// (sect Table 3.1B. Legal UTF-8 Byte Sequences)
3249
typedef struct {
3250
  unsigned int pos;
3251
  unsigned char data[4];
3252
} t_auto_seq;
3253
3254
// char between a and b
3255
#define CHAR_BETWEEN(c, a, b)       ( (c) >= 0x##a ) && ( (c) <= 0x##b )
3256
// sequence start
3257
#define SEQBEG                      ( inseq == 0 )
3258
// in this block
3259
#define BLK(n,a, b)                 ( (seq.pos >= n) && ((err = CHAR_BETWEEN(seq.data[n], a, b))) )
3260
#define ELT(n,a)                    BLK(n,a,a)
3261
// end
3262
#define SEQEND                      ((ok = 1))
3263
// sequence started, character will fail if error
3264
#define IN_SEQ                      ( (inseq = 1) )
3265
// decoding error
3266
#define BAD_SEQ                     ( (ok == 0) && (inseq != 0) && (!err) )
3267
// no sequence started
3268
#define NO_SEQ                      ( inseq == 0 )
3269
3270
0
void map_characters(unsigned char *buffer, unsigned int size, unsigned int *map) {
3271
0
  unsigned int i;
3272
3273
0
  memset(map, 0, sizeof(unsigned int) * 256);
3274
0
  for(i = 0; i < size; i++) {
3275
0
    map[buffer[i]]++;
3276
0
  }
3277
0
}
3278
3279
// le fichier est-il un fichier html?
3280
//  0 : non
3281
//  1 : oui
3282
// -1 : on sait pas
3283
// -2 : on sait pas, pas d'extension
3284
0
int ishtml(httrackp * opt, const char *fil) {
3285
  /* User-defined MIME types (overrides ishtml()) */
3286
0
  char BIGSTK fil_noquery[HTS_URLMAXSIZE * 2];
3287
0
  char mime[256];
3288
0
  char *a;
3289
3290
0
  strcpybuff(fil_noquery, fil);
3291
0
  if ((a = strchr(fil_noquery, '?')) != NULL) {
3292
0
    *a = '\0';
3293
0
  }
3294
0
  if (get_userhttptype(opt, mime, sizeof(mime), fil_noquery)) {
3295
0
    if (is_html_mime_type(mime)) {
3296
0
      return 1;
3297
0
    } else {
3298
0
      return 0;
3299
0
    }
3300
0
  }
3301
3302
0
  if (!strnotempty(fil_noquery)) {
3303
0
    return -2;
3304
0
  }
3305
3306
  /* Search for known ext */
3307
0
  for (a = hts_lastcharptr(fil_noquery);
3308
0
       *a != '.' && *a != '/' && a > fil_noquery; a--)
3309
0
    ;
3310
0
  if (*a == '.') {              // a une extension
3311
0
    char BIGSTK fil_noquery[HTS_URLMAXSIZE * 2];
3312
0
    char *b;
3313
0
    int ret;
3314
0
    char *dotted = a;
3315
3316
0
    fil_noquery[0] = '\0';
3317
0
    a++;                        // pointer sur extension
3318
0
    strncatbuff(fil_noquery, a, HTS_URLMAXSIZE);
3319
0
    b = strchr(fil_noquery, '?');
3320
0
    if (b)
3321
0
      *b = '\0';
3322
0
    ret = ishtml_ext(fil_noquery);      // retour
3323
0
    if (ret == -1) {
3324
0
      switch (is_knowntype(opt, dotted)) {
3325
0
      case 1:
3326
0
        ret = 0;                // connu, non html
3327
0
        break;
3328
0
      case 2:
3329
0
        ret = 1;                // connu, html
3330
0
        break;
3331
0
      default:
3332
0
        ret = -1;               // inconnu..
3333
0
        break;
3334
0
      }
3335
0
    }
3336
0
    return ret;
3337
0
  } else
3338
0
    return -2;                  // indéterminé, par exemple /truc
3339
0
}
3340
3341
// idem, mais pour uniquement l'extension
3342
0
int ishtml_ext(const char *a) {
3343
0
  int html = 0;
3344
3345
  //
3346
0
  if (strfield2(a, "html"))
3347
0
    html = 1;
3348
0
  else if (strfield2(a, "htm"))
3349
0
    html = 1;
3350
0
  else if (strfield2(a, "shtml"))
3351
0
    html = 1;
3352
0
  else if (strfield2(a, "phtml"))
3353
0
    html = 1;
3354
0
  else if (strfield2(a, "htmlx"))
3355
0
    html = 1;
3356
0
  else if (strfield2(a, "shtm"))
3357
0
    html = 1;
3358
0
  else if (strfield2(a, "phtm"))
3359
0
    html = 1;
3360
0
  else if (strfield2(a, "htmx"))
3361
0
    html = 1;
3362
  //
3363
  // insuccès..
3364
0
  else {
3365
0
#if 1
3366
0
    html = -1;                  // inconnu..
3367
#else
3368
    // XXXXXX not suitable (ext)
3369
    switch (is_knownext(a)) {
3370
    case 1:
3371
      html = 0;                 // connu, non html
3372
      break;
3373
    case 2:
3374
      html = 1;                 // connu, html
3375
      break;
3376
    default:
3377
      html = -1;                // inconnu..
3378
      break;
3379
    }
3380
#endif
3381
0
  }
3382
0
  return html;
3383
0
}
3384
3385
// error (404,500..)
3386
0
int ishttperror(int err) {
3387
0
  switch (err / 100) {
3388
0
  case 4:
3389
0
  case 5:
3390
0
    return 1;
3391
0
    break;
3392
0
  }
3393
0
  return 0;
3394
0
}
3395
3396
/* Declare a non-const version of FUN */
3397
#define DECLARE_NON_CONST_VERSION(FUN) \
3398
0
char *FUN(char *source) { \
3399
0
  const char *const ret = FUN ##_const(source); \
3400
0
  return ret != NULL ? source + ( ret - source ) : NULL; \
3401
0
}
3402
3403
// retourne le pointeur ou le pointeur + offset si il existe dans la chaine un @ signifiant 
3404
// une identification
3405
0
HTSEXT_API const char *jump_identification_const(const char *source) {
3406
0
  const char *a, *trytofind;
3407
3408
0
  if (strcmp(source, "file://") == 0)
3409
0
    return source;
3410
  // rechercher dernier @ (car parfois email transmise dans adresse!)
3411
  // mais sauter ftp:// éventuel
3412
0
  a = jump_protocol_const(source);
3413
0
  trytofind = strrchr_limit(a, '@', strchr(a, '/'));
3414
0
  return trytofind != NULL ? trytofind : a;
3415
0
}
3416
3417
0
HTSEXT_API DECLARE_NON_CONST_VERSION(jump_identification)
3418
3419
0
HTSEXT_API const char *jump_normalized_const(const char *source) {
3420
0
  if (strcmp(source, "file://") == 0)
3421
0
    return source;
3422
0
  source = jump_identification_const(source);
3423
0
  if (strfield(source, "www") && source[3] != '\0') {
3424
0
    if (source[3] == '.') {     // www.foo.com -> foo.com
3425
0
      source += 4;
3426
0
    } else {                    // www-4.foo.com -> foo.com
3427
0
      const char *a = source + 3;
3428
3429
0
      while(*a && (isdigit(*a) || *a == '-'))
3430
0
        a++;
3431
0
      if (*a == '.') {
3432
0
        source = a + 1;
3433
0
      }
3434
0
    }
3435
0
  }
3436
0
  return source;
3437
0
}
3438
3439
0
HTSEXT_API DECLARE_NON_CONST_VERSION(jump_normalized)
3440
3441
0
static int sortNormFnc(const void *a_, const void *b_) {
3442
0
  const char *const*const a = (const char *const*) a_;
3443
0
  const char *const*const b = (const char *const*) b_;
3444
3445
0
  return strcmp(*a + 1, *b + 1);
3446
0
}
3447
3448
/* Path normalizer core: optionally collapse redundant '//' (DO_SLASH) and/or
3449
   sort query arguments (DO_QUERY) so equivalent URLs dedupe. */
3450
static char *fil_normalized_ex(const char *source, char *dest, int do_slash,
3451
0
                               int do_query) {
3452
0
  char lastc = 0;
3453
0
  int gotquery = 0;
3454
0
  size_t ampargs = 0;
3455
0
  size_t i, j;
3456
0
  char *query = NULL;
3457
3458
0
  for(i = j = 0; source[i] != '\0'; i++) {
3459
0
    if (!gotquery && source[i] == '?') {
3460
0
      gotquery = 1;
3461
0
      ampargs = 1;
3462
0
    }
3463
0
    if (do_slash && !gotquery && lastc == '/' && source[i] == '/') {
3464
      // foo//bar -> foo/bar
3465
0
    } else {
3466
0
      if (gotquery && source[i] == '&') {
3467
0
        ampargs++;
3468
0
      }
3469
0
      dest[j++] = source[i];
3470
0
    }
3471
0
    lastc = source[i];
3472
0
  }
3473
0
  dest[j++] = '\0';
3474
3475
  /* Sort arguments (&foo=1&bar=2 == &bar=2&foo=1) */
3476
0
  if (do_query && ampargs > 1) {
3477
0
    char **amps = malloct(ampargs * sizeof(char *));
3478
0
    char *copyBuff = NULL;
3479
0
    size_t qLen = 0;
3480
3481
0
    assertf(amps != NULL);
3482
0
    gotquery = 0;
3483
0
    for(i = j = 0; dest[i] != '\0'; i++) {
3484
0
      if ((gotquery && dest[i] == '&') || (!gotquery && dest[i] == '?')) {
3485
0
        if (!gotquery) {
3486
0
          gotquery = 1;
3487
0
          query = &dest[i];
3488
0
          qLen = strlen(query);
3489
0
        }
3490
0
        assertf(j < ampargs);
3491
0
        amps[j++] = &dest[i];
3492
0
        dest[i] = '\0';
3493
0
      }
3494
0
    }
3495
0
    assertf(gotquery);
3496
0
    assertf(j == ampargs);
3497
3498
    /* Sort 'em all */
3499
0
    qsort(amps, ampargs, sizeof(char *), sortNormFnc);
3500
3501
    /* Replace query by sorted query */
3502
0
    copyBuff = malloct(qLen + 1);
3503
0
    assertf(copyBuff != NULL);
3504
0
    {
3505
0
      htsbuff cb = htsbuff_ptr(copyBuff, qLen + 1);
3506
3507
0
      for (i = 0; i < ampargs; i++) {
3508
0
        htsbuff_cat(&cb, i == 0 ? "?" : "&");
3509
0
        htsbuff_cat(&cb, amps[i] + 1);
3510
0
      }
3511
0
      assertf(cb.len == qLen);
3512
0
    }
3513
    /* query points into dest where the original qLen-byte query was */
3514
0
    strlcpybuff(query, copyBuff, qLen + 1);
3515
3516
    /* Cleanup */
3517
0
    freet(amps);
3518
0
    freet(copyBuff);
3519
0
  }
3520
3521
0
  return dest;
3522
0
}
3523
3524
0
HTSEXT_API char *fil_normalized(const char *source, char *dest) {
3525
0
  return fil_normalized_ex(source, dest, 1, 1);
3526
0
}
3527
3528
/* Is query key ARG[0..keylen) in the comma-separated STRIP list? "*" = all;
3529
   case-sensitive, space-trimmed tokens. */
3530
static int hts_query_key_stripped(const char *arg, size_t keylen,
3531
0
                                  const char *strip) {
3532
0
  const char *p = strip;
3533
3534
0
  while (*p != '\0') {
3535
0
    const char *start = p;
3536
0
    size_t toklen;
3537
3538
0
    while (*p != '\0' && *p != ',')
3539
0
      p++;
3540
0
    toklen = (size_t) (p - start);
3541
0
    while (toklen > 0 && *start == ' ') {
3542
0
      start++;
3543
0
      toklen--;
3544
0
    }
3545
0
    while (toklen > 0 && start[toklen - 1] == ' ')
3546
0
      toklen--;
3547
0
    if (toklen == 1 && start[0] == '*')
3548
0
      return 1;
3549
0
    if (toklen == keylen && strncmp(start, arg, keylen) == 0)
3550
0
      return 1;
3551
0
    if (*p == ',')
3552
0
      p++;
3553
0
  }
3554
0
  return 0;
3555
0
}
3556
3557
/* see htscore.h */
3558
char *fil_normalized_filtered_ex(const char *source, char *dest,
3559
                                 const char *strip, int do_slash,
3560
0
                                 int do_query) {
3561
0
  const char *query;
3562
0
  char BIGSTK tmp[HTS_URLMAXSIZE * 2];
3563
0
  htsbuff cb;
3564
0
  int wrote = 0;
3565
3566
  /* No strip list, or no query: plain normalization. */
3567
0
  if (strip == NULL || *strip == '\0' ||
3568
0
      (query = strchr(source, '?')) == NULL) {
3569
0
    return fil_normalized_ex(source, dest, do_slash, do_query);
3570
0
  }
3571
3572
  /* Copy the path, re-emit kept query args, let fil_normalized() sort. Walk
3573
     every field incl. empty/trailing ("a&","?&&") so the result is a fixpoint
3574
     (the read re-normalizes it; a dropped empty arg would miss dedup). */
3575
0
  cb = htsbuff_ptr(tmp, sizeof(tmp));
3576
0
  htsbuff_catn(&cb, source, (size_t) (query - source));
3577
0
  for (query++;;) {
3578
0
    const char *const arg = query;
3579
0
    const char *eq = NULL;
3580
0
    size_t keylen, arglen;
3581
3582
0
    while (*query != '\0' && *query != '&') {
3583
0
      if (eq == NULL && *query == '=')
3584
0
        eq = query;
3585
0
      query++;
3586
0
    }
3587
0
    arglen = (size_t) (query - arg);
3588
0
    keylen = eq != NULL ? (size_t) (eq - arg) : arglen;
3589
0
    if (!hts_query_key_stripped(arg, keylen, strip)) {
3590
0
      htsbuff_catc(&cb, wrote ? '&' : '?');
3591
0
      htsbuff_catn(&cb, arg, arglen);
3592
0
      wrote = 1;
3593
0
    }
3594
0
    if (*query == '\0')
3595
0
      break;
3596
0
    query++;
3597
0
  }
3598
0
  return fil_normalized_ex(tmp, dest, do_slash, do_query);
3599
0
}
3600
3601
/* see htscore.h */
3602
char *fil_normalized_filtered(const char *source, char *dest,
3603
0
                              const char *strip) {
3604
0
  return fil_normalized_filtered_ex(source, dest, strip, 1, 1);
3605
0
}
3606
3607
/* see htscore.h */
3608
const char *hts_query_strip_keys(const char *rules, const char *adr,
3609
0
                                 const char *fil, char *dest, size_t destsize) {
3610
0
  const char *p, *q;
3611
0
  const char *result = NULL;
3612
  /* holds a host and a path, each up to HTS_URLMAXSIZE * 2 */
3613
0
  char BIGSTK url[HTS_URLMAXSIZE * 4];
3614
3615
0
  if (rules == NULL || *rules == '\0' || destsize == 0)
3616
0
    return NULL;
3617
3618
  /* Match string = normalized host/path, query removed. jump_normalized_const
3619
     collapses www+scheme/auth so read and write (double-normalized) agree;
3620
     query excluded keeps the decision on host/path only. Clipped rather than
3621
     aborting: a longer one is hostile input, and which rule it picks is a
3622
     filter decision, not a memory error. */
3623
0
  (void) strclipbuff(url, sizeof(url), jump_normalized_const(adr));
3624
0
  if (fil[0] != '/')
3625
0
    (void) strclipbuff(&url[strlen(url)], sizeof(url) - strlen(url), "/");
3626
0
  q = strchr(fil, '?');
3627
0
  {
3628
0
    const size_t room = sizeof(url) - strlen(url) - 1;
3629
0
    const size_t fillen = q != NULL ? (size_t) (q - fil) : strlen(fil);
3630
3631
0
    strlncatbuff(url, fil, sizeof(url), fillen < room ? fillen : room);
3632
0
  }
3633
3634
  /* Walk the '\n' entries; last match wins (like the +/- filter eval). Each is
3635
     "pattern=keys"; no '=' is the bare form, pattern "*". */
3636
0
  for (p = rules; *p != '\0';) {
3637
0
    const char *const line = p;
3638
0
    const char *eol, *eq, *keys;
3639
0
    char BIGSTK pat[HTS_URLMAXSIZE * 2];
3640
3641
0
    while (*p != '\0' && *p != '\n')
3642
0
      p++;
3643
0
    eol = p;
3644
0
    if (*p == '\n')
3645
0
      p++;
3646
0
    if (eol == line)
3647
0
      continue;
3648
0
    eq = memchr(line, '=', (size_t) (eol - line));
3649
0
    if (eq != NULL) {
3650
0
      size_t patlen = (size_t) (eq - line);
3651
3652
0
      if (patlen >= sizeof(pat))
3653
0
        patlen = sizeof(pat) - 1;
3654
0
      memcpy(pat, line, patlen);
3655
0
      pat[patlen] = '\0';
3656
0
      keys = eq + 1;
3657
0
    } else {
3658
0
      pat[0] = '*';
3659
0
      pat[1] = '\0';
3660
0
      keys = line;
3661
0
    }
3662
0
    if (strjoker(url, pat, NULL, NULL) != NULL) {
3663
0
      size_t klen = (size_t) (eol - keys);
3664
3665
0
      if (klen >= destsize)
3666
0
        klen = destsize - 1;
3667
0
      memcpy(dest, keys, klen);
3668
0
      dest[klen] = '\0';
3669
0
      result = dest;
3670
0
    }
3671
0
  }
3672
0
  return result;
3673
0
}
3674
3675
/* see htscore.h */
3676
0
const char *hts_host_alias_rules(httrackp *opt) {
3677
0
  return StringNotEmpty(opt->host_alias) ? StringBuff(opt->host_alias) : NULL;
3678
0
}
3679
3680
/* Last rule of RULES whose alias list matches HOST (strjoker, last wins as in
3681
   the +/- filter list): its canonical host, of length *CANONLEN, or NULL. FULL
3682
   is the scheme://host form a scheme-qualified pattern matches; NOWWW collapses
3683
   the www. prefix of both the pattern and the host. */
3684
static const char *hts_host_alias_match(const char *rules, const char *host,
3685
                                        const char *full,
3686
                                        hts_boolean collapse_www,
3687
0
                                        size_t *canonlen) {
3688
0
  const char *p;
3689
0
  const char *canon = NULL;
3690
3691
  /* Walk the '\n' entries "alias[,alias...]=canonical" */
3692
0
  for (p = rules; *p != '\0';) {
3693
0
    const char *const line = p;
3694
0
    const char *eol, *eq, *pat;
3695
3696
0
    while (*p != '\0' && *p != '\n')
3697
0
      p++;
3698
0
    eol = p;
3699
0
    if (*p == '\n')
3700
0
      p++;
3701
0
    eq = memchr(line, '=', (size_t) (eol - line));
3702
0
    if (eq == NULL || eq == line || eq + 1 == eol)
3703
0
      continue; /* not "alias=canonical" */
3704
0
    for (pat = line; pat < eq;) {
3705
0
      const char *const sep = memchr(pat, ',', (size_t) (eq - pat));
3706
0
      const char *const end = sep != NULL ? sep : eq;
3707
0
      const char *start = pat;
3708
0
      size_t len = (size_t) (end - pat);
3709
0
      char BIGSTK glob[HTS_URLMAXSIZE * 2];
3710
3711
0
      while (len > 0 && (*start == ' ' || *start == '\t')) {
3712
0
        start++; /* "a.com, b.com=c.com" */
3713
0
        len--;
3714
0
      }
3715
0
      while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t' ||
3716
0
                         start[len - 1] == '/'))
3717
0
        len--; /* a trailing slash is not a path */
3718
0
      if (len != 0 && len < sizeof(glob)) {
3719
0
        char *ghost;
3720
3721
0
        memcpy(glob, start, len);
3722
0
        glob[len] = '\0';
3723
        /* the collapse applies to the pattern too, so a rule may spell either
3724
           www.example.com or example.com under -%u */
3725
0
        ghost = glob + (jump_protocol_const(glob) - glob);
3726
0
        if (collapse_www) {
3727
0
          const char *const bare = jump_normalized_const(ghost);
3728
3729
0
          if (bare != ghost)
3730
0
            memmove(ghost, bare, strlen(bare) + 1);
3731
0
        }
3732
        /* a rule that names no scheme keeps matching every scheme */
3733
0
        if (strjoker(strstr(glob, "://") != NULL ? full : host,
3734
0
                     strstr(glob, "://") != NULL ? glob : ghost, NULL,
3735
0
                     NULL) != NULL) {
3736
0
          const char *end = eol;
3737
3738
0
          canon = eq + 1;
3739
          /* Trim here, where the canonical is produced: the resolver compares
3740
             one match against the next to see a chain settle, and an untrimmed
3741
             one never equals the trimmed bytes it emits. */
3742
0
          while (canon < end && (*canon == ' ' || *canon == '\t'))
3743
0
            canon++;
3744
0
          while (end > canon &&
3745
0
                 (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '/'))
3746
0
            end--;
3747
0
          *canonlen = (size_t) (end - canon);
3748
0
          break;
3749
0
        }
3750
0
      }
3751
0
      pat = end + 1;
3752
0
    }
3753
0
  }
3754
0
  return canon;
3755
0
}
3756
3757
/* Build "scheme://host" from URL's scheme and HOST, for the rules that name a
3758
   scheme. HTS_FALSE if it does not fit DEST. */
3759
static hts_boolean hts_host_alias_subject(const char *url, const char *host,
3760
0
                                          char *dest, size_t destsize) {
3761
  /* An address with no scheme is the engine's plain-http form. Spell that one
3762
     out, or a rule naming http:// could never match anything. */
3763
0
  const hts_boolean bare =
3764
0
      jump_protocol_const(url) == url ? HTS_TRUE : HTS_FALSE;
3765
0
  const char *const scheme = bare ? "http://" : url;
3766
0
  const size_t schemelen =
3767
0
      bare ? strlen("http://") : (size_t) (jump_protocol_const(url) - url);
3768
0
  const size_t hostlen = strlen(host);
3769
3770
0
  if (schemelen >= destsize || hostlen >= destsize - schemelen)
3771
0
    return HTS_FALSE;
3772
0
  memcpy(dest, scheme, schemelen);
3773
0
  memcpy(dest + schemelen, host, hostlen);
3774
0
  dest[schemelen + hostlen] = '\0';
3775
0
  return HTS_TRUE;
3776
0
}
3777
3778
/* Chain hops resolved before giving up. A longer chain, or a loop, is a
3779
   misconfiguration; the fold leaves those hosts alone rather than settle on an
3780
   arbitrary hop, which would depend on how often a link is named. */
3781
enum { HTS_HOST_ALIAS_MAXHOPS = 8 };
3782
3783
/* hts_host_alias(), reporting through SETTLED (may be NULL) whether the chain
3784
   ended within HTS_HOST_ALIAS_MAXHOPS. */
3785
static const char *hts_host_alias_resolve(const char *rules, const char *adr,
3786
                                          hts_boolean collapse_www, char *dest,
3787
                                          size_t destsize,
3788
0
                                          hts_boolean *settled) {
3789
0
  const char *proto, *host, *hostsubj, *canon;
3790
0
  char BIGSTK fullsubj[HTS_URLMAXSIZE * 2];
3791
0
  char schemebuf[32]; /* longest jump_protocol_const knows is "socks5h://" */
3792
0
  size_t canonlen = 0, schemelen;
3793
0
  int hop;
3794
3795
0
  if (settled != NULL)
3796
0
    *settled = HTS_TRUE;
3797
0
  if (rules == NULL || *rules == '\0' || adr == NULL || destsize == 0)
3798
0
    return NULL;
3799
  /* engine pseudo-hosts: a "*=host" rule must not swallow them */
3800
0
  if (adr[0] == '\0' || strcmp(adr, "primary") == 0 ||
3801
0
      strcmp(adr, "file://") == 0)
3802
0
    return NULL;
3803
0
  proto = jump_protocol_const(adr);      /* after the scheme */
3804
0
  host = jump_identification_const(adr); /* after the credentials */
3805
0
  schemelen = (size_t) (proto - adr);
3806
0
  if (schemelen >= sizeof(schemebuf))
3807
0
    return NULL;
3808
0
  memcpy(schemebuf, adr, schemelen);
3809
0
  schemebuf[schemelen] = '\0';
3810
  /* Match the host in the form the dedup layer uses: under the www. collapse a
3811
     rule naming example.com covers www.example.com, and with the collapse off
3812
     the rule matches the name as the link spells it. */
3813
0
  hostsubj = collapse_www ? jump_normalized_const(host) : host;
3814
0
  if (!hts_host_alias_subject(adr, hostsubj, fullsubj, sizeof(fullsubj)))
3815
0
    return NULL;
3816
0
  canon =
3817
0
      hts_host_alias_match(rules, hostsubj, fullsubj, collapse_www, &canonlen);
3818
0
  if (canon == NULL)
3819
0
    return NULL;
3820
3821
  /* Follow the chain to its end, so the fold is a fixpoint: url_savename names
3822
     the same link more than once, each time from the host it wrote last. */
3823
0
  for (hop = 0;; hop++) {
3824
0
    char BIGSTK canonbuf[HTS_URLMAXSIZE * 2];
3825
0
    const char *next, *dhost, *chost, *cproto;
3826
0
    size_t nextlen = 0, credlen, hostlen;
3827
3828
    /* The rule may name the scheme the canonical host speaks; when it does not,
3829
       the scheme in effect stays, which is the previous hop's when a rule
3830
       earlier in the chain named one. The link's credentials are kept. */
3831
0
    if (canonlen >= sizeof(canonbuf))
3832
0
      return NULL;
3833
0
    memcpy(canonbuf, canon, canonlen);
3834
0
    canonbuf[canonlen] = '\0';
3835
    /* the link's credentials are the ones kept, so drop any the rule carries:
3836
       they would otherwise be prepended to them again on every re-fold */
3837
0
    cproto = jump_protocol_const(canonbuf);
3838
0
    chost = jump_identification_const(cproto);
3839
0
    hostlen = canonlen - (size_t) (chost - canonbuf);
3840
0
    if (cproto != canonbuf) { /* the rule names a scheme */
3841
0
      const size_t len = (size_t) (cproto - canonbuf);
3842
3843
0
      if (len >= sizeof(schemebuf))
3844
0
        return NULL;
3845
0
      memcpy(schemebuf, canonbuf, len);
3846
0
      schemebuf[len] = '\0';
3847
0
      schemelen = len;
3848
0
    }
3849
0
    credlen = (size_t) (host - proto);
3850
3851
0
    if (hostlen == 0)
3852
0
      return NULL; /* a rule naming no host: leave the URL alone */
3853
0
    if (schemelen >= destsize || credlen >= destsize - schemelen ||
3854
0
        hostlen >= destsize - schemelen - credlen)
3855
0
      return NULL; /* would not fit: leave the URL alone */
3856
0
    memcpy(dest, schemebuf, schemelen);
3857
0
    memcpy(dest + schemelen, proto, credlen);
3858
0
    memcpy(dest + schemelen + credlen, chost, hostlen);
3859
0
    dest[schemelen + credlen + hostlen] = '\0';
3860
3861
0
    dhost = jump_identification_const(dest);
3862
0
    if (collapse_www)
3863
0
      dhost = jump_normalized_const(dhost);
3864
0
    if (!hts_host_alias_subject(dest, dhost, fullsubj, sizeof(fullsubj)))
3865
0
      return NULL;
3866
0
    next = hts_host_alias_match(rules, dhost, fullsubj, collapse_www, &nextlen);
3867
0
    if (next == NULL)
3868
0
      break; /* settled: no rule maps this host further */
3869
0
    if (nextlen == canonlen && strncasecmp(next, canon, canonlen) == 0)
3870
0
      break; /* a rule matching its own target */
3871
0
    if (hop + 1 >= HTS_HOST_ALIAS_MAXHOPS) {
3872
0
      if (settled != NULL)
3873
0
        *settled = HTS_FALSE;
3874
0
      return NULL;
3875
0
    }
3876
0
    canon = next;
3877
0
    canonlen = nextlen;
3878
0
  }
3879
  /* a host that maps to itself: keep the callers' fast path */
3880
0
  return strcasecmp(dest, adr) != 0 ? dest : NULL;
3881
0
}
3882
3883
/* see htscore.h */
3884
const char *hts_host_alias(const char *rules, const char *adr,
3885
                           hts_boolean collapse_www, char *dest,
3886
0
                           size_t destsize) {
3887
0
  return hts_host_alias_resolve(rules, adr, collapse_www, dest, destsize, NULL);
3888
0
}
3889
3890
/* see htscore.h */
3891
0
hts_boolean hts_host_alias_collapse_www(httrackp *opt) {
3892
0
  return opt->urlhack && !opt->no_www_dedup ? HTS_TRUE : HTS_FALSE;
3893
0
}
3894
3895
/* see htscore.h */
3896
0
const char *hts_host_alias_fold(httrackp *opt, lien_adrfil *af) {
3897
0
  char BIGSTK canon[HTS_URLMAXSIZE * 2];
3898
0
  const char *const alias =
3899
0
      hts_host_alias(hts_host_alias_rules(opt), af->adr,
3900
0
                     hts_host_alias_collapse_www(opt), canon, sizeof(canon));
3901
3902
0
  if (alias != NULL)
3903
0
    strcpybuff(af->adr, alias);
3904
0
  return af->adr;
3905
0
}
3906
3907
/* see htscore.h */
3908
hts_boolean hts_host_same_alias(const char *rules, const char *adra,
3909
0
                                const char *adrb, hts_boolean collapse_www) {
3910
0
  char BIGSTK ca[HTS_URLMAXSIZE * 2], cb[HTS_URLMAXSIZE * 2];
3911
0
  const char *a, *b;
3912
3913
0
  if (rules == NULL || *rules == '\0' || adra == NULL || adrb == NULL)
3914
0
    return HTS_FALSE;
3915
0
  a = hts_host_alias(rules, adra, collapse_www, ca, sizeof(ca));
3916
0
  b = hts_host_alias(rules, adrb, collapse_www, cb, sizeof(cb));
3917
0
  if (a == NULL && b == NULL)
3918
0
    return HTS_FALSE; /* neither is aliased: the caller's own test */
3919
0
  return strcasecmp(jump_identification_const(a != NULL ? a : adra),
3920
0
                    jump_identification_const(b != NULL ? b : adrb)) == 0
3921
0
             ? HTS_TRUE
3922
0
             : HTS_FALSE;
3923
0
}
3924
3925
/* HTS_TRUE if TOKEN[0..LEN) is a host, optionally behind the scheme it speaks
3926
   and with a trailing slash. GLOB keeps the filter metacharacters legal, which
3927
   only the alias side of a rule may use. */
3928
static hts_boolean hts_host_alias_token_ok(const char *token, size_t len,
3929
0
                                           hts_boolean glob) {
3930
0
  char BIGSTK buff[HTS_URLMAXSIZE * 2];
3931
0
  const char *host;
3932
3933
0
  while (len > 0 && (*token == ' ' || *token == '\t')) {
3934
0
    token++;
3935
0
    len--;
3936
0
  }
3937
0
  while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t'))
3938
0
    len--;
3939
0
  if (len == 0 || len >= sizeof(buff))
3940
0
    return HTS_FALSE;
3941
0
  memcpy(buff, token, len);
3942
0
  buff[len] = '\0';
3943
0
  while (len > 0 && buff[len - 1] == '/')
3944
0
    buff[--len] = '\0'; /* the matcher strips the whole run */
3945
  /* an alias may glob its scheme; an unknown canonical one lands in the host */
3946
0
  host = glob && strstr(buff, "://") != NULL ? strstr(buff, "://") + 3
3947
0
                                             : jump_protocol_const(buff);
3948
  /* a scheme with no host behind it, and the engine's own pseudo-host, name
3949
     nothing the fold could write into an address */
3950
0
  if (*host == '\0' || strcmp(host, "primary") == 0)
3951
0
    return HTS_FALSE;
3952
  /* --host-alias maps hosts: a path belongs to no part of an address */
3953
0
  if (strchr(host, '/') != NULL)
3954
0
    return HTS_FALSE;
3955
  /* a control byte names no host, and '\n' would split the store's rule list */
3956
0
  for (; *host != '\0'; host++) {
3957
0
    if ((unsigned char) *host < ' ' || *host == '\x7f')
3958
0
      return HTS_FALSE;
3959
0
    if (strchr(glob ? "=# " : "=,*?();\\#@ ", *host) != NULL)
3960
0
      return HTS_FALSE;
3961
0
  }
3962
0
  return HTS_TRUE;
3963
0
}
3964
3965
/* see httrack-library.h */
3966
0
HTSEXT_API hts_boolean hts_host_alias_rule_ok(const char *rule) {
3967
0
  const char *const eq = rule != NULL ? strchr(rule, '=') : NULL;
3968
0
  const char *pat;
3969
3970
0
  if (eq == NULL || eq == rule || eq[1] == '\0')
3971
0
    return HTS_FALSE;
3972
0
  if (!hts_host_alias_token_ok(eq + 1, strlen(eq + 1), HTS_FALSE))
3973
0
    return HTS_FALSE;
3974
0
  for (pat = rule; pat < eq;) {
3975
0
    const char *const sep = memchr(pat, ',', (size_t) (eq - pat));
3976
0
    const char *const end = sep != NULL ? sep : eq;
3977
3978
0
    if (!hts_host_alias_token_ok(pat, (size_t) (end - pat), HTS_TRUE))
3979
0
      return HTS_FALSE;
3980
0
    pat = end + 1;
3981
0
  }
3982
0
  return HTS_TRUE;
3983
0
}
3984
3985
/* see htscore.h */
3986
const char *hts_host_alias_looping(const char *rules, hts_boolean collapse_www,
3987
0
                                   char *dest, size_t destsize) {
3988
0
  const char *p;
3989
3990
0
  if (rules == NULL || destsize == 0)
3991
0
    return NULL;
3992
0
  for (p = rules; *p != '\0';) {
3993
0
    const char *const line = p;
3994
0
    const char *eol, *eq;
3995
3996
0
    while (*p != '\0' && *p != '\n')
3997
0
      p++;
3998
0
    eol = p;
3999
0
    if (*p == '\n')
4000
0
      p++;
4001
0
    eq = memchr(line, '=', (size_t) (eol - line));
4002
0
    if (eq == NULL || eq + 1 == eol)
4003
0
      continue;
4004
0
    {
4005
0
      const size_t len = (size_t) (eol - eq - 1);
4006
0
      char BIGSTK canon[HTS_URLMAXSIZE * 2], tmp[HTS_URLMAXSIZE * 2];
4007
0
      hts_boolean settled;
4008
4009
0
      if (len >= sizeof(canon))
4010
0
        continue;
4011
0
      memcpy(canon, eq + 1, len);
4012
0
      canon[len] = '\0';
4013
0
      (void) hts_host_alias_resolve(rules, canon, collapse_www, tmp,
4014
0
                                    sizeof(tmp), &settled);
4015
0
      if (!settled) {
4016
0
        if (strlen(canon) >= destsize)
4017
0
          continue; /* cannot name it in full: keep looking */
4018
0
        strlcpybuff(dest, canon, destsize);
4019
0
        return dest;
4020
0
      }
4021
0
    }
4022
0
  }
4023
0
  return NULL;
4024
0
}
4025
4026
#define endwith(a) ( (len >= (sizeof(a)-1)) ? ( strncmp(dest, a+len-(sizeof(a)-1), sizeof(a)-1) == 0 ) : 0 );
4027
HTSEXT_API char *adr_normalized_sized(const char *source, char *dest,
4028
0
                                      size_t destsize) {
4029
  /* not yet too aggressive (no com<->net<->org checkings) */
4030
0
  strlcpybuff(dest, jump_normalized_const(source), destsize);
4031
0
  return dest;
4032
0
}
4033
4034
// deprecated variant; kept for ABI compatibility. Bounds to the implicit
4035
// contract the old callers relied on (an HTS_URLMAXSIZE*2 URL buffer).
4036
0
HTSEXT_API char *adr_normalized(const char *source, char *dest) {
4037
0
  return adr_normalized_sized(source, dest, HTS_URLMAXSIZE * 2);
4038
0
}
4039
4040
#undef endwith
4041
4042
// find port (:80) or NULL if not found
4043
// can handle IPV6 addresses
4044
0
HTSEXT_API const char *jump_toport_const(const char *source) {
4045
0
  const char *a, *trytofind;
4046
4047
0
  a = jump_identification_const(source);
4048
  // last ] of the authority (http://[3ffe:b80:1234::1]:80/foo.html), bounded
4049
  // from a: source's own scheme "//" would end the scan before it
4050
0
  trytofind = strrchr_limit(a, ']', strchr(a, '/'));
4051
0
  a = strchr((trytofind) ? trytofind : a, ':');
4052
0
  return a;
4053
0
}
4054
4055
0
HTSEXT_API DECLARE_NON_CONST_VERSION(jump_toport)
4056
4057
// strrchr, but not too far
4058
0
const char *strrchr_limit(const char *s, char c, const char *limit) {
4059
0
  if (limit == NULL) {
4060
0
    const char *p = strrchr(s, c);
4061
4062
0
    return p ? (p + 1) : NULL;
4063
0
  } else {
4064
0
    const char *a = NULL, *p;
4065
4066
0
    for(;;) {
4067
0
      p = strchr((a) ? a : s, c);
4068
0
      if ((p >= limit) || (p == NULL))
4069
0
        return a;
4070
0
      a = p + 1;
4071
0
    }
4072
0
  }
4073
0
}
4074
4075
// retourner adr sans ftp://
4076
0
const char *jump_protocol_const(const char *source) {
4077
0
  int p;
4078
4079
  // scheme
4080
  // "Comparisons of scheme names MUST be case-insensitive" (RFC2616)
4081
0
  if ((p = strfield(source, "http:")))
4082
0
    source += p;
4083
0
  else if ((p = strfield(source, "ftp:")))
4084
0
    source += p;
4085
0
  else if ((p = strfield(source, "https:")))
4086
0
    source += p;
4087
0
  else if ((p = strfield(source, "file:")))
4088
0
    source += p;
4089
0
  else if ((p = strfield(source, "socks5h:")))
4090
0
    source += p;
4091
0
  else if ((p = strfield(source, "socks5:")))
4092
0
    source += p;
4093
0
  else if ((p = strfield(source, "connect:")))
4094
0
    source += p;
4095
  // net_path
4096
0
  if (strncmp(source, "//", 2) == 0)
4097
0
    source += 2;
4098
0
  return source;
4099
0
}
4100
4101
0
DECLARE_NON_CONST_VERSION(jump_protocol)
4102
4103
0
hts_boolean hts_is_control_free_sized(const char *str, size_t len) {
4104
0
  size_t i;
4105
4106
0
  for (i = 0; i < len; i++) {
4107
0
    if ((unsigned char) str[i] < ' ')
4108
0
      return HTS_FALSE;
4109
0
  }
4110
0
  return HTS_TRUE;
4111
0
}
4112
4113
0
hts_boolean hts_is_control_free(const char *str) {
4114
0
  return hts_is_control_free_sized(str, strlen(str));
4115
0
}
4116
4117
0
hts_boolean hts_host_is_ipv4(const char *host, size_t len) {
4118
0
  size_t i;
4119
0
  int dots = 0;
4120
4121
0
  for (i = 0; i < len; i++) {
4122
0
    if (host[i] == '.')
4123
0
      dots++;
4124
0
    else if (host[i] < '0' || host[i] > '9')
4125
0
      return HTS_FALSE;
4126
0
  }
4127
0
  return dots > 0 ? HTS_TRUE : HTS_FALSE;
4128
0
}
4129
4130
0
hts_boolean hts_proxy_is_socks(const char *name) {
4131
0
  if (name == NULL)
4132
0
    return HTS_FALSE;
4133
0
  return (strfield(name, "socks5h:") || strfield(name, "socks5:")) ? HTS_TRUE
4134
0
                                                                   : HTS_FALSE;
4135
0
}
4136
4137
0
hts_boolean hts_proxy_is_connect(const char *name) {
4138
0
  if (name == NULL)
4139
0
    return HTS_FALSE;
4140
0
  return strfield(name, "connect:") ? HTS_TRUE : HTS_FALSE;
4141
0
}
4142
4143
// default proxy port for a -P argument, keyed on the scheme
4144
0
static int proxy_default_port(const char *arg) {
4145
0
  return hts_proxy_is_socks(arg) ? 1080 : 8080;
4146
0
}
4147
4148
// port "a" of -P argument "arg": digits fitting TCP's 1..65535, else the scheme
4149
// default. Not sscanf("%d"): past INT_MAX it wraps to a garbage port (#602)
4150
0
static int parse_proxy_port(const char *a, const char *arg) {
4151
0
  int port;
4152
4153
0
  if (!hts_parse_url_port(a, &port))
4154
0
    return proxy_default_port(arg);
4155
0
  return port;
4156
0
}
4157
4158
0
void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
4159
0
  const char *authority = strstr(arg, "://");
4160
0
  const char *a;
4161
0
  size_t namelen;
4162
4163
0
  if (name_size == 0)
4164
0
    return;
4165
  // scan back to the port ':' (or userinfo '@'), never past the authority (a
4166
  // scheme's own colon is not a port separator) nor into an IPv6 literal (']'
4167
  // stops it); inspect a[-1] from one-past-end so no pointer underflows
4168
0
  authority = (authority != NULL) ? authority + 3 : arg;
4169
0
  a = arg + strlen(arg);
4170
0
  while (a > authority && a[-1] != ':' && a[-1] != '@' && a[-1] != ']')
4171
0
    a--;
4172
0
  if (a > authority && a[-1] == ':') {
4173
0
    *port = parse_proxy_port(a, arg);
4174
0
    namelen = (size_t) (a - 1 - arg);
4175
0
  } else {
4176
0
    *port = proxy_default_port(arg);
4177
0
    namelen = strlen(arg);
4178
0
  }
4179
0
  if (namelen >= name_size) // arg is user-controlled: truncate, don't overflow
4180
0
    namelen = name_size - 1;
4181
0
  memcpy(name, arg, namelen);
4182
0
  name[namelen] = '\0';
4183
0
}
4184
4185
// codage base 64 a vers b
4186
0
void code64(unsigned char *a, int size_a, unsigned char *b, int crlf) {
4187
0
  int i1 = 0, i2 = 0, i3 = 0, i4 = 0;
4188
0
  int loop = 0;
4189
0
  unsigned long int store;
4190
0
  int n;
4191
0
  const char _hts_base64[] =
4192
0
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4193
0
  while(size_a-- > 0) {
4194
    // 24 bits
4195
0
    n = 1;
4196
0
    store = *a++;
4197
0
    if (size_a-- > 0) {
4198
0
      n = 2;
4199
0
      store <<= 8;
4200
0
      store |= *a++;
4201
0
    }
4202
0
    if (size_a-- > 0) {
4203
0
      n = 3;
4204
0
      store <<= 8;
4205
0
      store |= *a++;
4206
0
    }
4207
0
    if (n == 3) {
4208
0
      i4 = store & 63;
4209
0
      i3 = (store >> 6) & 63;
4210
0
      i2 = (store >> 12) & 63;
4211
0
      i1 = (store >> 18) & 63;
4212
0
    } else if (n == 2) {
4213
0
      store <<= 2;
4214
0
      i3 = store & 63;
4215
0
      i2 = (store >> 6) & 63;
4216
0
      i1 = (store >> 12) & 63;
4217
0
    } else {
4218
0
      store <<= 4;
4219
0
      i2 = store & 63;
4220
0
      i1 = (store >> 6) & 63;
4221
0
    }
4222
4223
0
    *b++ = _hts_base64[i1];
4224
0
    *b++ = _hts_base64[i2];
4225
0
    if (n >= 2)
4226
0
      *b++ = _hts_base64[i3];
4227
0
    else
4228
0
      *b++ = '=';
4229
0
    if (n >= 3)
4230
0
      *b++ = _hts_base64[i4];
4231
0
    else
4232
0
      *b++ = '=';
4233
4234
0
    if (crlf && ((loop += 3) % 60) == 0) {
4235
0
      *b++ = '\r';
4236
0
      *b++ = '\n';
4237
0
    }
4238
0
  }
4239
0
  *b++ = '\0';
4240
0
}
4241
4242
35.8k
void unescape_amp(char *s) {
4243
35.8k
  if (hts_unescapeEntities(s, s, strlen(s) + 1) != 0) {
4244
0
    assertf(! "error escaping html entities");
4245
0
  }
4246
35.8k
}
4247
4248
// remplacer %20 par ' ', etc..
4249
// buffer MAX 1Ko
4250
35.8k
HTSEXT_API char *unescape_http(char *const catbuff, const size_t size, const char *const s) {
4251
35.8k
  size_t i, j;
4252
4253
35.8k
  RUNTIME_TIME_CHECK_SIZE(size);
4254
4255
1.40M
  for(i = 0, j = 0; s[i] != '\0' && j + 1 < size ; i++) {
4256
1.36M
    int h;
4257
1.36M
    if (s[i] == '%' && (h = hts_ehex(&s[i + 1])) >= 0) {
4258
4.09k
      catbuff[j++] = (char) h;
4259
4.09k
      i += 2;
4260
4.09k
    } else
4261
1.36M
      catbuff[j++] = s[i];
4262
1.36M
  }
4263
35.8k
  catbuff[j++] = '\0';
4264
35.8k
  return catbuff;
4265
35.8k
}
4266
4267
// unescape in URL/URI ONLY what has to be escaped, to form a standard URL/URI
4268
// DOES NOT DECODE %25 (part of CHAR_DELIM)
4269
// no_high & 1: decode high chars
4270
// no_high & 2: decode space
4271
HTSEXT_API char *unescape_http_unharm(char *const catbuff, const size_t size,
4272
                                      const char *s,
4273
71.6k
                                      const hts_boolean no_high) {
4274
71.6k
  size_t i, j;
4275
4276
71.6k
  RUNTIME_TIME_CHECK_SIZE(size);
4277
4278
2.82M
  for(i = 0, j = 0; s[i] != '\0' && j + 1 < size ; i++) {
4279
2.75M
    if (s[i] == '%') {
4280
13.1k
      const int nchar = hts_ehex(&s[i + 1]);
4281
4282
13.1k
      const int test = 
4283
13.1k
        ( CHAR_RESERVED(nchar) && nchar != '+' )        /* %2B => + (not in query!) */
4284
10.6k
        || CHAR_DELIM(nchar)
4285
9.57k
        || CHAR_UNWISE(nchar)
4286
8.01k
        || CHAR_LOW(nchar)    /* CHAR_SPECIAL */
4287
7.66k
        || ( CHAR_XXAVOID(nchar) && ( nchar != ' ' || ( no_high & 2) == 0 ) )
4288
6.24k
        || ( ( no_high & 1 ) && CHAR_HIG(nchar) )
4289
13.1k
        ;
4290
4291
13.1k
      if (!test && nchar >= 0) {  /* can safely unescape */
4292
846
        catbuff[j++] = (char) nchar;
4293
846
        i += 2;
4294
12.2k
      } else {
4295
12.2k
        catbuff[j++] = '%';
4296
12.2k
      }
4297
2.73M
    } else {
4298
2.73M
      catbuff[j++] = s[i];
4299
2.73M
    }
4300
2.75M
  }
4301
71.6k
  catbuff[j++] = '\0';
4302
71.6k
  return catbuff;
4303
71.6k
}
4304
4305
// remplacer " par %xx etc..
4306
// buffer MAX 1Ko
4307
HTSEXT_API size_t escape_spc_url(const char *const src, 
4308
0
                                 char *const dest, const size_t size) {
4309
0
  return x_escape_http(src, dest, size, 2);
4310
0
}
4311
4312
// smith / john -> smith%20%2f%20john
4313
HTSEXT_API size_t escape_in_url(const char *const src, 
4314
0
                                char *const dest, const size_t size) {
4315
0
  return x_escape_http(src, dest, size, 1);
4316
0
}
4317
4318
// smith / john -> smith%20/%20john
4319
HTSEXT_API size_t escape_uri(const char *const src, 
4320
0
                             char *const dest, const size_t size) {
4321
0
  return x_escape_http(src, dest, size, 3);
4322
0
}
4323
4324
HTSEXT_API size_t escape_uri_utf(const char *const src, 
4325
0
                                 char *const dest, const size_t size) {
4326
0
  return x_escape_http(src, dest, size, 30);
4327
0
}
4328
4329
HTSEXT_API size_t escape_check_url(const char *const src, char *const dest,
4330
0
                                   const size_t size) {
4331
0
  return x_escape_http(src, dest, size, 0);
4332
0
}
4333
4334
HTSEXT_API size_t escape_control_url(const char *const src, char *const dest,
4335
0
                                     const size_t size) {
4336
0
  return x_escape_http(src, dest, size, 4);
4337
0
}
4338
4339
// same as escape_check_url, but returns char*
4340
HTSEXT_API char *escape_check_url_addr(const char *const src, 
4341
0
                                       char *const dest, const size_t size) {
4342
0
  escape_check_url(src, dest, size);
4343
0
  return dest;
4344
0
}
4345
4346
// Same as above, but appending to "dest"
4347
#undef DECLARE_APPEND_ESCAPE_VERSION
4348
#define DECLARE_APPEND_ESCAPE_VERSION(NAME) \
4349
0
HTSEXT_API size_t append_ ##NAME(const char *const src, char *const dest, const size_t size) { \
4350
0
  const size_t len = strnlen(dest, size); \
4351
0
  assertf(len < size); \
4352
0
  return NAME(src, dest + len, size - len); \
4353
0
}
Unexecuted instantiation: append_escape_in_url
Unexecuted instantiation: append_escape_spc_url
Unexecuted instantiation: append_escape_uri_utf
Unexecuted instantiation: append_escape_check_url
Unexecuted instantiation: append_escape_uri
4354
4355
DECLARE_APPEND_ESCAPE_VERSION(escape_in_url)
4356
DECLARE_APPEND_ESCAPE_VERSION(escape_spc_url)
4357
DECLARE_APPEND_ESCAPE_VERSION(escape_uri_utf)
4358
DECLARE_APPEND_ESCAPE_VERSION(escape_check_url)
4359
DECLARE_APPEND_ESCAPE_VERSION(escape_uri)
4360
4361
#undef DECLARE_APPEND_ESCAPE_VERSION
4362
4363
// In-place escaping: copy dest aside, then escape that copy back into dest.
4364
typedef size_t (*escape_fn_t)(const char *src, char *dest, size_t size);
4365
4366
static size_t inplace_escape(char *const dest, const size_t size,
4367
0
                             escape_fn_t escape) {
4368
0
  char buffer[256];
4369
0
  const size_t len = strnlen(dest, size);
4370
0
  const int in_buffer = len + 1 < sizeof(buffer);
4371
0
  char *src = in_buffer ? buffer : malloct(len + 1);
4372
0
  size_t ret;
4373
0
  assertf(src != NULL);
4374
0
  assertf(len < size);
4375
0
  memcpy(src, dest, len + 1);
4376
0
  ret = escape(src, dest, size);
4377
0
  if (!in_buffer) {
4378
0
    freet(src);
4379
0
  }
4380
0
  return ret;
4381
0
}
4382
4383
// Thin exported wrappers binding inplace_escape() to each escaper (ABI).
4384
#undef DECLARE_INPLACE_ESCAPE_VERSION
4385
#define DECLARE_INPLACE_ESCAPE_VERSION(NAME)                                   \
4386
0
  HTSEXT_API size_t inplace_##NAME(char *const dest, const size_t size) {      \
4387
0
    return inplace_escape(dest, size, NAME);                                   \
4388
0
  }
Unexecuted instantiation: inplace_escape_in_url
Unexecuted instantiation: inplace_escape_spc_url
Unexecuted instantiation: inplace_escape_uri_utf
Unexecuted instantiation: inplace_escape_check_url
Unexecuted instantiation: inplace_escape_uri
4389
4390
DECLARE_INPLACE_ESCAPE_VERSION(escape_in_url)
4391
DECLARE_INPLACE_ESCAPE_VERSION(escape_spc_url)
4392
DECLARE_INPLACE_ESCAPE_VERSION(escape_uri_utf)
4393
DECLARE_INPLACE_ESCAPE_VERSION(escape_check_url)
4394
DECLARE_INPLACE_ESCAPE_VERSION(escape_uri)
4395
4396
#undef DECLARE_INPLACE_ESCAPE_VERSION
4397
4398
4399
HTSEXT_API size_t make_content_id(const char *const adr, const char *const fil, 
4400
0
                                  char *const dest, const size_t size) {
4401
0
  char *a;
4402
0
  size_t esc_size = escape_in_url(adr, dest, size);
4403
0
  esc_size += escape_in_url(fil, dest + esc_size, size - esc_size);
4404
0
  RUNTIME_TIME_CHECK_SIZE(size);
4405
0
  for(a = dest ; (a = strchr(a, '%')) != NULL ; a++) {
4406
0
    *a = 'X';
4407
0
  }
4408
0
  return esc_size;
4409
0
}
4410
4411
// strip all control characters
4412
0
HTSEXT_API void escape_remove_control(char *const s) {
4413
0
  size_t i, j;
4414
0
  for(i = 0, j = 0 ; s[i] != '\0' ; i++) {
4415
0
    const unsigned char c = (unsigned  char) s[i];
4416
0
    if (c >= 32) {
4417
0
      if (i != j) {
4418
0
        assertf(j < i);
4419
0
        s[j] = s[i];
4420
0
      }
4421
0
      j++;
4422
0
    }
4423
0
  }
4424
  // compaction left the original tail sitting past j
4425
0
  s[j] = '\0';
4426
0
}
4427
4428
#undef ADD_CHAR
4429
0
#define ADD_CHAR(C) do {   \
4430
0
      assertf(j < size);    \
4431
0
      if (j + 1 == size) { \
4432
0
        dest[j] = '\0';    \
4433
0
        return size;       \
4434
0
      }                    \
4435
0
      dest[j++] = (C);     \
4436
0
  } while(0)
4437
4438
/* Returns the number of characters written (not taking in account the terminating \0), or 'size' upon overflow. */
4439
HTSEXT_API size_t x_escape_http(const char *const s, char *const dest, 
4440
0
                                const size_t size, const int mode) {
4441
0
  static const char hex[] = "0123456789abcdef";
4442
0
  size_t i, j;
4443
4444
0
  RUNTIME_TIME_CHECK_SIZE(size);
4445
4446
  // Out-of-bound.
4447
  // Previous character is supposed to be the terminating \0.
4448
0
  if (size == 0) {
4449
0
    return 0;
4450
0
  }
4451
4452
0
  for(i = 0, j = 0 ; s[i] != '\0' ; i++) {
4453
0
    const unsigned char c = (unsigned char) s[i];
4454
0
    int test = 0;
4455
4456
0
    if (mode == 0)
4457
0
      test = c == '"' || c == ' ' || CHAR_SPECIAL(c);
4458
0
    else if (mode == 1)
4459
0
      test = CHAR_RESERVED(c)
4460
0
             || CHAR_DELIM(c)
4461
0
             || CHAR_UNWISE(c)
4462
0
             || CHAR_SPECIAL(c)
4463
0
             || CHAR_XXAVOID(c)
4464
0
             || CHAR_MARK(c);
4465
0
    else if (mode == 2)
4466
0
      test = c == ' ';       // n'escaper que espace
4467
0
    else if (mode == 3)         // échapper que ce qui est nécessaire
4468
0
      test = CHAR_SPECIAL(c)
4469
0
             || CHAR_XXAVOID(c);
4470
0
    else if (mode == 4) // C0 controls only, leaving high bytes (UTF-8)
4471
0
      test = CHAR_LOW(c);
4472
0
    else if (mode == 30)      // échapper que ce qui est nécessaire
4473
0
      test = (c != '/' && CHAR_RESERVED(c))
4474
0
        || CHAR_DELIM(c)
4475
0
        || CHAR_UNWISE(c)
4476
0
        || CHAR_SPECIAL(c)
4477
0
        || CHAR_XXAVOID(c);
4478
4479
0
    if (!test) {
4480
0
      ADD_CHAR(c);
4481
0
    } else {
4482
0
      ADD_CHAR('%');
4483
0
      ADD_CHAR(hex[c / 16]);
4484
0
      ADD_CHAR(hex[c % 16]);
4485
0
    }
4486
0
  }
4487
4488
0
  assertf(j < size);
4489
0
  dest[j] = '\0';
4490
0
  return j;
4491
0
}
4492
4493
0
HTSEXT_API size_t escape_for_html_print(const char *const s, char *const dest, const size_t size) {
4494
0
  size_t i, j;
4495
4496
0
  RUNTIME_TIME_CHECK_SIZE(size);
4497
4498
0
  for(i = 0, j = 0 ; s[i] != '\0' ; i++) {
4499
0
    const unsigned char c = (unsigned char) s[i];
4500
0
    if (c == '&') {
4501
0
      ADD_CHAR('&');
4502
0
      ADD_CHAR('a');
4503
0
      ADD_CHAR('m');
4504
0
      ADD_CHAR('p');
4505
0
      ADD_CHAR(';');
4506
0
    } else {
4507
0
      ADD_CHAR(c);
4508
0
    }
4509
0
  }
4510
0
  assertf(j < size);
4511
0
  dest[j] = '\0';
4512
0
  return j;
4513
0
}
4514
4515
0
HTSEXT_API size_t escape_for_html_print_full(const char *const s, char *const dest, const size_t size) {
4516
0
  static const char hex[] = "0123456789abcdef";
4517
0
  size_t i, j;
4518
4519
0
  RUNTIME_TIME_CHECK_SIZE(size);
4520
4521
0
  for(i = 0, j = 0 ; s[i] != '\0' ; i++) {
4522
0
    const unsigned char c = (unsigned char) s[i];
4523
0
    if (c == '&') {
4524
0
      ADD_CHAR('&');
4525
0
      ADD_CHAR('a');
4526
0
      ADD_CHAR('m');
4527
0
      ADD_CHAR('p');
4528
0
      ADD_CHAR(';');
4529
0
    } else if (CHAR_HIG(c)) {
4530
0
      ADD_CHAR('&');
4531
0
      ADD_CHAR('#');
4532
0
      ADD_CHAR('x');
4533
0
      ADD_CHAR(hex[c / 16]);
4534
0
      ADD_CHAR(hex[c % 16]);
4535
0
      ADD_CHAR(';');
4536
0
    } else {
4537
0
      ADD_CHAR(c);
4538
0
    }
4539
0
  }
4540
0
  assertf(j < size);
4541
0
  dest[j] = '\0';
4542
0
  return j;
4543
0
}
4544
4545
#undef ADD_CHAR
4546
4547
// lower-case conversion into caller buffer (capacity catbuffsize)
4548
0
char *convtolower(char *catbuff, size_t catbuffsize, const char *a) {
4549
0
  strlcpybuff(catbuff, a, catbuffsize);
4550
0
  hts_lowcase(catbuff);         // lower case
4551
0
  return catbuff;
4552
0
}
4553
4554
// conversion en minuscules
4555
0
void hts_lowcase(char *s) {
4556
0
  size_t i;
4557
4558
0
  for(i = 0; s[i] != '\0'; i++)
4559
0
    if ((s[i] >= 'A') && (s[i] <= 'Z'))
4560
0
      s[i] += ('a' - 'A');
4561
0
}
4562
4563
// guess a local file's mime type (e.g. fil="toto.gif" -> s="image/gif")
4564
// returns 1 if a type was written to s, 0 otherwise
4565
hts_boolean guess_httptype_sized(httrackp *opt, char *s, size_t ssize,
4566
0
                                 const char *fil) {
4567
0
  return get_httptype_sized(opt, s, ssize, fil, 1);
4568
0
}
4569
4570
// deprecated variant; kept for ABI compatibility. Bounds to the implicit
4571
// contract the old callers relied on (a contenttype-sized buffer).
4572
0
void guess_httptype(httrackp * opt, char *s, const char *fil) {
4573
0
  (void) get_httptype_sized(opt, s, HTS_MIMETYPE_SIZE, fil, 1);
4574
0
}
4575
4576
// first match in a NUL-terminated {mime,ext} table. key selects the lookup
4577
// column (0=mime, 1=ext); returns the other column, or NULL if no row matches
4578
// (a "*" partner means the row carries no value).
4579
static const char *hts_mime_lookup(const char *(*table)[2], int key,
4580
0
                                   const char *needle) {
4581
0
  int j;
4582
4583
0
  for (j = 0; strnotempty(table[j][1]); j++) {
4584
0
    if (strfield2(table[j][key], needle) && table[j][!key][0] != '*')
4585
0
      return table[j][!key];
4586
0
  }
4587
0
  return NULL;
4588
0
}
4589
4590
// write the mime type for fil into s (capacity ssize)
4591
// flag: 1 to always return a type (the "application/..." / octet-stream
4592
// fallback) returns 1 if a type was written to s, 0 otherwise
4593
HTSEXT_API hts_boolean get_httptype_sized(httrackp *opt, char *s, size_t ssize,
4594
0
                                          const char *fil, hts_boolean flag) {
4595
  // userdef overrides get_httptype (a rule with an empty value, e.g. "--assume
4596
  // cgi=", matches but writes nothing: report it as "no type" like the old
4597
  // code, whose callers tested strnotempty(s))
4598
0
  if (get_userhttptype(opt, s, ssize, fil)) {
4599
0
    return s[0] != '\0';
4600
0
  }
4601
  // regular tests
4602
0
  if (ishtml(opt, fil) == 1) {
4603
0
    strlcpybuff(s, "text/html", ssize);
4604
0
    return 1;
4605
0
  } else {
4606
    /* Check html -> text/html */
4607
0
    const char *a = hts_lastcharptr(fil);
4608
4609
0
    while ((a > fil) && (*a != '.') && (*a != '/'))
4610
0
      a--;
4611
0
    if (a >= fil && *a == '.' && strlen(a) < 32) {
4612
0
      const char *mime;
4613
4614
0
      a++;
4615
0
      mime = hts_mime_lookup(hts_mime, 1, a);
4616
0
      if (mime == NULL)
4617
0
        mime = hts_mime_lookup(hts_mime_modern, 1, a);
4618
0
      if (mime != NULL) {
4619
0
        strlcpybuff(s, mime, ssize);
4620
0
        return 1;
4621
0
      }
4622
4623
0
      if (flag) {
4624
0
        snprintf(s, ssize, "application/%s", a);
4625
0
        return 1;
4626
0
      }
4627
0
    } else {
4628
0
      if (flag) {
4629
0
        strlcpybuff(s, "application/octet-stream", ssize);
4630
0
        return 1;
4631
0
      }
4632
0
    }
4633
0
  }
4634
0
  return 0;
4635
0
}
4636
4637
// deprecated variant; kept for ABI compatibility. Bounds to the implicit
4638
// contract the old callers relied on (a contenttype-sized buffer).
4639
HTSEXT_API void get_httptype(httrackp *opt, char *s, const char *fil,
4640
0
                             int flag) {
4641
0
  (void) get_httptype_sized(opt, s, HTS_MIMETYPE_SIZE, fil, flag);
4642
0
}
4643
4644
// get type of fil (php)
4645
// s: destination buffer, or NULL
4646
hts_boolean get_userhttptype(httrackp *opt, char *s, size_t ssize,
4647
0
                             const char *fil) {
4648
0
  if (s != NULL) {
4649
0
    assertf(ssize != 0);
4650
0
    if (s)
4651
0
      s[0] = '\0';
4652
0
    if (fil == NULL || *fil == '\0')
4653
0
      return HTS_FALSE;
4654
0
#if 1
4655
0
    if (StringLength(opt->mimedefs) > 0) {
4656
4657
      /* Check --assume foooo/foo/bar.cgi=text/html, then foo/bar.cgi=text/html, then bar.cgi=text/html */
4658
      /* also: --assume baz,bar,foooo/foo/bar.cgi=text/html */
4659
      /* start from path beginning */
4660
0
      do {
4661
0
        const char *next;
4662
0
        const char *mimedefs = StringBuff(opt->mimedefs);       /* loop through mime definitions : \nfoo=bar\nzoo=baz\n.. */
4663
4664
0
        while(*mimedefs != '\0') {
4665
0
          const char *segment = fil + 1;
4666
4667
0
          if (*mimedefs == '\n') {
4668
0
            mimedefs++;
4669
0
          }
4670
          /* compare current segment with user's definition */
4671
0
          do {
4672
0
            int i;
4673
4674
            /* check current item */
4675
0
            for(i = 0; mimedefs[i] != '\0'      /* end of all defs */
4676
0
                && mimedefs[i] != ' '   /* next item in left list */
4677
0
                && mimedefs[i] != '='   /* end of left list */
4678
0
                && mimedefs[i] != '\n'  /* end of this def (?) */
4679
0
                && mimedefs[i] == segment[i]    /* same item */
4680
0
                ; i++) ;
4681
            /* success */
4682
0
            if ((mimedefs[i] == '=' || mimedefs[i] == ' ') &&
4683
0
                segment[i] == '\0') {
4684
0
              while(mimedefs[i] != 0 && mimedefs[i] != '\n'
4685
0
                    && mimedefs[i] != '=')
4686
0
                i++;
4687
0
              if (mimedefs[i] == '=') {
4688
0
                size_t len;
4689
4690
0
                i++;
4691
0
                len = strcspn(&mimedefs[i], "\n");
4692
                /* clip, don't abort: the consumers match whole strings far
4693
                   shorter than any destination, so a value long enough to be
4694
                   clipped already matched nothing (#1276) */
4695
0
                if (len >= ssize) {
4696
0
                  hts_log_print(opt, LOG_WARNING,
4697
0
                                "--assume value for '%s' is %d bytes, clipped "
4698
0
                                "to %d",
4699
0
                                fil, (int) len, (int) (ssize - 1));
4700
0
                  len = ssize - 1;
4701
0
                }
4702
0
                memcpy(s, &mimedefs[i], len);
4703
0
                s[len] = '\0';
4704
0
                return HTS_TRUE; /* SUCCESS! */
4705
0
              }
4706
0
            }
4707
            /* next item in list */
4708
0
            for(mimedefs += i;
4709
0
                *mimedefs != '\0' && *mimedefs != '\n' && *mimedefs != '='
4710
0
                && *mimedefs != ' '; mimedefs++) ;
4711
0
            if (*mimedefs == ' ') {
4712
0
              mimedefs++;
4713
0
            }
4714
0
          } while(*mimedefs != '\0' && *mimedefs != '\n' && *mimedefs != '=');
4715
          /* next user-def */
4716
0
          for(; *mimedefs != '\0' && *mimedefs != '\n'; mimedefs++) ;
4717
0
        }
4718
        /* shorten segment */
4719
0
        next = strchr(fil + 1, '/');
4720
0
        if (next == NULL) {
4721
          /* ext tests */
4722
0
          next = strchr(fil + 1, '.');
4723
0
        }
4724
0
        fil = next;
4725
0
      } while(fil != NULL);
4726
0
    }
4727
#else
4728
    if (*buffer) {
4729
      char BIGSTK search[1024];
4730
      char *detect;
4731
4732
      sprintf(search, "\n%s=", ext);    // php=text/html
4733
      detect = strstr(*buffer, search);
4734
      if (!detect) {
4735
        sprintf(search, "\n%s\n", ext); // php\ncgi=text/html
4736
        detect = strstr(*buffer, search);
4737
      }
4738
      if (detect) {
4739
        detect = strchr(detect, '=');
4740
        if (detect) {
4741
          detect++;
4742
          if (s) {
4743
            char *a;
4744
4745
            a = strchr(detect, '\n');
4746
            if (a) {
4747
              strncatbuff(s, detect, (int) (a - detect));
4748
            }
4749
          }
4750
          return 1;
4751
        }
4752
      }
4753
    }
4754
#endif
4755
0
  }
4756
0
  return HTS_FALSE;
4757
0
}
4758
4759
// give the file extension for a mime type (e.g. "image/gif" -> "gif")
4760
// returns 1 if an extension was found (and written to s), 0 otherwise
4761
0
int give_mimext(char *s, size_t ssize, const char *st) {
4762
0
  int ok = 0;
4763
0
  const char *ext;
4764
4765
0
  st = hts_effective_mime(st); /* no declared type: derive an html ext */
4766
0
  s[0] = '\0';
4767
0
  ext = hts_mime_lookup(hts_mime, 0, st);
4768
0
  if (ext == NULL)
4769
0
    ext = hts_mime_lookup(hts_mime_modern, 0, st);
4770
0
  if (ext != NULL) {
4771
0
    strlcpybuff(s, ext, ssize);
4772
0
    ok = 1;
4773
0
  }
4774
  // wrap "x" mimetypes, such as:
4775
  // application/x-mp3
4776
  // or
4777
  // application/mp3
4778
0
  if (!ok) {
4779
0
    int p;
4780
0
    const char *a = NULL;
4781
4782
0
    if ((p = strfield(st, "application/x-")))
4783
0
      a = st + p;
4784
0
    else if ((p = strfield(st, "application/")))
4785
0
      a = st + p;
4786
0
    if (a) {
4787
0
      if ((int) strlen(a) >= 1) {
4788
0
        if ((int) strlen(a) <= 4) {
4789
0
          strlcpybuff(s, a, ssize);
4790
0
          ok = 1;
4791
0
        }
4792
0
      }
4793
0
    }
4794
0
  }
4795
0
  return ok;
4796
0
}
4797
4798
// extension connue?..
4799
//  0 : non
4800
//  1 : oui
4801
//  2 : html
4802
0
HTSEXT_API int is_knowntype(httrackp * opt, const char *fil) {
4803
0
  char catbuff[CATBUFF_SIZE];
4804
0
  const char *ext;
4805
0
  int j = 0;
4806
4807
0
  if (!fil)
4808
0
    return 0;
4809
0
  ext = get_ext(catbuff, sizeof(catbuff), fil);
4810
0
  while(strnotempty(hts_mime[j][1])) {
4811
0
    if (strfield2(hts_mime[j][1], ext)) {
4812
0
      if (is_html_mime_type(hts_mime[j][0]))
4813
0
        return 2;
4814
0
      else
4815
0
        return 1;
4816
0
    }
4817
0
    j++;
4818
0
  }
4819
4820
  // Known by user?
4821
0
  return (is_userknowntype(opt, fil));
4822
0
}
4823
4824
// known type?..
4825
//  0 : no
4826
//  1 : yes
4827
//  2 : html
4828
// setdefs : set mime buffer:
4829
//   file=(char*) "asp=text/html\nphp=text/html\n"
4830
0
HTSEXT_API int is_userknowntype(httrackp * opt, const char *fil) {
4831
0
  char BIGSTK mime[1024];
4832
4833
0
  if (!fil)
4834
0
    return 0;
4835
0
  if (!strnotempty(fil))
4836
0
    return 0;
4837
0
  mime[0] = '\0';
4838
0
  get_userhttptype(opt, mime, sizeof(mime), fil);
4839
0
  if (!strnotempty(mime))
4840
0
    return 0;
4841
0
  else if (is_html_mime_type(mime))
4842
0
    return 2;
4843
0
  else
4844
0
    return 1;
4845
0
}
4846
4847
// page dynamique?
4848
// is_dyntype(get_ext("foo.asp"))
4849
0
HTSEXT_API hts_boolean is_dyntype(const char *fil) {
4850
0
  int j = 0;
4851
4852
0
  if (!fil)
4853
0
    return 0;
4854
0
  if (!strnotempty(fil))
4855
0
    return 0;
4856
0
  while(strnotempty(hts_ext_dynamic[j])) {
4857
0
    if (strfield2(hts_ext_dynamic[j], fil)) {
4858
0
      return 1;
4859
0
    }
4860
0
    j++;
4861
0
  }
4862
0
  return 0;
4863
0
}
4864
4865
// types critiques qui ne doivent pas être changés car renvoyés par des serveurs qui ne
4866
// connaissent pas le type
4867
0
hts_boolean may_unknown(httrackp *opt, const char *st) {
4868
0
  int j = 0;
4869
4870
  // types média
4871
0
  if (may_be_hypertext_mime(opt, st, "")) {
4872
0
    return 1;
4873
0
  }
4874
0
  while(strnotempty(hts_mime_keep[j])) {
4875
0
    if (strfield2(hts_mime_keep[j], st)) {      // trouvé
4876
0
      return 1;
4877
0
    }
4878
0
    j++;
4879
0
  }
4880
0
  return 0;
4881
0
}
4882
4883
/* returns 1 if the mime/filename seems to be bogus because of badly recognized multiple extension
4884
  ; such as "application/x-wais-source" for "httrack-3.42-1.el5.src.rpm"
4885
  reported by Hippy Dave 08/2008 (3.43) */
4886
0
int may_bogus_multiple(httrackp * opt, const char *mime, const char *filename) {
4887
0
  int j;
4888
4889
0
  for(j = 0; strnotempty(hts_mime_bogus_multiple[j]); j++) {
4890
0
    if (strfield2(hts_mime_bogus_multiple[j], mime)) {  /* found mime type in suspicious list */
4891
0
      char ext[64];
4892
4893
0
      if (give_mimext(ext, sizeof(ext),
4894
0
                      mime)) { /* we have an extension for that */
4895
0
        const size_t ext_size = strlen(ext);
4896
0
        const char *file = strrchr(filename, '/');      /* fetch terminal filename */
4897
4898
0
        if (file != NULL) {
4899
0
          int i;
4900
4901
0
          for(i = 0; file[i] != 0; i++) {
4902
0
            if (i > 0 && file[i - 1] == '.'
4903
0
                && strncasecmp(&file[i], ext, ext_size) == 0
4904
0
                && (file[i + ext_size] == 0 || file[i + ext_size] == '.'
4905
0
                    || file[i + ext_size] == '?')) {
4906
0
              return 1;         /* is ambiguous */
4907
0
            }
4908
0
          }
4909
0
        }
4910
0
      }
4911
0
      return 0;
4912
0
    }
4913
0
  }
4914
0
  return 0;
4915
0
}
4916
4917
/* filename extension should not be changed because potentially bogus ; replaces may_unknown() (3.43) */
4918
0
int may_unknown2(httrackp * opt, const char *mime, const char *filename) {
4919
0
  int ret = may_unknown(opt, mime);
4920
4921
0
  if (ret == 0) {
4922
0
    ret = may_bogus_multiple(opt, mime, filename);
4923
0
  }
4924
0
  return ret;
4925
0
}
4926
4927
// -- Utils fichiers
4928
4929
// pretty print for i/o
4930
0
void fprintfio(FILE * fp, const char *buff, const char *prefix) {
4931
0
  char nl = 1;
4932
4933
0
  while(*buff) {
4934
0
    switch (*buff) {
4935
0
    case 13:
4936
0
      break;
4937
0
    case 10:
4938
0
      fprintf(fp, "\r\n");
4939
0
      nl = 1;
4940
0
      break;
4941
0
    default:
4942
0
      if (nl)
4943
0
        fprintf(fp, "%s", prefix);
4944
0
      nl = 0;
4945
0
      fputc(*buff, fp);
4946
0
    }
4947
0
    buff++;
4948
0
  }
4949
0
}
4950
4951
/* Le fichier existe-t-il? (ou est-il accessible?) */
4952
/* Note: NOT utf-8 */
4953
/* Note: preserve errno */
4954
0
int fexist(const char *s) {
4955
0
  char catbuff[CATBUFF_SIZE];
4956
0
  const int err = errno;
4957
0
  struct stat st;
4958
4959
0
  memset(&st, 0, sizeof(st));
4960
0
  if (stat(fconv(catbuff, sizeof(catbuff), s), &st) == 0) {
4961
0
    if (S_ISREG(st.st_mode)) {
4962
0
      return 1;
4963
0
    } else {
4964
0
      return 0;
4965
0
    }
4966
0
  }
4967
0
  errno = err;
4968
0
  return 0;
4969
0
}
4970
4971
/* Le fichier existe-t-il? (ou est-il accessible?) */
4972
/* Note: utf-8 */
4973
/* Note: preserve errno */
4974
0
int fexist_utf8(const char *s) {
4975
0
  char catbuff[CATBUFF_SIZE];
4976
0
  const int err = errno;
4977
0
  STRUCT_STAT st;
4978
4979
0
  memset(&st, 0, sizeof(st));
4980
0
  if (STAT(fconv(catbuff, sizeof(catbuff), s), &st) == 0) {
4981
0
    if (S_ISREG(st.st_mode)) {
4982
0
      return 1;
4983
0
    } else {
4984
0
      return 0;
4985
0
    }
4986
0
  }
4987
0
  errno = err;
4988
0
  return 0;
4989
0
}
4990
4991
/* File size, -1 if absent */
4992
/* Note: NOT utf-8 */
4993
0
LLint fsize(const char *s) {
4994
0
  STRUCT_STAT st;
4995
4996
0
  if (!strnotempty(s)) // empty name: error
4997
0
    return -1;
4998
  /* _stat64, not stat(): the latter is 32-bit on MSVC */
4999
#ifdef _WIN32
5000
  if (_stat64(s, &st) == 0 && S_ISREG(st.st_mode)) {
5001
#else
5002
0
  if (stat(s, &st) == 0 && S_ISREG(st.st_mode)) {
5003
0
#endif
5004
0
    return st.st_size;
5005
0
  } else {
5006
0
    return -1;
5007
0
  }
5008
0
}
5009
5010
/* File size, -1 if absent */
5011
/* Note: utf-8 */
5012
0
LLint fsize_utf8(const char *s) {
5013
0
  STRUCT_STAT st;
5014
5015
0
  if (!strnotempty(s)) // empty name: error
5016
0
    return -1;
5017
0
  if (STAT(s, &st) == 0 && S_ISREG(st.st_mode)) {
5018
0
    return st.st_size;
5019
0
  } else {
5020
0
    return -1;
5021
0
  }
5022
0
}
5023
5024
/* fseeko/ftello, never fseek/ftell: ftell returns long, which cannot carry an
5025
   offset past 2GB on a 32-bit platform and fails with EOVERFLOW there. */
5026
0
LLint fpsize(FILE *fp) {
5027
0
  LLint oldpos, size;
5028
5029
0
  if (!fp)
5030
0
    return -1;
5031
0
  oldpos = ftello(fp);
5032
0
  fseeko(fp, 0, SEEK_END);
5033
0
  size = ftello(fp);
5034
0
  fseeko(fp, oldpos, SEEK_SET);
5035
0
  return size;
5036
0
}
5037
5038
/* root dir, with ending / */
5039
typedef struct {
5040
  char path[1024 + 4];
5041
  int init;
5042
} hts_rootdir_strc;
5043
0
HTSEXT_API const char *hts_rootdir(char *file) {
5044
0
  static hts_rootdir_strc strc = { "", 0 };
5045
0
  if (file) {
5046
0
    if (!strc.init) {
5047
0
      strc.path[0] = '\0';
5048
0
      strc.init = 1;
5049
0
      if (strnotempty(file)) {
5050
0
        const size_t file_len = strlen(file);
5051
0
        char *a;
5052
5053
0
        assertf(file_len < sizeof(strc.path));
5054
0
        strcpybuff(strc.path, file);
5055
0
        while((a = strrchr(strc.path, '\\')))
5056
0
          *a = '/';
5057
0
        if ((a = strrchr(strc.path, '/'))) {
5058
0
          *(a + 1) = '\0';
5059
0
        } else
5060
0
          strc.path[0] = '\0';
5061
0
      }
5062
0
      if (!strnotempty(strc.path)) {
5063
0
        if (getcwd(strc.path, sizeof(strc.path)) == NULL)
5064
0
          strc.path[0] = '\0';
5065
0
        else
5066
0
          strcatbuff(strc.path, "/");
5067
0
      }
5068
0
    }
5069
0
    return NULL;
5070
0
  } else if (strc.init)
5071
0
    return strc.path;
5072
0
  else
5073
0
    return "";
5074
0
}
5075
5076
HTSEXT_API hts_stat_struct HTS_STAT;
5077
5078
//
5079
// return  number of downloadable bytes, depending on rate limiter
5080
// see engine_stats() routine, too
5081
// this routine works quite well for big files and regular ones, but apparently the rate limiter has
5082
// some problems with very small files (rate too high)
5083
0
LLint check_downloadable_bytes(int rate) {
5084
0
  if (rate > 0) {
5085
0
    TStamp time_now;
5086
0
    TStamp elapsed_useconds;
5087
0
    LLint bytes_transferred_during_period;
5088
0
    LLint left;
5089
5090
    // get the older timer
5091
0
    int id_timer = (HTS_STAT.istat_idlasttimer + 1) % 2;
5092
5093
0
    time_now = mtime_local();
5094
0
    elapsed_useconds = time_now - HTS_STAT.istat_timestart[id_timer];
5095
0
    bytes_transferred_during_period =
5096
0
      (HTS_STAT.HTS_TOTAL_RECV - HTS_STAT.istat_bytes[id_timer]);
5097
5098
0
    left = ((rate * elapsed_useconds) / 1000) - bytes_transferred_during_period;
5099
0
    if (left <= 0)
5100
0
      left = 0;
5101
5102
0
    return left;
5103
0
  } else
5104
0
    return TAILLE_BUFFER;
5105
0
}
5106
5107
// Lecture dans buff de size octets au maximum en utilisant la socket r (structure htsblk)
5108
// returns: 
5109
// >0 : data received
5110
// == 0 : not yet data
5111
// <0: error or no data: READ_ERROR, READ_EOF or READ_TIMEOUT
5112
0
int hts_read(htsblk * r, char *buff, int size) {
5113
0
  int retour;
5114
5115
0
  if (r->is_file) {
5116
#if HTS_WIDE_DEBUG
5117
    DEBUG_W("read(%p, %d, %d)\n" _(void *)buff _(int) size _(int) r->fp);
5118
#endif
5119
0
    if (r->fp) {
5120
0
      retour = (int) fread(buff, 1, size, r->fp);
5121
0
      if (retour == 0)  // can happen with directories (!)
5122
0
        retour = READ_ERROR;
5123
0
    } else
5124
0
      retour = READ_ERROR;
5125
0
  } else {
5126
#if HTS_WIDE_DEBUG
5127
    DEBUG_W("recv(%d, %p, %d)\n" _(int) r->soc _(void *)buff _(int) size);
5128
5129
    if (r->soc == INVALID_SOCKET)
5130
      printf("!!WIDE_DEBUG ERROR, soc==INVALID hts_read\n");
5131
#endif
5132
#if HTS_USEOPENSSL
5133
    if (r->ssl) {
5134
      retour = SSL_read(r->ssl_con, buff, size);
5135
      if (retour <= 0) {
5136
        int err_code = SSL_get_error(r->ssl_con, retour);
5137
5138
        if ((err_code == SSL_ERROR_WANT_READ)
5139
            || (err_code == SSL_ERROR_WANT_WRITE)
5140
          ) {
5141
          retour = 0;           /* no data yet (ssl cache) */
5142
        } else if (err_code == SSL_ERROR_ZERO_RETURN) {
5143
          retour = READ_EOF;    /* completed */
5144
        } else {
5145
          retour = READ_ERROR;  /* eof or error */
5146
        }
5147
      }
5148
    } else {
5149
#endif
5150
0
      retour = recv(r->soc, buff, size, 0);
5151
0
      if (retour == 0) {
5152
0
        retour = READ_EOF;
5153
0
      } else if (retour < 0) {
5154
0
        retour = READ_ERROR;
5155
0
      }
5156
0
    }
5157
0
    if (retour > 0)             // compter flux entrant
5158
0
      HTS_STAT.HTS_TOTAL_RECV += retour;
5159
#if HTS_USEOPENSSL
5160
  }
5161
#endif
5162
#if HTS_WIDE_DEBUG
5163
  DEBUG_W("recv/read done (%d bytes)\n" _(int) retour);
5164
#endif
5165
0
  return retour;
5166
0
}
5167
5168
// -- Gestion cache DNS --
5169
// 'RX98
5170
5171
// Free a DNS cache record (coucal value handler).
5172
0
static void hts_cache_value_free(coucal_opaque arg, coucal_value value) {
5173
0
  void *record = value.ptr;
5174
5175
0
  (void) arg;
5176
0
  freet(record);
5177
0
}
5178
5179
// opt's DNS cache hashtable, created on first use. Records (t_dnscache*) are
5180
// owned by the table and freed by hts_cache_value_free on coucal_delete.
5181
0
coucal hts_cache(httrackp *opt) {
5182
0
  assertf(opt != NULL);
5183
0
  if (opt->state.dns_cache == NULL) {
5184
0
    coucal cache = coucal_new(0);
5185
5186
0
    coucal_set_name(cache, "dns_cache");
5187
0
    coucal_value_set_value_handler(cache, hts_cache_value_free, NULL);
5188
0
    opt->state.dns_cache = cache;
5189
0
  }
5190
0
  assertf(opt->state.dns_cache != NULL);
5191
0
  return opt->state.dns_cache;
5192
0
}
5193
5194
/* First lifetime of a negative DNS answer. Unlocked because only the
5195
   self-test writes it, from the single thread that then resolves. */
5196
static int hts_dns_negative_ttl_ms = HTS_DNS_NEGATIVE_TTL_MS;
5197
5198
0
void hts_dns_set_negative_ttl_ms(int ms) {
5199
0
  hts_dns_negative_ttl_ms = ms > 0 ? ms : 1; /* the doubling needs a floor */
5200
0
}
5201
5202
0
int hts_dns_negative_failures(httrackp *opt, const char *host) {
5203
0
  void *ptr;
5204
0
  int failures = 0;
5205
5206
0
  hts_mutexlock(&opt->state.lock);
5207
0
  if (coucal_read_pvoid(hts_cache(opt), host, &ptr))
5208
0
    failures = ((const t_dnscache *) ptr)->failures;
5209
0
  hts_mutexrelease(&opt->state.lock);
5210
0
  return failures;
5211
0
}
5212
5213
0
void hts_dns_test_move_clock(httrackp *opt, const char *host, TStamp ms) {
5214
0
  void *ptr;
5215
5216
0
  hts_mutexlock(&opt->state.lock);
5217
0
  if (coucal_read_pvoid(hts_cache(opt), host, &ptr)) {
5218
0
    t_dnscache *const record = (t_dnscache *) ptr;
5219
5220
0
    if (record->expiry != 0) { /* a positive record carries no stamps */
5221
0
      record->expiry -= ms;
5222
0
      record->stored -= ms;
5223
0
    }
5224
0
  }
5225
0
  hts_mutexrelease(&opt->state.lock);
5226
0
}
5227
5228
/* Doubles per consecutive failure up to the ceiling: an outage is retried
5229
   soon, a host that is simply gone costs a handful of lookups per crawl. */
5230
0
int hts_dns_negative_wait_ms(int failures) {
5231
0
  int wait = hts_dns_negative_ttl_ms;
5232
0
  int i;
5233
5234
0
  for (i = 1; i < failures && wait < HTS_DNS_NEGATIVE_TTL_MAX_MS; i++)
5235
0
    wait *= 2;
5236
0
  return wait < HTS_DNS_NEGATIVE_TTL_MAX_MS ? wait
5237
0
                                            : HTS_DNS_NEGATIVE_TTL_MAX_MS;
5238
0
}
5239
5240
// MUST BE LOCKED (coucal is not internally serialized vs FTP/web threads)
5241
// Look up iadr in the DNS cache, filling out[0..min(count,max)-1].
5242
// Returns: -1 not tested, or a negative answer past its expiry; 0
5243
// negative-cached (not in DNS); >0 address count.
5244
static int hts_ghbn_all(coucal cache, const char *const iadr,
5245
0
                        SOCaddr *const out, const int max) {
5246
0
  void *ptr;
5247
5248
0
  assertf(out != NULL);
5249
0
  assertf(iadr != NULL);
5250
0
  if (*iadr == '\0') {
5251
0
    return -1;
5252
0
  }
5253
0
  if (coucal_read_pvoid(cache, iadr, &ptr)) { // ok trouvé
5254
0
    const t_dnscache *const record = (const t_dnscache *) ptr;
5255
0
    int i;
5256
5257
0
    assertf(record->host_count <= HTS_MAXADDRNUM);
5258
    /* An outage must not blacklist a host for the rest of the crawl, and the
5259
       stamps are wall-clock: a clock now behind the store time expires the
5260
       record too, or an NTP step delays the retry by the size of the step. */
5261
0
    if (record->expiry != 0) {
5262
0
      const TStamp now = mtime_local();
5263
5264
0
      if (now >= record->expiry || now < record->stored) {
5265
0
        return -1;
5266
0
      }
5267
0
    }
5268
0
    for (i = 0; i < record->host_count && i < max; i++) {
5269
0
      assertf(record->host_length[i] <= sizeof(record->host_addr[i]));
5270
0
      SOCaddr_copyaddr2(out[i], record->host_addr[i], record->host_length[i]);
5271
0
    }
5272
0
    return record->host_count;
5273
0
  }
5274
0
  return -1;
5275
0
}
5276
5277
#if HTS_INET6 != 0
5278
/* Active resolver backend; defaults to the libc resolver. The self-test
5279
   reroutes it to script DNS answers in-process (see
5280
   hts_dns_set_resolver_backend). */
5281
static const hts_resolver_backend hts_resolver_libc = {getaddrinfo,
5282
                                                       freeaddrinfo};
5283
static const hts_resolver_backend *hts_resolver = &hts_resolver_libc;
5284
5285
0
void hts_dns_set_resolver_backend(const hts_resolver_backend *backend) {
5286
0
  hts_resolver = (backend != NULL) ? backend : &hts_resolver_libc;
5287
0
}
5288
5289
/* Debug/test hook: HTTRACK_DEBUG_RESOLVE="host:ip[,ip...]" pins the resolution
5290
   of `host` to the listed addresses (curl --resolve style), so the connect
5291
   fallback can be exercised deterministically (a dead address first, a live one
5292
   next). Any other host resolves normally. Below: an addrinfo backend that owns
5293
   its chain (its own freeaddrinfo), so a synthesized and a delegated result
5294
   free the same way. */
5295
5296
/* Deep-copy a libc addrinfo chain into our own allocations. */
5297
0
static struct addrinfo *resolver_dup_chain(const struct addrinfo *src) {
5298
0
  struct addrinfo *head = NULL, *tail = NULL;
5299
5300
0
  for (; src != NULL; src = src->ai_next) {
5301
0
    struct addrinfo *const ai = calloct(1, sizeof(*ai));
5302
5303
0
    ai->ai_family = src->ai_family;
5304
0
    ai->ai_socktype = src->ai_socktype;
5305
0
    ai->ai_protocol = src->ai_protocol;
5306
0
    ai->ai_addrlen = src->ai_addrlen;
5307
0
    ai->ai_addr = malloct(src->ai_addrlen);
5308
0
    memcpy(ai->ai_addr, src->ai_addr, src->ai_addrlen);
5309
0
    if (head == NULL)
5310
0
      head = ai;
5311
0
    else
5312
0
      tail->ai_next = ai;
5313
0
    tail = ai;
5314
0
  }
5315
0
  return head;
5316
0
}
5317
5318
/* Build one addrinfo node from an IPv4/IPv6 literal, or NULL if it does not
5319
   parse or is filtered out by want_family (AF_INET/AF_INET6/PF_UNSPEC). */
5320
0
static struct addrinfo *resolver_make_ai(const char *ip, int want_family) {
5321
0
  struct addrinfo *ai;
5322
5323
0
  if (strchr(ip, ':') != NULL) { // IPv6 literal
5324
0
    struct sockaddr_in6 sa6;
5325
5326
0
    if (want_family != PF_UNSPEC && want_family != AF_INET6)
5327
0
      return NULL;
5328
0
    memset(&sa6, 0, sizeof(sa6));
5329
0
    if (inet_pton(AF_INET6, ip, &sa6.sin6_addr) != 1)
5330
0
      return NULL;
5331
0
    sa6.sin6_family = AF_INET6;
5332
0
    ai = calloct(1, sizeof(*ai));
5333
0
    ai->ai_family = AF_INET6;
5334
0
    ai->ai_addrlen = sizeof(sa6);
5335
0
    ai->ai_addr = malloct(sizeof(sa6));
5336
0
    memcpy(ai->ai_addr, &sa6, sizeof(sa6));
5337
0
  } else { // IPv4 literal
5338
0
    struct sockaddr_in sa;
5339
5340
0
    if (want_family != PF_UNSPEC && want_family != AF_INET)
5341
0
      return NULL;
5342
0
    memset(&sa, 0, sizeof(sa));
5343
0
    if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
5344
0
      return NULL;
5345
0
    sa.sin_family = AF_INET;
5346
0
    ai = calloct(1, sizeof(*ai));
5347
0
    ai->ai_family = AF_INET;
5348
0
    ai->ai_addrlen = sizeof(sa);
5349
0
    ai->ai_addr = malloct(sizeof(sa));
5350
0
    memcpy(ai->ai_addr, &sa, sizeof(sa));
5351
0
  }
5352
0
  return ai;
5353
0
}
5354
5355
0
static void HTS_RESOLVER_CALL override_freeaddrinfo(struct addrinfo *res) {
5356
0
  while (res != NULL) {
5357
0
    struct addrinfo *const next = res->ai_next;
5358
5359
0
    freet(res->ai_addr);
5360
0
    freet(res);
5361
0
    res = next;
5362
0
  }
5363
0
}
5364
5365
static int HTS_RESOLVER_CALL override_getaddrinfo(const char *node,
5366
                                                  const char *service,
5367
                                                  const struct addrinfo *hints,
5368
0
                                                  struct addrinfo **res) {
5369
0
  const char *const spec = getenv("HTTRACK_DEBUG_RESOLVE");
5370
0
  const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
5371
0
  const char *colon;
5372
5373
0
  *res = NULL;
5374
0
  if (spec != NULL && node != NULL && (colon = strchr(spec, ':')) != NULL &&
5375
0
      (size_t) (colon - spec) == strlen(node) &&
5376
0
      strncmp(spec, node, colon - spec) == 0) {
5377
0
    struct addrinfo *head = NULL, *tail = NULL;
5378
0
    char buf[256];
5379
0
    char *p;
5380
5381
0
    buf[0] = '\0';
5382
0
    strncatbuff(buf, colon + 1, sizeof(buf) - 1);
5383
0
    for (p = strtok(buf, ","); p != NULL; p = strtok(NULL, ",")) {
5384
0
      struct addrinfo *const ai = resolver_make_ai(p, want);
5385
5386
0
      if (ai != NULL) {
5387
0
        if (head == NULL)
5388
0
          head = ai;
5389
0
        else
5390
0
          tail->ai_next = ai;
5391
0
        tail = ai;
5392
0
      }
5393
0
    }
5394
0
    if (head == NULL)
5395
0
      return EAI_NONAME;
5396
0
    *res = head;
5397
0
    return 0;
5398
0
  }
5399
5400
  /* not overridden: delegate to libc, copying into our owned format */
5401
0
  {
5402
0
    struct addrinfo *sys = NULL;
5403
0
    int gerr = getaddrinfo(node, service, hints, &sys);
5404
5405
0
    if (gerr != 0)
5406
0
      return gerr;
5407
0
    *res = resolver_dup_chain(sys);
5408
0
    freeaddrinfo(sys);
5409
0
    return 0;
5410
0
  }
5411
0
}
5412
5413
static const hts_resolver_backend hts_resolver_override = {
5414
    override_getaddrinfo, override_freeaddrinfo};
5415
5416
/* Install the env override once, unless a backend was already set (self-test).
5417
 */
5418
0
static void hts_resolver_check_env(void) {
5419
0
  static int checked = 0;
5420
5421
0
  if (!checked) {
5422
0
    checked = 1;
5423
0
    if (hts_resolver == &hts_resolver_libc &&
5424
0
        getenv("HTTRACK_DEBUG_RESOLVE") != NULL) {
5425
0
      hts_resolver = &hts_resolver_override;
5426
0
    }
5427
0
  }
5428
0
}
5429
#endif
5430
5431
// Resolve hostname into up to max addresses (resolver/RFC 6724 order), no
5432
// cache. Returns the count copied into out[0..count-1]; 0 = does not resolve.
5433
/* On a zero count, *permanent tells the two failures apart: the resolver
5434
   saying the name does not exist, or it being unable to answer at all. */
5435
static int hts_dns_resolve_nocache_list_(const char *const hostname,
5436
                                         SOCaddr *const out, const int max,
5437
                                         const char **error,
5438
0
                                         hts_boolean *permanent) {
5439
0
  int count = 0;
5440
0
  hts_boolean is_permanent = HTS_FALSE;
5441
5442
#if HTS_INET6==0
5443
  /* IPv4 resolver */
5444
  struct hostent *const hp = gethostbyname(hostname);
5445
5446
  if (hp == NULL && h_errno == HOST_NOT_FOUND)
5447
    is_permanent = HTS_TRUE;
5448
  if (hp != NULL) {
5449
    char **h;
5450
5451
    for (h = hp->h_addr_list; count < max && h != NULL && *h != NULL; h++) {
5452
      SOCaddr_clear(out[count]);
5453
      SOCaddr_copyaddr2(out[count], *h, hp->h_length);
5454
      if (SOCaddr_is_valid(out[count]))
5455
        count++;
5456
    }
5457
  }
5458
#else
5459
  /* IPv6 resolver */
5460
0
  struct addrinfo *res = NULL, *cur;
5461
0
  struct addrinfo hints;
5462
0
  int gerr;
5463
5464
0
  hts_resolver_check_env();
5465
0
  memset(&hints, 0, sizeof(hints));
5466
0
  if (IPV6_resolver == 1) // V4 only (for bogus V6 entries)
5467
0
    hints.ai_family = PF_INET;
5468
0
  else if (IPV6_resolver == 2) // V6 only (for testing V6 only)
5469
0
    hints.ai_family = PF_INET6;
5470
0
  else // V4 + V6
5471
0
    hints.ai_family = PF_UNSPEC;
5472
0
  hints.ai_socktype = SOCK_STREAM;
5473
0
  hints.ai_protocol = IPPROTO_TCP;
5474
0
  if ((gerr = hts_resolver->getaddrinfo(hostname, NULL, &hints, &res)) == 0) {
5475
0
    for (cur = res; cur != NULL && count < max; cur = cur->ai_next) {
5476
0
      if (cur->ai_addr != NULL && cur->ai_addrlen != 0) {
5477
0
        SOCaddr_clear(out[count]);
5478
0
        SOCaddr_copyaddr2(out[count], cur->ai_addr, cur->ai_addrlen);
5479
0
        if (SOCaddr_is_valid(out[count]))
5480
0
          count++;
5481
0
      }
5482
0
    }
5483
0
  } else {
5484
0
    if (error != NULL)
5485
0
      *error = gai_strerror(gerr);
5486
    /* EAI_NONAME answers about the name; every other code says the resolver
5487
       could not answer at all. */
5488
0
    if (gerr == EAI_NONAME)
5489
0
      is_permanent = HTS_TRUE;
5490
0
  }
5491
0
  if (res) {
5492
0
    hts_resolver->freeaddrinfo(res);
5493
0
  }
5494
0
#endif
5495
5496
0
  if (permanent != NULL)
5497
0
    *permanent = is_permanent;
5498
0
  return count;
5499
0
}
5500
5501
// Strip [] around a literal IPv6 ([3ffe:b80:1234:1::1]) the resolver won't
5502
// take, then resolve into a list. Returns the count.
5503
static int hts_dns_resolve_nocache_list(const char *const hostname,
5504
                                        SOCaddr *const out, const int max,
5505
                                        const char **error,
5506
0
                                        hts_boolean *permanent) {
5507
0
  if (!strnotempty(hostname) || max <= 0) {
5508
0
    if (permanent != NULL)
5509
0
      *permanent = HTS_FALSE;
5510
0
    return 0;
5511
0
  }
5512
0
  if ((hostname[0] == '[') && (hts_lastchar(hostname) == ']')) {
5513
0
    size_t size = strlen(hostname);
5514
0
    char *copy = malloct(size + 1);
5515
0
    int count;
5516
5517
0
    assertf(copy != NULL);
5518
0
    copy[0] = '\0';
5519
0
    strncat(copy, hostname + 1, size - 2);
5520
0
    count = hts_dns_resolve_nocache_list_(copy, out, max, error, permanent);
5521
0
    freet(copy);
5522
0
    return count;
5523
0
  } else {
5524
0
    return hts_dns_resolve_nocache_list_(hostname, out, max, error, permanent);
5525
0
  }
5526
0
}
5527
5528
HTSEXT_API SOCaddr *hts_dns_resolve_nocache2(const char *const hostname,
5529
                                             SOCaddr *const addr,
5530
0
                                             const char **error) {
5531
0
  SOCaddr_clear(*addr);
5532
0
  if (hts_dns_resolve_nocache_list(hostname, addr, 1, error, NULL) > 0) {
5533
0
    return SOCaddr_is_valid(*addr) ? addr : NULL;
5534
0
  }
5535
0
  return NULL;
5536
0
}
5537
5538
0
HTSEXT_API SOCaddr* hts_dns_resolve_nocache(const char *const hostname, SOCaddr *const addr) {
5539
0
  return hts_dns_resolve_nocache2(hostname, addr, NULL);
5540
0
}
5541
5542
0
HTSEXT_API int check_hostname_dns(const char *const hostname) {
5543
0
  SOCaddr buffer;
5544
0
  return hts_dns_resolve_nocache(hostname, &buffer) != NULL;
5545
0
}
5546
5547
/* A resolve in flight. Refcounted: a timed-out resolve is abandoned, not
5548
   cancelled, so the last of caller/worker to leave frees the job. */
5549
typedef struct dns_resolve_job {
5550
  htsmutex lock;
5551
  int refcount;
5552
  hts_boolean done;
5553
  char *hostname;
5554
  SOCaddr addr[HTS_MAXADDRNUM];
5555
  int count;
5556
  const char *error;
5557
  hts_boolean permanent; /* a zero count that was about the name itself */
5558
} dns_resolve_job;
5559
5560
/* Copy the first min(count, max) addresses of src into dest. */
5561
0
static void dns_copy_addrs(SOCaddr *dest, SOCaddr *src, int count, int max) {
5562
0
  int i;
5563
5564
0
  for (i = 0; i < count && i < max; i++)
5565
0
    SOCaddr_copy_SOCaddr(dest[i], src[i]);
5566
0
}
5567
5568
0
static void dns_job_release(dns_resolve_job *job) {
5569
0
  hts_boolean last;
5570
5571
0
  hts_mutexlock(&job->lock);
5572
0
  last = (--job->refcount == 0) ? HTS_TRUE : HTS_FALSE;
5573
0
  hts_mutexrelease(&job->lock);
5574
0
  if (last) {
5575
0
    hts_mutexfree(&job->lock);
5576
0
    freet(job->hostname);
5577
0
    freet(job);
5578
0
  }
5579
0
}
5580
5581
/* Outlives a timed-out resolve, so it writes only the job: never opt (freed
5582
   before the thread wait at exit) nor the DNS cache. */
5583
0
static void dns_resolve_thread(void *arg) {
5584
0
  dns_resolve_job *const job = (dns_resolve_job *) arg;
5585
0
  SOCaddr resolved[HTS_MAXADDRNUM];
5586
0
  const char *error = NULL;
5587
0
  hts_boolean permanent = HTS_FALSE;
5588
0
  const int count = hts_dns_resolve_nocache_list(
5589
0
      job->hostname, resolved, HTS_MAXADDRNUM, &error, &permanent);
5590
5591
0
  hts_mutexlock(&job->lock);
5592
0
  dns_copy_addrs(job->addr, resolved, count, HTS_MAXADDRNUM);
5593
0
  job->count = count;
5594
0
  job->error = error;
5595
0
  job->permanent = permanent;
5596
0
  job->done = HTS_TRUE; /* published last: gates the caller's read of addr[] */
5597
0
  hts_mutexrelease(&job->lock);
5598
0
  dns_job_release(job);
5599
0
}
5600
5601
/* Resolve hostname on a worker thread, giving up after timeout seconds or as
5602
   soon as *cancel (optional) is raised. Returns the address count, or -1 on
5603
   timeout -- distinct from 0 ("does not resolve"), which is a real answer and
5604
   gets negative-cached. */
5605
static int hts_dns_resolve_nocache_list_bounded(
5606
    const char *hostname, SOCaddr *const out, const int max, const int timeout,
5607
    const volatile hts_boolean *cancel, const char **error,
5608
0
    hts_boolean *permanent) {
5609
0
  dns_resolve_job *job;
5610
0
  TStamp deadline;
5611
0
  int count = -1;
5612
0
  int poll_ms = 1;
5613
5614
  /* no bound asked for (--timeout 0), and nobody to cut it short either */
5615
0
  if (timeout <= 0 && cancel == NULL)
5616
0
    return hts_dns_resolve_nocache_list(hostname, out, max, error, permanent);
5617
5618
0
  job = calloct(1, sizeof(*job));
5619
0
  assertf(job != NULL);
5620
0
  hts_mutexinit(&job->lock);
5621
0
  job->hostname = strdupt(hostname);
5622
0
  job->refcount = 2; /* this caller + the worker */
5623
0
  if (hts_newthread(dns_resolve_thread, job) != 0) {
5624
0
    job->refcount = 1; /* no worker: fall back to resolving inline */
5625
0
    dns_job_release(job);
5626
0
    return hts_dns_resolve_nocache_list(hostname, out, max, error, permanent);
5627
0
  }
5628
5629
  /* timeout <= 0 got here only for a cancellable resolve: no deadline then */
5630
0
  deadline = timeout > 0 ? mtime_local() + (TStamp) timeout * 1000 : 0;
5631
0
  for (;;) {
5632
0
    hts_boolean done;
5633
5634
0
    hts_mutexlock(&job->lock);
5635
0
    done = job->done;
5636
0
    if (done) {
5637
0
      count = job->count;
5638
0
      dns_copy_addrs(out, job->addr, count, max);
5639
0
      if (error != NULL)
5640
0
        *error = job->error;
5641
0
      if (permanent != NULL)
5642
0
        *permanent = job->permanent;
5643
0
    }
5644
0
    hts_mutexrelease(&job->lock);
5645
0
    if (done || (cancel != NULL && *cancel) ||
5646
0
        (deadline != 0 && mtime_local() >= deadline))
5647
0
      break;
5648
0
    Sleep(poll_ms);
5649
0
    if (poll_ms < 50) /* short first polls keep a fast resolve fast */
5650
0
      poll_ms *= 2;
5651
0
  }
5652
0
  dns_job_release(job);
5653
0
  return count;
5654
0
}
5655
5656
int hts_dns_resolve_all(httrackp *opt, const char *iadr, SOCaddr *out, int max,
5657
0
                        const char **error) {
5658
0
  assertf(opt != NULL);
5659
  /* the mirror's stop flag cancels: a resolve behind a black hole otherwise
5660
     holds a stopped crawl for the whole --timeout (#1073) */
5661
0
  return hts_dns_resolve_all_bounded(opt, iadr, out, max, opt->timeout,
5662
0
                                     &opt->state.stop, error);
5663
0
}
5664
5665
int hts_dns_resolve_all_bounded(httrackp *opt, const char *iadr, SOCaddr *out,
5666
                                int max, int timeout,
5667
                                const volatile hts_boolean *cancel,
5668
0
                                const char **error) {
5669
0
  char BIGSTK host[HTS_URLMAXSIZE * 2];
5670
0
  SOCaddr resolved[HTS_MAXADDRNUM];
5671
0
  hts_boolean permanent = HTS_FALSE;
5672
0
  coucal cache;
5673
0
  int count, i;
5674
5675
0
  assertf(opt != NULL);
5676
0
  assertf(out != NULL);
5677
0
  if (!strnotempty(iadr) || max <= 0) {
5678
0
    return 0;
5679
0
  }
5680
5681
  /* cache key and resolver input: identification and any ":port" stripped */
5682
0
  strcpybuff(host, jump_identification_const(iadr));
5683
0
  {
5684
0
    char *a;
5685
5686
0
    if ((a = jump_toport(host)))
5687
0
      *a = '\0';
5688
0
  }
5689
5690
0
  hts_mutexlock(&opt->state.lock);
5691
0
  cache = hts_cache(opt);
5692
0
#if HTS_INET6 != 0
5693
0
  hts_resolver_check_env(); /* settle the backend before a worker reads it */
5694
0
#endif
5695
0
  count = hts_ghbn_all(cache, host, out, max);
5696
0
  hts_mutexrelease(&opt->state.lock);
5697
0
  if (count >= 0) { // cache hit (0 == negative-cached)
5698
0
    return count;
5699
0
  }
5700
5701
#if DEBUGDNS
5702
  printf("resolving (not cached) %s\n", host);
5703
#endif
5704
5705
  /* Resolve with no lock held: getaddrinfo can block for a long time, and
5706
     state.lock also gates the stop request (#606). */
5707
0
  count = hts_dns_resolve_nocache_list_bounded(
5708
0
      host, resolved, HTS_MAXADDRNUM, timeout, cancel, error, &permanent);
5709
5710
#if HTS_WIDE_DEBUG
5711
  DEBUG_W("gethostbyname done\n");
5712
#endif
5713
5714
0
  if (count < 0) { /* timed out: no answer to cache, and none to report */
5715
0
    if (error != NULL)
5716
0
      *error = "host name resolution timed out";
5717
0
    return 0;
5718
0
  }
5719
5720
0
  hts_mutexlock(&opt->state.lock);
5721
0
  { /* store the full list (coucal owns the record and dups the host key; a
5722
       concurrent resolve of the same host replaces, and frees, this one) */
5723
0
    t_dnscache *const record = malloct(sizeof(t_dnscache));
5724
0
    void *previous;
5725
0
    const int failures =
5726
0
        (count == 0 && coucal_read_pvoid(cache, host, &previous))
5727
0
            ? ((const t_dnscache *) previous)->failures + 1
5728
0
            : 1;
5729
5730
0
    if (record != NULL) {
5731
0
      memset(record, 0, sizeof(*record));
5732
0
      record->host_count = count;
5733
0
      record->failures = count == 0 ? failures : 0;
5734
      /* The resolver saying the name does not exist stands for the crawl;
5735
         it being unable to answer is only as good as the network it asked. */
5736
0
      if (count == 0 && !permanent) {
5737
0
        record->stored = mtime_local();
5738
0
        record->expiry = record->stored + hts_dns_negative_wait_ms(failures);
5739
0
      }
5740
0
      for (i = 0; i < count; i++) {
5741
0
        record->host_length[i] = SOCaddr_size(resolved[i]);
5742
0
        assertf(record->host_length[i] <= sizeof(record->host_addr[i]));
5743
0
        memcpy(record->host_addr[i], &SOCaddr_sockaddr(resolved[i]),
5744
0
               record->host_length[i]);
5745
0
      }
5746
0
      coucal_add_pvoid(cache, host, record);
5747
0
    }
5748
0
  }
5749
0
  hts_mutexrelease(&opt->state.lock);
5750
5751
  /* copy result to caller (cache store may have failed; result still valid) */
5752
0
  dns_copy_addrs(out, resolved, count, max);
5753
0
  return count;
5754
0
}
5755
5756
SOCaddr *hts_dns_resolve2(httrackp *opt, const char *_iadr, SOCaddr *const addr,
5757
0
                          const char **error) {
5758
0
  SOCaddr_clear(*addr);
5759
0
  if (hts_dns_resolve_all(opt, _iadr, addr, 1, error) > 0) {
5760
0
    return SOCaddr_is_valid(*addr) ? addr : NULL;
5761
0
  }
5762
0
  return NULL;
5763
0
}
5764
5765
// --- Tracage des mallocs() ---
5766
#ifdef HTS_TRACE_MALLOC
5767
#define htsLocker(A, N) do {} while(0)
5768
static mlink trmalloc = { NULL, 0, 0, NULL };
5769
5770
static int trmalloc_id = 0;
5771
static htsmutex *mallocMutex = NULL;
5772
5773
static void hts_meminit(void) {}
5774
void *hts_malloc(size_t len) {
5775
  void *adr;
5776
5777
  hts_meminit();
5778
  htsLocker(mallocMutex, 1);
5779
  assertf(len > 0);
5780
  adr = hts_xmalloc(len, 0);
5781
  htsLocker(mallocMutex, 0);
5782
  return adr;
5783
}
5784
void *hts_calloc(size_t len, size_t len2) {
5785
  void *adr;
5786
5787
  hts_meminit();
5788
  assertf(len > 0);
5789
  assertf(len2 > 0);
5790
  htsLocker(mallocMutex, 1);
5791
  adr = hts_xmalloc(len, len2);
5792
  htsLocker(mallocMutex, 0);
5793
  memset(adr, 0, len * len2);
5794
  return adr;
5795
}
5796
void *hts_strdup(char *str) {
5797
  size_t size = str ? strlen(str) : 0;
5798
  char *adr = (char *) hts_malloc(size + 1);
5799
5800
  assertf(adr != NULL);
5801
  strcpy(adr, str ? str : "");
5802
  return adr;
5803
}
5804
void *hts_xmalloc(size_t len, size_t len2) {
5805
  mlink *lnk = (mlink *) calloc(1, sizeof(mlink));
5806
5807
  assertf(lnk != NULL);
5808
  assertf(len > 0);
5809
  assertf(len2 >= 0);
5810
  if (lnk) {
5811
    void *r = NULL;
5812
    int size, bsize = sizeof(t_htsboundary);
5813
5814
    if (len2)
5815
      size = len * len2;
5816
    else
5817
      size = len;
5818
    size += ((bsize - (size % bsize)) % bsize); /* check alignement */
5819
    r = malloc(size + bsize * 2);
5820
    assertf(r != NULL);
5821
    if (r) {
5822
      *((t_htsboundary *) ((char *) r))
5823
        = *((t_htsboundary *) ((char *) r + size + bsize))
5824
        = htsboundary;
5825
      ((char *) r) += bsize;    /* boundary */
5826
      lnk->adr = r;
5827
      lnk->len = size;
5828
      lnk->id = trmalloc_id++;
5829
      lnk->next = trmalloc.next;
5830
      trmalloc.next = lnk;
5831
      return r;
5832
    } else {
5833
      free(lnk);
5834
    }
5835
  }
5836
  return NULL;
5837
}
5838
void hts_free(void *adr) {
5839
  mlink *lnk = &trmalloc;
5840
  int bsize = sizeof(t_htsboundary);
5841
5842
  assertf(adr != NULL);
5843
  if (!adr) {
5844
    return;
5845
  }
5846
  htsLocker(mallocMutex, 1);
5847
  while(lnk->next != NULL) {
5848
    if (lnk->next->adr == adr) {
5849
      mlink *blk_free = lnk->next;
5850
5851
      assertf(blk_free->id != -1);
5852
      assertf(*((t_htsboundary *) ((char *) adr - bsize)) == htsboundary);
5853
      assertf(*((t_htsboundary *) ((char *) adr + blk_free->len)) ==
5854
              htsboundary);
5855
      lnk->next = lnk->next->next;
5856
      free((void *) blk_free);
5857
      free((char *) adr - bsize);
5858
      htsLocker(mallocMutex, 0);
5859
      return;
5860
    }
5861
    lnk = lnk->next;
5862
    assertf(lnk->next != NULL);
5863
  }
5864
  free(adr);
5865
  htsLocker(mallocMutex, 0);
5866
}
5867
void *hts_realloc(void *adr, size_t len) {
5868
  int bsize = sizeof(t_htsboundary);
5869
5870
  len += ((bsize - (len % bsize)) % bsize);     /* check alignement */
5871
  if (adr != NULL) {
5872
    mlink *lnk = &trmalloc;
5873
5874
    htsLocker(mallocMutex, 1);
5875
    while(lnk->next != NULL) {
5876
      if (lnk->next->adr == adr) {
5877
        {
5878
          mlink *blk_free = lnk->next;
5879
5880
          assertf(blk_free->id != -1);
5881
          assertf(*((t_htsboundary *) ((char *) adr - bsize)) == htsboundary);
5882
          assertf(*((t_htsboundary *) ((char *) adr + blk_free->len)) ==
5883
                  htsboundary);
5884
        }
5885
        adr = realloc((char *) adr - bsize, len + bsize * 2);
5886
        assertf(adr != NULL);
5887
        lnk->next->adr = (char *) adr + bsize;
5888
        lnk->next->len = len;
5889
        *((t_htsboundary *) ((char *) adr))
5890
          = *((t_htsboundary *) ((char *) adr + len + bsize))
5891
          = htsboundary;
5892
        htsLocker(mallocMutex, 0);
5893
        return (char *) adr + bsize;
5894
      }
5895
      lnk = lnk->next;
5896
      assertf(lnk->next != NULL);
5897
    }
5898
    htsLocker(mallocMutex, 0);
5899
  }
5900
  return hts_malloc(len);
5901
}
5902
mlink *hts_find(char *adr) {
5903
  char *stkframe = (char *) &stkframe;
5904
  mlink *lnk = &trmalloc;
5905
  int bsize = sizeof(t_htsboundary);
5906
5907
  assertf(adr != NULL);
5908
  if (!adr) {
5909
    return NULL;
5910
  }
5911
  htsLocker(mallocMutex, 1);
5912
  while(lnk->next != NULL) {
5913
    if (adr >= lnk->next->adr && adr <= lnk->next->adr + lnk->next->len) {      /* found */
5914
      htsLocker(mallocMutex, 0);
5915
      return lnk->next;
5916
    }
5917
    lnk = lnk->next;
5918
  }
5919
  htsLocker(mallocMutex, 0);
5920
  {
5921
    int depl = (int) (adr - stkframe);
5922
5923
    if (depl < 0)
5924
      depl = -depl;
5925
    return NULL;
5926
  }
5927
}
5928
5929
// check the malloct() and calloct() trace stack
5930
void hts_freeall(void) {
5931
  int bsize = sizeof(t_htsboundary);
5932
5933
  while(trmalloc.next) {
5934
#if MEMDEBUG
5935
    printf("* block %d\t not released: at %d\t (%d\t bytes)\n",
5936
           trmalloc.next->id, trmalloc.next->adr, trmalloc.next->len);
5937
#endif
5938
    if (trmalloc.next->id != -1) {
5939
      free((char *) trmalloc.next->adr - bsize);
5940
    }
5941
  }
5942
}
5943
#endif
5944
5945
// -- divers //
5946
5947
// cut path and project name
5948
// patch also initial path
5949
void cut_path(char *fullpath, char *path, size_t path_size, char *pname,
5950
0
              size_t pname_size) {
5951
0
  path[0] = pname[0] = '\0';
5952
0
  if (strnotempty(fullpath)) {
5953
0
    if (!hts_striplastchar(fullpath, '/'))
5954
0
      hts_striplastchar(fullpath, '\\');
5955
0
    if (strlen(fullpath) > 1) {
5956
0
      char *a;
5957
5958
0
      while((a = strchr(fullpath, '\\')))
5959
0
        *a = '/';               // remplacer par /
5960
0
      a = fullpath + strlen(fullpath) - 2;
5961
0
      while((*a != '/') && (a > fullpath))
5962
0
        a--;
5963
0
      if (*a == '/')
5964
0
        a++;
5965
0
      strlcpybuff(pname, a, pname_size);
5966
0
      strlncatbuff(path, fullpath, path_size, (size_t) (a - fullpath));
5967
0
    }
5968
0
  }
5969
0
}
5970
5971
// -- Gestion protocole ftp --
5972
5973
#ifdef _WIN32
5974
int ftp_available(void) {
5975
  return 1;
5976
}
5977
#else
5978
0
int ftp_available(void) {
5979
0
  return 1; // ok!
5980
0
}
5981
#endif
5982
5983
static void hts_debug_log_print(const char *format, ...) HTS_PRINTF_FUN(1, 2);
5984
5985
static int hts_dgb_init = 0;
5986
static FILE *hts_dgb_init_fp = NULL;
5987
0
HTSEXT_API void hts_debug(int level) {
5988
0
  hts_dgb_init = level;
5989
0
  if (hts_dgb_init > 0) {
5990
0
    hts_debug_log_print("hts_debug() called");
5991
0
  }
5992
0
}
5993
5994
0
static FILE *hts_dgb_(void) {
5995
0
  if (hts_dgb_init_fp == NULL) {
5996
0
    if ((hts_dgb_init & 0x80) == 0) {
5997
0
      hts_dgb_init_fp = stderr;
5998
0
    } else {
5999
0
      hts_dgb_init_fp = FOPEN("hts-debug.txt", "wb");
6000
0
      if (hts_dgb_init_fp != NULL) {
6001
0
        fprintf(hts_dgb_init_fp, "* Creating file\r\n");
6002
0
      }
6003
0
    }
6004
0
  }
6005
0
  return hts_dgb_init_fp;
6006
0
}
6007
6008
0
static void hts_debug_log_print(const char *format, ...) {
6009
0
  if (hts_dgb_init > 0) {
6010
0
    const int error = errno;
6011
0
    FILE *const fp = hts_dgb_();
6012
0
    va_list args;
6013
6014
0
    assertf(format != NULL);
6015
0
    va_start(args, format);
6016
0
    (void) vfprintf(fp, format, args);
6017
0
    va_end(args);
6018
0
    fputs("\n", fp);
6019
0
    fflush(fp);
6020
0
    errno = error;
6021
0
  }
6022
0
}
6023
6024
0
HTSEXT_API const char* hts_version(void) {
6025
0
  return HTTRACK_VERSIONID;
6026
0
}
6027
6028
0
static int ssl_vulnerable(const char *version) {
6029
0
#ifdef _WIN32
6030
0
  static const char *const match = "OpenSSL 1.0.1";
6031
0
  const size_t match_len = strlen(match);
6032
0
  if (version != NULL && strncmp(version, match, match_len) == 0) {
6033
0
    // CVE-2014-0160
6034
0
    // "OpenSSL 1.0.1g 7 Apr 2014"
6035
0
    const char minor = version[match_len];
6036
0
    return minor == ' ' || ( minor >= 'a' && minor <= 'f' );
6037
0
  }
6038
0
#endif
6039
0
  return 0;
6040
0
}
6041
6042
/* user abort callback */
6043
htsErrorCallback htsCallbackErr = NULL;
6044
6045
0
HTSEXT_API void hts_set_error_callback(htsErrorCallback handler) {
6046
0
  htsCallbackErr = handler;
6047
0
}
6048
6049
0
HTSEXT_API htsErrorCallback hts_get_error_callback(void) {
6050
0
  return htsCallbackErr;
6051
0
}
6052
6053
0
static void default_coucal_asserthandler(void *arg, const char* exp, const char* file, int line) {
6054
0
  abortf_(exp, file, line);
6055
0
}
6056
6057
0
static int get_loglevel_from_coucal(coucal_loglevel level) {
6058
0
  switch(level) {
6059
0
  case coucal_log_critical:
6060
0
    return LOG_PANIC;
6061
0
    break;
6062
0
  case coucal_log_warning:
6063
0
    return LOG_WARNING;
6064
0
    break;
6065
0
  case coucal_log_info:
6066
0
    return LOG_INFO;
6067
0
    break;
6068
0
  case coucal_log_debug:
6069
0
    return LOG_DEBUG;
6070
0
    break;
6071
0
  case coucal_log_trace:
6072
0
    return LOG_TRACE;
6073
0
    break;
6074
0
  default:
6075
0
    return LOG_ERROR;
6076
0
    break;
6077
0
  }
6078
0
}
6079
6080
/* log to default console */
6081
static HTS_PRINTF_FUN(3, 0) void default_coucal_loghandler(
6082
0
    void *arg, coucal_loglevel level, const char *format, va_list args) {
6083
6084
  /* informational chatter (hashtable stats on delete, etc.) only when
6085
     debugging; keep warnings and critical errors always visible. */
6086
0
  if (level > coucal_log_warning && hts_dgb_init <= 0) {
6087
0
    return;
6088
0
  }
6089
0
  if (level <= coucal_log_warning) {
6090
0
    fprintf(stderr, "** warning: ");
6091
0
  }
6092
0
  vfprintf(stderr, format, args);
6093
0
  fprintf(stderr, "\n");
6094
0
}
6095
6096
/* log to project log */
6097
static HTS_PRINTF_FUN(3, 0) void htsopt_coucal_loghandler(void *arg,
6098
                                                          coucal_loglevel level,
6099
                                                          const char *format,
6100
0
                                                          va_list args) {
6101
0
  httrackp *const opt = (httrackp*) arg;
6102
0
  if (opt != NULL && opt->log != NULL) {
6103
0
    hts_log_vprint(opt, get_loglevel_from_coucal(level), 
6104
0
      format, args);
6105
0
  } else {
6106
0
    default_coucal_loghandler(NULL, level, format, args);
6107
0
  }
6108
0
}
6109
6110
/* attach hashtable logger to project log */
6111
0
void hts_set_hash_handler(coucal hashtable, httrackp *opt) {
6112
  /* Init hashtable default assertion handler. */
6113
0
  coucal_set_assert_handler(hashtable,
6114
0
    htsopt_coucal_loghandler, 
6115
0
    default_coucal_asserthandler,
6116
0
    opt);
6117
0
}
6118
6119
static int hts_init_ok = 0;
6120
0
HTSEXT_API int hts_init(void) {
6121
0
  const char *dbg_env;
6122
6123
  /* */
6124
0
  if (hts_init_ok)
6125
0
    return 1;
6126
0
  hts_init_ok = 1;
6127
6128
  /* enable debugging ? */
6129
0
  dbg_env = getenv("HTS_LOG");
6130
0
  if (dbg_env != NULL && *dbg_env != 0) {
6131
0
    int level = 0;
6132
6133
0
    if (sscanf(dbg_env, "%d", &level) == 1) {
6134
0
      hts_debug(level);
6135
0
    }
6136
0
  }
6137
6138
0
  hts_debug_log_print("entering hts_init()");   /* debug */
6139
6140
  /* Init hashtable default assertion handler. */
6141
0
  coucal_set_global_assert_handler(default_coucal_loghandler, 
6142
0
    default_coucal_asserthandler);
6143
6144
  /* Init threads (lazy init) */
6145
0
  htsthread_init();
6146
6147
  /* Ensure external modules are loaded */
6148
0
  hts_debug_log_print("calling htspe_init()");  /* debug */
6149
0
  htspe_init();                 /* module load (lazy) */
6150
6151
  /* MD5 Auto-test */
6152
0
  {
6153
0
    char digest[32 + 2];
6154
0
    const char *atest = "MD5 Checksum Autotest";
6155
6156
0
    digest[0] = '\0';
6157
0
    domd5mem(atest, strlen(atest), digest, 1);  /* a42ec44369da07ace5ec1d660ba4a69a */
6158
0
    if (strcmp(digest, "a42ec44369da07ace5ec1d660ba4a69a") != 0) {
6159
0
      int fatal_broken_md5 = 0;
6160
6161
0
      assertf(fatal_broken_md5);
6162
0
    }
6163
0
  }
6164
6165
0
  hts_debug_log_print("initializing SSL");      /* debug */
6166
#if HTS_USEOPENSSL
6167
  /*
6168
     Initialize the OpensSSL library
6169
   */
6170
  if (!openssl_ctx) {
6171
    const char *version;
6172
    const SSL_METHOD *method;
6173
6174
    /* OpenSSL >= 1.1.0 / LibreSSL >= 2.7.0 auto-init and provide the generic
6175
       methods. The legacy init and SSLv23/SSLeay calls (deprecated since 1.1.0,
6176
       likely gone in 4.0) are kept only for older OpenSSL. */
6177
#if OPENSSL_VERSION_NUMBER < 0x10100000L \
6178
  || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
6179
    SSL_load_error_strings();
6180
    SSL_library_init();
6181
    version = SSLeay_version(SSLEAY_VERSION);
6182
    method = SSLv23_client_method();
6183
#else
6184
    version = OpenSSL_version(OPENSSL_VERSION);
6185
    method = TLS_client_method();
6186
#endif
6187
6188
    // Check CVE-2014-0160.
6189
    if (ssl_vulnerable(version)) {
6190
      fprintf(stderr, "OpenSSL version == '%s'\n", version);
6191
      abortLog("unable to initialize TLS: OpenSSL version seems vulnerable to heartbleed bug (CVE-2014-0160)");
6192
      assertf("OpenSSL version seems vulnerable to heartbleed bug (CVE-2014-0160)" == NULL);
6193
    }
6194
6195
    openssl_ctx = SSL_CTX_new(method);
6196
    if (!openssl_ctx) {
6197
      fprintf(stderr, "fatal: unable to initialize TLS: SSL_CTX_new()\n");
6198
      abortLog("unable to initialize TLS: SSL_CTX_new()");
6199
      assertf("unable to initialize TLS" == NULL);
6200
    }
6201
    /* Pin a TLS floor (no SSLv3/TLS1.0/1.1); no cert verify, by design. */
6202
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
6203
    SSL_CTX_set_min_proto_version(openssl_ctx, TLS1_2_VERSION);
6204
#else
6205
    SSL_CTX_set_options(openssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
6206
                                         SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
6207
#endif
6208
  }
6209
#endif
6210
6211
0
  hts_debug_log_print("ending hts_init()");     /* debug */
6212
0
  return 1;
6213
0
}
6214
6215
/* will not free thread env. */
6216
0
HTSEXT_API int hts_uninit(void) {
6217
  /* hts_init() is a lazy initializer, with limited a allocation (one or two mutexes) ;
6218
     we won't free anything here as the .h semantic was never being very clear */
6219
0
  return 1;
6220
0
}
6221
6222
0
HTSEXT_API int hts_uninit_module(void) {
6223
0
  if (!hts_init_ok)
6224
0
    return 1;
6225
0
  htsthread_uninit();
6226
0
  htspe_uninit();
6227
0
  hts_init_ok = 0;
6228
0
  return 1;
6229
0
}
6230
6231
// legacy. do not use
6232
HTSEXT_API hts_boolean hts_log(httrackp *opt, const char *prefix,
6233
0
                               const char *msg) {
6234
0
  if (opt->log != NULL) {
6235
0
    fspc(opt, opt->log, prefix);
6236
0
    fprintf(opt->log, "%s" LF, msg);
6237
0
    return 0;
6238
0
  }
6239
0
  return 1;                     /* Error */
6240
0
}
6241
6242
static void (*hts_log_print_callback)(httrackp * opt, int type, const char *format, va_list args) = NULL;
6243
6244
HTSEXT_API void hts_set_log_vprint_callback(void (*callback)(httrackp * opt,
6245
0
                                            int type, const char *format, va_list args)) {
6246
0
  hts_log_print_callback = callback;
6247
0
}
6248
6249
0
HTSEXT_API void hts_log_vprint(httrackp * opt, int type, const char *format, va_list args) {
6250
0
  assertf(format != NULL);
6251
0
  if (hts_log_print_callback != NULL) {
6252
0
    va_list args_copy;
6253
0
    va_copy(args_copy, args);
6254
    /* the copy, so the vfprintf() below still has an unread list */
6255
0
    hts_log_print_callback(opt, type, format, args_copy);
6256
0
    va_end(args_copy);
6257
0
  }
6258
0
  if (opt != NULL && opt->log != NULL) {
6259
0
    const int save_errno = errno;
6260
0
    const char *s_type = "unknown";
6261
0
    const int level = type & 0xff;
6262
6263
    // Check log level
6264
0
    if (opt->debug < level) {
6265
0
      return;
6266
0
    }
6267
6268
0
    switch (level) {
6269
0
    case LOG_TRACE:
6270
0
      s_type = "trace";
6271
0
      break;
6272
0
    case LOG_DEBUG:
6273
0
      s_type = "debug";
6274
0
      break;
6275
0
    case LOG_INFO:
6276
0
    case LOG_NOTICE: /* not a warning: counted in the footer's messages */
6277
0
      s_type = "info";
6278
0
      break;
6279
0
    case LOG_WARNING:
6280
0
      s_type = "warning";
6281
0
      break;
6282
0
    case LOG_ERROR:
6283
0
      s_type = "error";
6284
0
      break;
6285
0
    case LOG_PANIC:
6286
0
      s_type = "panic";
6287
0
      break;
6288
0
    }
6289
0
    fspc(opt, opt->log, s_type);
6290
0
    (void) vfprintf(opt->log, format, args);
6291
0
    if ((type & LOG_ERRNO) != 0) {
6292
0
      fprintf(opt->log, ": %s", strerror(save_errno));
6293
0
    }
6294
0
    fputs(LF, opt->log);
6295
0
    if (opt->flush) {
6296
0
      fflush(opt->log);
6297
0
    }
6298
0
    errno = save_errno;
6299
0
  }
6300
0
}
6301
6302
0
HTSEXT_API void hts_log_print(httrackp * opt, int type, const char *format, ...) {
6303
0
  va_list args;
6304
0
  assertf(format != NULL);
6305
0
  va_start(args, format);
6306
0
  hts_log_vprint(opt, type, format, args);
6307
0
  va_end(args);
6308
0
}
6309
6310
0
HTSEXT_API void set_wrappers(httrackp * opt) {  // LEGACY
6311
0
}
6312
6313
HTSEXT_API int plug_wrapper(httrackp * opt, const char *moduleName,
6314
0
                            const char *argv) {
6315
0
  void *handle = openFunctionLib(moduleName);
6316
6317
0
  if (handle != NULL) {
6318
0
    t_hts_plug plug = (t_hts_plug) getFunctionPtr(handle, "hts_plug");
6319
0
    t_hts_unplug unplug = (t_hts_unplug) getFunctionPtr(handle, "hts_unplug");
6320
6321
0
    if (plug != NULL) {
6322
0
      int ret = plug(opt, argv);
6323
6324
0
      if (hts_dgb_init > 0 && opt->log != NULL) {
6325
0
        hts_debug_log_print("plugged module '%s' (return code=%d)", moduleName,
6326
0
                            ret);
6327
0
      }
6328
0
      if (ret == 1) {           /* Success! */
6329
0
        opt->libHandles.handles =
6330
0
          (htslibhandle *) realloct(opt->libHandles.handles,
6331
0
                                    (opt->libHandles.count +
6332
0
                                     1) * sizeof(htslibhandle));
6333
0
        opt->libHandles.handles[opt->libHandles.count].handle = handle;
6334
0
        opt->libHandles.handles[opt->libHandles.count].moduleName =
6335
0
          strdupt(moduleName);
6336
0
        opt->libHandles.count++;
6337
0
        return 1;
6338
0
      } else {
6339
0
        hts_debug_log_print
6340
0
          ("* note: error while running entry point 'hts_plug' in %s",
6341
0
           moduleName);
6342
0
        if (unplug)
6343
0
          unplug(opt);
6344
0
      }
6345
0
    } else {
6346
0
      int last_errno = errno;
6347
6348
0
      hts_debug_log_print("* note: can't find entry point 'hts_plug' in %s: %s",
6349
0
                          moduleName, strerror(last_errno));
6350
0
    }
6351
0
    closeFunctionLib(handle);
6352
0
    return 0;
6353
0
  } else {
6354
0
    int last_errno = errno;
6355
6356
0
    hts_debug_log_print("* note: can't load %s: %s", moduleName,
6357
0
                        strerror(last_errno));
6358
0
  }
6359
0
  return -1;
6360
0
}
6361
6362
0
static void unplug_wrappers(httrackp * opt) {
6363
0
  if (opt->libHandles.handles != NULL) {
6364
0
    int i;
6365
6366
0
    for(i = 0; i < opt->libHandles.count; i++) {
6367
0
      if (opt->libHandles.handles[i].handle != NULL) {
6368
        /* hts_unplug(), the dll exit point (finalizer) */
6369
0
        t_hts_unplug unplug =
6370
0
          (t_hts_unplug) getFunctionPtr(opt->libHandles.handles[i].handle,
6371
0
                                        "hts_unplug");
6372
0
        if (unplug != NULL)
6373
0
          unplug(opt);
6374
0
        closeFunctionLib(opt->libHandles.handles[i].handle);
6375
0
        opt->libHandles.handles[i].handle = NULL;
6376
0
      }
6377
0
      if (opt->libHandles.handles[i].moduleName != NULL) {
6378
0
        freet(opt->libHandles.handles[i].moduleName);
6379
0
        opt->libHandles.handles[i].moduleName = NULL;
6380
0
      }
6381
0
    }
6382
0
    freet(opt->libHandles.handles);
6383
0
    opt->libHandles.handles = NULL;
6384
0
    opt->libHandles.count = 0;
6385
0
  }
6386
0
}
6387
6388
0
int multipleStringMatch(const char *s, const char *match) {
6389
0
  int ret = 0;
6390
0
  String name = STRING_EMPTY;
6391
6392
0
  if (match == NULL || s == NULL || *s == 0)
6393
0
    return 0;
6394
0
  for(; *match != 0; match++) {
6395
0
    StringClear(name);
6396
0
    for(; *match != 0 && *match != '\n'; match++) {
6397
0
      StringAddchar(name, *match);
6398
0
    }
6399
0
    if (StringLength(name) > 0 && strstr(s, StringBuff(name)) != NULL) {
6400
0
      ret = 1;
6401
0
      break;
6402
0
    }
6403
0
  }
6404
0
  StringFree(name);
6405
0
  return ret;
6406
0
}
6407
6408
0
HTSEXT_API httrackp *hts_create_opt(void) {
6409
0
  static const char *defaultModules[] = {"httrack-plugin", NULL};
6410
0
  httrackp *opt = malloc(sizeof(httrackp));
6411
6412
  /* default options */
6413
0
  memset(opt, 0, sizeof(httrackp));
6414
0
  opt->size_httrackp = sizeof(httrackp);
6415
6416
  /* mutexes */
6417
0
  hts_mutexinit(&opt->state.lock);
6418
6419
  /* custom wrappers */
6420
0
  opt->libHandles.count = 0;
6421
6422
  /* default settings */
6423
6424
0
  opt->wizard = HTS_WIZARD_AUTO; // wizard automatique
6425
0
  opt->quiet = HTS_FALSE;
6426
  //
6427
0
  opt->travel = HTS_TRAVEL_SAME_ADDRESS; // même adresse
6428
0
  opt->depth = 9999;            // mirror total par défaut
6429
0
  opt->extdepth = 0;            // mais pas à l'extérieur
6430
0
  opt->seeker = HTS_SEEKER_DOWN;       // down
6431
0
  opt->urlmode = HTS_URLMODE_RELATIVE; // relatif par défaut
6432
0
  opt->no_type_change = HTS_FALSE;
6433
0
  opt->debug = LOG_NOTICE;      // small log
6434
0
  opt->getmode = HTS_GETMODE_HTML | HTS_GETMODE_NONHTML;
6435
0
  opt->maxsite = -1;            // taille max site (aucune)
6436
0
  opt->maxfile_nonhtml = -1;    // taille max fichier non html
6437
0
  opt->maxfile_html = -1;       // idem pour html
6438
0
  opt->maxsoc = 4;              // nbre socket max
6439
0
  opt->fragment = -1;           // pas de fragmentation
6440
0
  opt->nearlink = HTS_FALSE;
6441
0
  opt->makeindex = HTS_TRUE;
6442
0
  opt->kindex = HTS_FALSE;
6443
0
  opt->delete_old = HTS_TRUE;
6444
0
  opt->background_on_suspend = HTS_TRUE;
6445
0
  opt->makestat = HTS_FALSE;
6446
0
  opt->maketrack = HTS_FALSE;
6447
0
  opt->timeout = 120;           // timeout par défaut (2 minutes)
6448
0
  opt->cache = HTS_CACHE_PRIORITY; // cache prioritaire
6449
0
  opt->shell = HTS_FALSE;
6450
0
  opt->proxy.active = 0;        // pas de proxy
6451
0
  opt->user_agent_send = HTS_TRUE;
6452
0
  StringCopy(opt->user_agent, HTS_DEFAULT_USER_AGENT);
6453
0
  StringCopy(opt->referer, "");
6454
0
  StringCopy(opt->from, "");
6455
0
  opt->savename_83 = HTS_SAVENAME_83_LONG; // long names by default
6456
0
  opt->savename_type = 0;       // avec structure originale
6457
0
  opt->savename_delayed =
6458
0
      HTS_SAVENAME_DELAYED_HARD; // always delay the type check (default)
6459
0
  opt->delayed_cached = HTS_TRUE;
6460
0
  opt->mimehtml = HTS_FALSE;
6461
0
  opt->parsejava = HTSPARSE_DEFAULT;    // parser classes
6462
0
  opt->hostcontrol = 0;         // PAS de control host pour timeout et traffic jammer
6463
0
  opt->retry = 2;               // 2 retry par défaut
6464
0
  opt->errpage = HTS_TRUE;
6465
  // d'erreur (404 etc.)
6466
0
  opt->check_type = HTS_TRUE;
6467
  // considéré comme html
6468
0
  opt->all_in_cache = HTS_FALSE;
6469
0
  opt->robots = HTS_ROBOTS_ALWAYS; // traiter les robots.txt
6470
0
  opt->external = HTS_FALSE;
6471
0
  opt->passprivacy = HTS_FALSE;
6472
0
  opt->includequery = HTS_TRUE;
6473
0
  opt->mirror_first_page = HTS_FALSE;
6474
0
  opt->accept_cookie = HTS_TRUE;
6475
0
  opt->cookie = NULL;
6476
0
  opt->http10 = HTS_FALSE;
6477
0
  opt->nokeepalive = HTS_FALSE;
6478
0
  opt->nocompression = HTS_FALSE;
6479
0
  opt->tolerant = HTS_FALSE;
6480
0
  opt->parseall = HTS_TRUE;
6481
0
  opt->parsedebug = HTS_FALSE;
6482
0
  opt->norecatch = HTS_FALSE;
6483
0
  opt->verbosedisplay = HTS_VERBOSE_NONE; // no text animation
6484
0
  opt->sizehack = HTS_FALSE;
6485
0
  opt->urlhack = HTS_TRUE;
6486
0
  opt->no_www_dedup = HTS_FALSE;
6487
0
  opt->no_slash_dedup = HTS_FALSE;
6488
0
  opt->no_query_dedup = HTS_FALSE;
6489
0
  StringCopy(opt->footer, HTS_DEFAULT_FOOTER);
6490
0
  StringCopy(opt->strip_query, "");
6491
0
  StringCopy(opt->host_alias, "");
6492
0
  StringCopy(opt->cookies_file, "");
6493
0
  StringCopy(opt->warc_file, "");
6494
0
  StringCopy(opt->sitemap_url, "");
6495
0
  opt->warc_max_size = 0; /* no rotation unless --warc-max-size sets it */
6496
0
  opt->changes = HTS_FALSE;
6497
0
  opt->changes_state = NULL;
6498
0
  opt->single_file = HTS_FALSE;
6499
0
  opt->single_file_max_size = SINGLEFILE_DEFAULT_MAX_SIZE;
6500
0
  opt->singlefile_state = NULL;
6501
0
  opt->links_unqueued = HTS_FALSE;
6502
0
  StringCopy(opt->why_url, "");
6503
0
  opt->pause_min_ms = 0;
6504
0
  opt->pause_max_ms = 0;
6505
0
  opt->ftp_proxy = HTS_TRUE;
6506
0
  opt->convert_utf8 = HTS_TRUE;
6507
0
  StringCopy(opt->filelist, "");
6508
0
  StringCopy(opt->lang_iso, "en, *");
6509
0
  StringCopy(opt->accept,
6510
0
    "text/html,image/png,image/jpeg,image/pjpeg,image/x-xbitmap,image/svg+xml,image/gif;q=0.9,*/*;q=0.1");
6511
0
  StringCopy(opt->headers, "");
6512
0
  StringCopy(opt->mimedefs, "\n");      // aucun filtre mime (\n IMPORTANT)
6513
0
  StringClear(opt->mod_blacklist);
6514
  //
6515
0
  opt->log = stdout;
6516
0
  opt->errlog = stderr;
6517
0
  opt->flush = HTS_TRUE;
6518
0
  opt->keyboard = HTS_FALSE;
6519
  //
6520
0
  StringCopy(opt->path_html, "");
6521
0
  StringCopy(opt->path_html_utf8, "");
6522
0
  StringCopy(opt->path_log, "");
6523
0
  StringCopy(opt->path_bin, "");
6524
  //
6525
0
  opt->maxlink = 100000;        // 100,000 liens max par défaut
6526
0
  opt->maxfilter = 200;         // 200 filtres max par défaut
6527
0
  opt->maxcache = 1048576 * 32; // a peu près 32Mo en cache max -- OPTION NON
6528
                                // PARAMETRABLE POUR L'INSTANT --
6529
0
  opt->maxtime = -1;            // temps max en secondes
6530
0
  opt->maxrate = 100000;        // taux maxi
6531
0
  opt->maxconn = 5.0;           // nombre connexions/s
6532
0
  opt->waittime = -1;           // wait until.. hh*3600+mm*60+ss
6533
  //
6534
0
  opt->exec = "";
6535
0
  opt->is_update = HTS_FALSE;
6536
0
  opt->dir_topindex = HTS_FALSE;
6537
  //
6538
0
  opt->bypass_limits = HTS_FALSE;
6539
0
  opt->state.stop = 0;          // stopper
6540
0
  opt->state.exit_xh = 0;       // abort
6541
  //
6542
0
  opt->state.is_ended = 0;
6543
6544
  /* Alocated buffers */
6545
6546
0
  opt->callbacks_fun =
6547
0
    (t_hts_htmlcheck_callbacks *) malloct(sizeof(t_hts_htmlcheck_callbacks));
6548
0
  memset(opt->callbacks_fun, 0, sizeof(t_hts_htmlcheck_callbacks));
6549
6550
  /* Preload callbacks : java and flash parser, and the automatic user-defined callback */
6551
6552
0
  {
6553
0
    int i;
6554
6555
0
    for(i = 0; defaultModules[i] != NULL; i++) {
6556
0
      int ret = plug_wrapper(opt, defaultModules[i], defaultModules[i]);
6557
6558
0
      if (ret == 0) {           /* Module aborted initialization */
6559
        /* Ignored. */
6560
0
      }
6561
0
    }
6562
0
  }
6563
6564
0
  return opt;
6565
0
}
6566
6567
0
HTSEXT_API size_t hts_sizeof_opt(void) {
6568
0
  return sizeof(httrackp);
6569
0
}
6570
6571
0
HTSEXT_API void hts_free_opt(httrackp * opt) {
6572
0
  if (opt != NULL) {
6573
    /* An FTP worker reads opt for its whole run, and not every caller drains
6574
       its threads the way httrack.c does before getting here. */
6575
0
    ftp_stop_workers();
6576
6577
    /* Alocated callbacks */
6578
6579
0
    if (opt->callbacks_fun != NULL) {
6580
0
      int i;
6581
0
      t_hts_htmlcheck_callbacks_item *items =
6582
0
        (t_hts_htmlcheck_callbacks_item *) opt->callbacks_fun;
6583
0
      const int size =
6584
0
        (int) sizeof(t_hts_htmlcheck_callbacks) /
6585
0
        sizeof(t_hts_htmlcheck_callbacks_item);
6586
0
      assertf(sizeof(t_hts_htmlcheck_callbacks_item) * size ==
6587
0
              sizeof(t_hts_htmlcheck_callbacks));
6588
6589
      /* Free all linked lists */
6590
0
      for(i = 0; i < size; i++) {
6591
0
        t_hts_callbackarg *carg, *next_carg;
6592
6593
0
        for(carg = items[i].carg;
6594
0
            carg != NULL && (next_carg = carg->prev.carg, carg != NULL);
6595
0
            carg = next_carg) {
6596
0
          hts_free(carg);
6597
0
        }
6598
0
      }
6599
6600
0
      freet(opt->callbacks_fun);
6601
0
      opt->callbacks_fun = NULL;
6602
0
    }
6603
6604
    /* Close library handles */
6605
0
    unplug_wrappers(opt);
6606
6607
    /* Cache */
6608
0
    if (opt->state.dns_cache != NULL) {
6609
0
      coucal root;
6610
6611
0
      hts_mutexlock(&opt->state.lock);
6612
0
      root = opt->state.dns_cache;
6613
0
      opt->state.dns_cache = NULL;
6614
0
      hts_mutexrelease(&opt->state.lock);
6615
6616
0
      coucal_delete(&root); // frees records via hts_cache_value_free
6617
0
    }
6618
6619
    /* URLs a front end queued but the engine never got to inject */
6620
0
    hts_addurl_free(hts_addurl_take(opt));
6621
6622
    /* Cancel chain */
6623
0
    if (opt->state.cancel != NULL) {
6624
0
      htsoptstatecancel *cancel;
6625
6626
0
      for(cancel = opt->state.cancel; cancel != NULL;) {
6627
0
        htsoptstatecancel *next = cancel->next;
6628
6629
0
        if (cancel->url != NULL) {
6630
0
          freet(cancel->url);
6631
0
        }
6632
0
        freet(cancel);
6633
0
        cancel = next;
6634
0
      }
6635
0
      opt->state.cancel = NULL;
6636
0
    }
6637
6638
    /* Free strings */
6639
6640
0
    StringFree(opt->proxy.name);
6641
0
    StringFree(opt->proxy.bindhost);
6642
6643
0
    StringFree(opt->savename_userdef);
6644
0
    StringFree(opt->user_agent);
6645
0
    StringFree(opt->referer);
6646
0
    StringFree(opt->from);
6647
0
    StringFree(opt->lang_iso);
6648
0
    StringFree(opt->accept);
6649
0
    StringFree(opt->headers);
6650
0
    StringFree(opt->sys_com);
6651
0
    StringFree(opt->mimedefs);
6652
0
    StringFree(opt->filelist);
6653
0
    StringFree(opt->urllist);
6654
0
    StringFree(opt->footer);
6655
0
    StringFree(opt->mod_blacklist);
6656
0
    StringFree(opt->strip_query);
6657
0
    StringFree(opt->host_alias);
6658
0
    StringFree(opt->cookies_file);
6659
0
    StringFree(opt->why_url);
6660
0
    StringFree(opt->warc_file);
6661
0
    StringFree(opt->sitemap_url);
6662
0
    hts_sitemap_free(opt); /* backstop: httpmirror's early-return paths */
6663
0
    singlefile_free(opt);
6664
6665
0
    hts_changes_free_opt(opt);
6666
6667
0
    StringFree(opt->path_html);
6668
0
    StringFree(opt->path_html_utf8);
6669
0
    StringFree(opt->path_log);
6670
0
    StringFree(opt->path_bin);
6671
6672
    /* mutexes */
6673
0
    hts_mutexfree(&opt->state.lock);
6674
6675
    /* Free structure */
6676
0
    free(opt);
6677
0
  }
6678
0
}
6679
6680
// TEMPORARY - PUT THIS STRUCTURE INSIDE httrackp !
6681
0
const hts_stat_struct* hts_get_stats(httrackp * opt) {
6682
0
  if (opt == NULL) {
6683
0
    return NULL;
6684
0
  }
6685
6686
0
  HTS_STAT.stat_nsocket = 0;
6687
0
  HTS_STAT.stat_errors = fspc(opt, NULL, "error");
6688
0
  HTS_STAT.stat_warnings = fspc(opt, NULL, "warning");
6689
0
  HTS_STAT.stat_infos = fspc(opt, NULL, "info");
6690
0
  HTS_STAT.nbk = 0;
6691
0
  HTS_STAT.nb = 0;
6692
6693
0
  return &HTS_STAT;
6694
0
}
6695
6696
// defaut wrappers
6697
0
static void __cdecl htsdefault_init(t_hts_callbackarg *carg) {}
6698
6699
0
static void __cdecl htsdefault_uninit(t_hts_callbackarg *carg) {}
6700
0
static int __cdecl htsdefault_start(t_hts_callbackarg * carg, httrackp * opt) {
6701
0
  return 1;
6702
0
}
6703
0
static int __cdecl htsdefault_chopt(t_hts_callbackarg * carg, httrackp * opt) {
6704
0
  return 1;
6705
0
}
6706
0
static int __cdecl htsdefault_end(t_hts_callbackarg * carg, httrackp * opt) {
6707
0
  return 1;
6708
0
}
6709
static int __cdecl htsdefault_preprocesshtml(t_hts_callbackarg * carg,
6710
                                             httrackp * opt, char **html,
6711
                                             int *len, const char *url_adresse,
6712
0
                                             const char *url_fichier) {
6713
0
  return 1;
6714
0
}
6715
static int __cdecl htsdefault_postprocesshtml(t_hts_callbackarg * carg,
6716
                                              httrackp * opt, char **html,
6717
                                              int *len, const char *url_adresse,
6718
0
                                              const char *url_fichier) {
6719
0
  return 1;
6720
0
}
6721
static int __cdecl htsdefault_checkhtml(t_hts_callbackarg * carg,
6722
                                        httrackp * opt, char *html, int len,
6723
                                        const char *url_adresse,
6724
0
                                        const char *url_fichier) {
6725
0
  return 1;
6726
0
}
6727
0
static int __cdecl htsdefault_loop(t_hts_callbackarg * carg, httrackp * opt, lien_back * back, int back_max, int back_index, int lien_n, int lien_tot, int stat_time, hts_stat_struct * stats) {        // appelé à chaque boucle de HTTrack
6728
0
  return 1;
6729
0
}
6730
static const char *__cdecl htsdefault_query(t_hts_callbackarg * carg,
6731
                                            httrackp * opt,
6732
0
                                            const char *question) {
6733
0
  return "";
6734
0
}
6735
static const char *__cdecl htsdefault_query2(t_hts_callbackarg * carg,
6736
                                             httrackp * opt,
6737
0
                                             const char *question) {
6738
0
  return "";
6739
0
}
6740
static const char *__cdecl htsdefault_query3(t_hts_callbackarg * carg,
6741
                                             httrackp * opt,
6742
0
                                             const char *question) {
6743
0
  return "";
6744
0
}
6745
static int __cdecl htsdefault_check(t_hts_callbackarg * carg, httrackp * opt,
6746
                                    const char *adr, const char *fil,
6747
0
                                    int status) {
6748
0
  return -1;
6749
0
}
6750
static int __cdecl htsdefault_check_mime(t_hts_callbackarg * carg,
6751
                                         httrackp * opt, const char *adr,
6752
                                         const char *fil, const char *mime,
6753
0
                                         int status) {
6754
0
  return -1;
6755
0
}
6756
static void __cdecl htsdefault_pause(t_hts_callbackarg * carg, httrackp * opt,
6757
0
                                     const char *lockfile) {
6758
0
  while(fexist(lockfile)) {
6759
0
    Sleep(1000);
6760
0
  }
6761
0
}
6762
static void __cdecl htsdefault_filesave(t_hts_callbackarg * carg,
6763
0
                                        httrackp * opt, const char *file) {
6764
0
}
6765
static void __cdecl htsdefault_filesave2(t_hts_callbackarg * carg,
6766
                                         httrackp * opt, const char *adr,
6767
                                         const char *file, const char *sav,
6768
                                         int is_new, int is_modified,
6769
0
                                         int not_updated) {
6770
0
}
6771
static int __cdecl htsdefault_linkdetected(t_hts_callbackarg * carg,
6772
0
                                           httrackp * opt, char *link) {
6773
0
  return 1;
6774
0
}
6775
static int __cdecl htsdefault_linkdetected2(t_hts_callbackarg * carg,
6776
                                            httrackp * opt, char *link,
6777
0
                                            const char *start_tag) {
6778
0
  return 1;
6779
0
}
6780
static int __cdecl htsdefault_xfrstatus(t_hts_callbackarg * carg,
6781
0
                                        httrackp * opt, lien_back * back) {
6782
0
  return 1;
6783
0
}
6784
static int __cdecl htsdefault_savename(t_hts_callbackarg * carg, httrackp * opt,
6785
                                       const char *adr_complete,
6786
                                       const char *fil_complete,
6787
                                       const char *referer_adr,
6788
0
                                       const char *referer_fil, char *save) {
6789
0
  return 1;
6790
0
}
6791
static int __cdecl htsdefault_sendhead(t_hts_callbackarg * carg, httrackp * opt,
6792
                                       char *buff, const char *adr,
6793
                                       const char *fil, const char *referer_adr,
6794
                                       const char *referer_fil,
6795
0
                                       htsblk * outgoing) {
6796
0
  return 1;
6797
0
}
6798
static int __cdecl htsdefault_receivehead(t_hts_callbackarg * carg,
6799
                                          httrackp * opt, char *buff,
6800
                                          const char *adr, const char *fil,
6801
                                          const char *referer_adr,
6802
                                          const char *referer_fil,
6803
0
                                          htsblk * incoming) {
6804
0
  return 1;
6805
0
}
6806
static int __cdecl htsdefault_detect(t_hts_callbackarg * carg, httrackp * opt,
6807
0
                                     htsmoduleStruct * str) {
6808
0
  return 0;
6809
0
}
6810
static int __cdecl htsdefault_parse(t_hts_callbackarg * carg, httrackp * opt,
6811
0
                                    htsmoduleStruct * str) {
6812
0
  return 0;
6813
0
}
6814
6815
/* Default internal dummy callbacks */
6816
const t_hts_htmlcheck_callbacks default_callbacks = {
6817
  {htsdefault_init, NULL},
6818
  {htsdefault_uninit, NULL},
6819
  {htsdefault_start, NULL},
6820
  {htsdefault_end, NULL},
6821
  {htsdefault_chopt, NULL},
6822
  {htsdefault_preprocesshtml, NULL},
6823
  {htsdefault_postprocesshtml, NULL},
6824
  {htsdefault_checkhtml, NULL},
6825
  {htsdefault_query, NULL},
6826
  {htsdefault_query2, NULL},
6827
  {htsdefault_query3, NULL},
6828
  {htsdefault_loop, NULL},
6829
  {htsdefault_check, NULL},
6830
  {htsdefault_check_mime, NULL},
6831
  {htsdefault_pause, NULL},
6832
  {htsdefault_filesave, NULL},
6833
  {htsdefault_filesave2, NULL},
6834
  {htsdefault_linkdetected, NULL},
6835
  {htsdefault_linkdetected2, NULL},
6836
  {htsdefault_xfrstatus, NULL},
6837
  {htsdefault_savename, NULL},
6838
  {htsdefault_sendhead, NULL},
6839
  {htsdefault_receivehead, NULL},
6840
  {htsdefault_detect, NULL},
6841
  {htsdefault_parse, NULL}
6842
};
6843
6844
0
#define CALLBACK_OP(CB, NAME, OPERATION, S, FUN) do {   \
6845
0
  if (strcmp(NAME, S) == 0) {                           \
6846
0
    OPERATION(t_hts_htmlcheck_ ##FUN, (CB)->FUN.fun);   \
6847
0
  }                                                     \
6848
0
} while(0)
6849
6850
0
#define DISPATCH_CALLBACK(CB, NAME, OPERATION) do { \
6851
0
  CALLBACK_OP(CB, NAME, OPERATION, "init", init); \
6852
0
  CALLBACK_OP(CB, NAME, OPERATION, "free", uninit); \
6853
0
  CALLBACK_OP(CB, NAME, OPERATION, "start", start); \
6854
0
  CALLBACK_OP(CB, NAME, OPERATION, "end", end); \
6855
0
  CALLBACK_OP(CB, NAME, OPERATION, "change-options", chopt); \
6856
0
  CALLBACK_OP(CB, NAME, OPERATION, "preprocess-html", preprocess); \
6857
0
  CALLBACK_OP(CB, NAME, OPERATION, "postprocess-html", postprocess); \
6858
0
  CALLBACK_OP(CB, NAME, OPERATION, "check-html", check_html); \
6859
0
  CALLBACK_OP(CB, NAME, OPERATION, "query", query); \
6860
0
  CALLBACK_OP(CB, NAME, OPERATION, "query2", query2); \
6861
0
  CALLBACK_OP(CB, NAME, OPERATION, "query3", query3); \
6862
0
  CALLBACK_OP(CB, NAME, OPERATION, "loop", loop); \
6863
0
  CALLBACK_OP(CB, NAME, OPERATION, "check-link", check_link); \
6864
0
  CALLBACK_OP(CB, NAME, OPERATION, "check-mime", check_mime); \
6865
0
  CALLBACK_OP(CB, NAME, OPERATION, "pause", pause); \
6866
0
  CALLBACK_OP(CB, NAME, OPERATION, "save-file", filesave); \
6867
0
  CALLBACK_OP(CB, NAME, OPERATION, "save-file2", filesave2); \
6868
0
  CALLBACK_OP(CB, NAME, OPERATION, "link-detected", linkdetected); \
6869
0
  CALLBACK_OP(CB, NAME, OPERATION, "link-detected2", linkdetected2); \
6870
0
  CALLBACK_OP(CB, NAME, OPERATION, "transfer-status", xfrstatus); \
6871
0
  CALLBACK_OP(CB, NAME, OPERATION, "save-name", savename); \
6872
0
  CALLBACK_OP(CB, NAME, OPERATION, "send-header", sendhead); \
6873
0
  CALLBACK_OP(CB, NAME, OPERATION, "receive-header", receivehead); \
6874
0
} while(0)
6875
6876
int hts_set_callback(t_hts_htmlcheck_callbacks * callbacks, const char *name,
6877
0
                     void *function) {
6878
0
  int error = 1;
6879
0
#define CALLBACK_OPERATION(TYPE, FUNCTION) do { \
6880
0
    FUNCTION = (TYPE) function;                 \
6881
0
    error = 0;                                  \
6882
0
  } while(0)
6883
0
  DISPATCH_CALLBACK(callbacks, name, CALLBACK_OPERATION);
6884
0
#undef CALLBACK_OPERATION
6885
0
  return error;
6886
0
}
6887
6888
0
void *hts_get_callback(t_hts_htmlcheck_callbacks * callbacks, const char *name) {
6889
0
#define CALLBACK_OPERATION(TYPE, FUNCTION) do { \
6890
0
    return (void*) FUNCTION;                    \
6891
0
  } while(0)
6892
0
  DISPATCH_CALLBACK(callbacks, name, CALLBACK_OPERATION);
6893
0
#undef CALLBACK_OPERATION
6894
0
  return NULL;
6895
0
}
6896
6897
// end defaut wrappers
6898
6899
/* libc stubs */
6900
6901
0
HTSEXT_API char *hts_strdup(const char *str) {
6902
0
  return strdup(str);
6903
0
}
6904
6905
0
HTSEXT_API void *hts_malloc(size_t size) {
6906
0
  return malloc(size);
6907
0
}
6908
6909
0
HTSEXT_API void *hts_realloc(void *const data, const size_t size) {
6910
0
  return realloc(data, size);
6911
0
}
6912
6913
0
HTSEXT_API void hts_free(void *data) {
6914
0
  free(data);
6915
0
}
6916
6917
/* Dummy functions */
6918
0
HTSEXT_API int hts_resetvar(void) {
6919
0
  return 0;
6920
0
}
6921
6922
#ifdef _WIN32
6923
6924
typedef struct dirent dirent;
6925
6926
DIR *opendir(const char *name) {
6927
  WIN32_FILE_ATTRIBUTE_DATA st;
6928
  DIR *dir;
6929
  size_t len;
6930
  int i;
6931
  LPWSTR wname;
6932
6933
  if (name == NULL || *name == '\0') {
6934
    errno = ENOENT;
6935
    return NULL;
6936
  }
6937
  // Wide \\?\ path: no MAX_PATH cap, no CP_ACP mis-decode (#133,#630).
6938
  wname = hts_pathToUCS2(name);
6939
  if (wname == NULL ||
6940
      !GetFileAttributesExW(wname, GetFileExInfoStandard, &st) ||
6941
      (st.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
6942
    freet(wname);
6943
    errno = ENOENT;
6944
    return NULL;
6945
  }
6946
  freet(wname);
6947
  dir = calloc(sizeof(DIR), 1);
6948
  if (dir == NULL) {
6949
    errno = ENOMEM;
6950
    return NULL;
6951
  }
6952
  len = strlen(name);
6953
  dir->h = INVALID_HANDLE_VALUE;
6954
  dir->name = malloc(len + 2 + 1);
6955
  strcpy(dir->name, name);
6956
  for(i = 0; dir->name[i] != '\0'; i++) {
6957
    if (dir->name[i] == '/') {
6958
      dir->name[i] = '\\';
6959
    }
6960
  }
6961
  strcat(dir->name, "\\*");
6962
  return dir;
6963
}
6964
6965
struct dirent *readdir(DIR * dir) {
6966
  WIN32_FIND_DATAW find;
6967
6968
  if (dir->h == INVALID_HANDLE_VALUE) {
6969
    // \\?\-prefix so a long/non-ASCII directory enumerates instead of ENOENT.
6970
    LPWSTR wname = hts_pathToUCS2(dir->name);
6971
6972
    dir->h =
6973
        wname != NULL ? FindFirstFileW(wname, &find) : INVALID_HANDLE_VALUE;
6974
    freet(wname);
6975
  } else {
6976
    if (!FindNextFileW(dir->h, &find)) {
6977
      FindClose(dir->h);
6978
      dir->h = INVALID_HANDLE_VALUE;
6979
    }
6980
  }
6981
  if (dir->h != INVALID_HANDLE_VALUE) {
6982
    char *u = hts_convertUCS2StringToUTF8(find.cFileName, -1);
6983
6984
    dir->entry.d_name[0] = 0;
6985
    if (u != NULL) {
6986
      strncat(dir->entry.d_name, u, HTS_DIRENT_SIZE - 1);
6987
      freet(u);
6988
    }
6989
    return &dir->entry;
6990
  }
6991
  errno = ENOENT;
6992
  return NULL;
6993
}
6994
6995
int closedir(DIR * dir) {
6996
  if (dir != NULL) {
6997
    if (dir->h != INVALID_HANDLE_VALUE) {
6998
      CloseHandle(dir->h);
6999
    }
7000
    if (dir->name != NULL) {
7001
      free(dir->name);
7002
    }
7003
    free(dir);
7004
    return 0;
7005
  }
7006
  errno = EBADF;
7007
  return -1;
7008
}
7009
7010
// UTF-8 aware FILE API
7011
7012
static void copyWchar(LPWSTR dest, const char *src) {
7013
  int i;
7014
7015
  for(i = 0; src[i]; i++) {
7016
    dest[i] = src[i];
7017
  }
7018
  dest[i] = '\0';
7019
}
7020
7021
/* UTF-8 path -> UCS-2 for the _w* file APIs. At/above HTS_WIN_LONGPATH_MIN,
7022
   \\?\-prefix it via GetFullPathNameW to clear MAX_PATH (#133); else unchanged.
7023
   Any prefixing failure falls back to the plain converted path. */
7024
#define HTS_WIN_LONGPATH_MIN 240 /* stay clear of MAX_PATH (260) */
7025
7026
LPWSTR hts_pathToUCS2(const char *path) {
7027
  LPWSTR wpath = hts_convertUTF8StringToUCS2(path, (int) strlen(path), NULL);
7028
7029
  if (wpath == NULL) {
7030
    return NULL;
7031
  }
7032
  const size_t len = wcslen(wpath);
7033
  // Already "\\?\" or "\\.\": don't re-prefix.
7034
  const int verbatim = len >= 4 && wpath[0] == L'\\' && wpath[1] == L'\\' &&
7035
                       (wpath[2] == L'?' || wpath[2] == L'.') &&
7036
                       wpath[3] == L'\\';
7037
7038
  if (len < HTS_WIN_LONGPATH_MIN || verbatim) {
7039
    return wpath;
7040
  }
7041
7042
  const DWORD need = GetFullPathNameW(wpath, 0, NULL, NULL); /* incl NUL */
7043
  LPWSTR full = need != 0 ? malloct((size_t) need * sizeof(WCHAR)) : NULL;
7044
7045
  if (full == NULL) {
7046
    return wpath; /* fall back to the plain path */
7047
  }
7048
  const DWORD written = GetFullPathNameW(wpath, need, full, NULL);
7049
7050
  if (written == 0 || written >= need || full[0] == L'\0') {
7051
    freet(full);
7052
    return wpath;
7053
  }
7054
7055
  const int isUNC = full[0] == L'\\' && full[1] == L'\\';
7056
  // UNC "\\srv\share" -> "\\?\UNC\srv\share": the prefix subsumes the "\\".
7057
  const WCHAR *const pfx = isUNC ? L"\\\\?\\UNC\\" : L"\\\\?\\";
7058
  const WCHAR *const body = isUNC ? full + 2 : full;
7059
  const size_t pfxLen = wcslen(pfx), bodyLen = wcslen(body);
7060
  LPWSTR out = malloct((pfxLen + bodyLen + 1) * sizeof(WCHAR));
7061
7062
  if (out == NULL) {
7063
    freet(full);
7064
    return wpath;
7065
  }
7066
  memcpybuff(out, pfx, pfxLen * sizeof(WCHAR));
7067
  memcpybuff(out + pfxLen, body, (bodyLen + 1) * sizeof(WCHAR));
7068
  freet(full);
7069
  freet(wpath);
7070
  return out;
7071
}
7072
7073
FILE *hts_fopen_utf8(const char *path, const char *mode) {
7074
  WCHAR wmode[32];
7075
  LPWSTR wpath = hts_pathToUCS2(path);
7076
7077
  assertf(strlen(mode) < sizeof(wmode) / sizeof(WCHAR));
7078
  copyWchar(wmode, mode);
7079
  if (wpath != NULL) {
7080
    FILE *const fp = _wfopen(wpath, wmode);
7081
7082
    free(wpath);
7083
    return fp;
7084
  } else {
7085
    // Fallback on conversion error.
7086
    return fopen(path, mode);
7087
  }
7088
}
7089
7090
int hts_stat_utf8(const char *path, STRUCT_STAT * buf) {
7091
  LPWSTR wpath = hts_pathToUCS2(path);
7092
7093
  if (wpath != NULL) {
7094
    const int result = _wstat64(wpath, buf);
7095
7096
    free(wpath);
7097
    return result;
7098
  } else {
7099
    // Fallback on conversion error.
7100
    return _stat64(path, buf);
7101
  }
7102
}
7103
7104
int hts_unlink_utf8(const char *path) {
7105
  LPWSTR wpath = hts_pathToUCS2(path);
7106
7107
  if (wpath != NULL) {
7108
    const int result = _wunlink(wpath);
7109
7110
    free(wpath);
7111
    return result;
7112
  } else {
7113
    // Fallback on conversion error.
7114
    return _unlink(path);
7115
  }
7116
}
7117
7118
int hts_rename_utf8(const char *oldpath, const char *newpath) {
7119
  LPWSTR woldpath = hts_pathToUCS2(oldpath);
7120
  LPWSTR wnewpath = hts_pathToUCS2(newpath);
7121
  if (woldpath != NULL && wnewpath != NULL) {
7122
    const int result = _wrename(woldpath, wnewpath);
7123
    /* Save errno: callers key off it (#779) and free() may clobber it. */
7124
    const int err = errno;
7125
7126
    free(woldpath);
7127
    free(wnewpath);
7128
    errno = err;
7129
    return result;
7130
  } else {
7131
    if (woldpath != NULL)
7132
      free(woldpath);
7133
    if (wnewpath != NULL)
7134
      free(wnewpath);
7135
    // Fallback on conversion error.
7136
    return rename(oldpath, newpath);
7137
  }
7138
}
7139
7140
int hts_rmdir_utf8(const char *path) {
7141
  LPWSTR wpath = hts_pathToUCS2(path);
7142
7143
  if (wpath != NULL) {
7144
    const int result = _wrmdir(wpath);
7145
7146
    free(wpath);
7147
    return result;
7148
  } else {
7149
    // Fallback on conversion error.
7150
    return _rmdir(path);
7151
  }
7152
}
7153
7154
int hts_mkdir_utf8(const char *path) {
7155
  LPWSTR wpath = hts_pathToUCS2(path);
7156
7157
  if (wpath != NULL) {
7158
    const int result = _wmkdir(wpath);
7159
7160
    free(wpath);
7161
    return result;
7162
  } else {
7163
    // Fallback on conversion error.
7164
    return _mkdir(path);
7165
  }
7166
}
7167
7168
HTSEXT_API int hts_utime_utf8(const char *path, const STRUCT_UTIMBUF * times) {
7169
  STRUCT_UTIMBUF mtimes = *times;
7170
  LPWSTR wpath = hts_pathToUCS2(path);
7171
7172
  if (wpath != NULL) {
7173
    const int result = _wutime(wpath, &mtimes);
7174
7175
    free(wpath);
7176
    return result;
7177
  } else {
7178
    // Fallback on conversion error.
7179
    return _utime(path, &mtimes);
7180
  }
7181
}
7182
7183
#endif
7184
7185
// Fin