1 /* Metalink module.
2 Copyright (C) 2015, 2018-2021 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget. If not, see <http://www.gnu.org/licenses/>.
18
19 Additional permission under GNU GPL version 3 section 7
20
21 If you modify this program, or any covered work, by linking or
22 combining it with the OpenSSL project's OpenSSL library (or a
23 modified version of that library), containing parts covered by the
24 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
25 grants you additional permission to convey the resulting work.
26 Corresponding Source for a non-source form of such a combination
27 shall include the source code for the parts of OpenSSL used as well
28 as that of the covered work. */
29
30 #include "wget.h"
31 #ifdef HAVE_METALINK
32
33 #include "metalink.h"
34 #include "retr.h"
35 #include "exits.h"
36 #include "utils.h"
37 #include "md2.h"
38 #include "md4.h"
39 #include "md5.h"
40 #include "sha1.h"
41 #include "sha256.h"
42 #include "sha512.h"
43 #include "filename.h"
44 #include "xmemdup0.h"
45 #include "xstrndup.h"
46 #include "c-strcase.h"
47 #include <errno.h>
48 #include <unistd.h> /* For unlink. */
49 #include <metalink/metalink_parser.h>
50 #ifdef HAVE_GPGME
51 #include <gpgme.h>
52 #include <fcntl.h> /* For open and close. */
53 #endif
54
55 #ifdef TESTING
56 #include "../tests/unit-tests.h"
57 #endif
58
59 /* Loop through all files in metalink structure and retrieve them.
60 Returns RETROK if all files were downloaded.
61 Returns last retrieval error (from retrieve_url) if some files
62 could not be downloaded. */
63 uerr_t
retrieve_from_metalink(const metalink_t * metalink)64 retrieve_from_metalink (const metalink_t* metalink)
65 {
66 metalink_file_t **mfile_ptr;
67 uerr_t last_retr_err = RETROK; /* Store last encountered retrieve error. */
68
69 FILE *_output_stream = output_stream;
70 bool _output_stream_regular = output_stream_regular;
71 char *_output_document = opt.output_document;
72
73 /* metalink file counter */
74 unsigned mfc = 0;
75
76 /* metalink retrieval type */
77 const char *metatpy = metalink->origin ? "Metalink/HTTP" : "Metalink/XML";
78
79 /* metalink mother source */
80 char *metasrc = metalink->origin ? metalink->origin : opt.input_metalink;
81
82 DEBUGP (("Retrieving from Metalink %s\n", quote (metasrc)));
83
84 /* No files to download. */
85 if (!metalink->files)
86 return RETROK;
87
88 if (opt.output_document)
89 {
90 /* We cannot support output_document as we need to compute checksum
91 of downloaded file, and to remove it if the checksum is bad. */
92 logputs (LOG_NOTQUIET,
93 _("-O not supported for metalink download. Ignoring.\n"));
94 }
95
96 for (mfile_ptr = metalink->files; *mfile_ptr; mfile_ptr++)
97 {
98 metalink_file_t *mfile = *mfile_ptr;
99 metalink_resource_t **mres_ptr;
100 char *planname = NULL;
101 char *trsrname = NULL;
102 char *filename;
103 char *basename;
104 char *safename = NULL;
105 char *destname = NULL;
106 bool size_ok = false;
107 bool hash_ok = false;
108
109 uerr_t retr_err = METALINK_MISSING_RESOURCE;
110
111 /* -1 -> file should be rejected
112 0 -> could not verify
113 1 -> verified successfully */
114 char sig_status = 0;
115
116 bool skip_mfile = false;
117
118 output_stream = NULL;
119
120 mfc++;
121
122 /* The directory prefix for opt.metalink_over_http is handled by
123 src/url.c (url_file_name), do not add it a second time. */
124 if (!metalink->origin && opt.dir_prefix && strlen (opt.dir_prefix))
125 planname = aprintf ("%s/%s", opt.dir_prefix, mfile->name);
126 else
127 planname = xstrdup (mfile->name);
128
129 /* With Metalink/HTTP, trust the metalink file name (from cli).
130 With --trust-server-names, trust the Metalink/XML file name,
131 otherwise, use the basename of --input-metalink followed by
132 the metalink file counter as suffix. */
133 if (metalink->origin || opt.trustservernames)
134 {
135 trsrname = xstrdup (mfile->name);
136 }
137 else
138 {
139 trsrname = xstrdup (get_metalink_basename (opt.input_metalink));
140 append_suffix_number (&trsrname, ".#", mfc);
141 }
142
143 /* Add the directory prefix for opt.input_metalink. */
144 if (!metalink->origin && opt.dir_prefix && strlen (opt.dir_prefix))
145 filename = aprintf ("%s/%s", opt.dir_prefix, trsrname);
146 else
147 filename = xstrdup (trsrname);
148
149 /* Enforce libmetalink's metalink_check_safe_path(). */
150 basename = get_metalink_basename (filename);
151 safename = metalink_check_safe_path (filename) ? filename : basename;
152
153 DEBUGP (("Processing metalink file %s...\n", quote (mfile->name)));
154 DEBUGP (("\n"));
155 DEBUGP ((" %s\n", metatpy));
156 DEBUGP (("\n"));
157 DEBUGP ((" --trust-server-names %s\n", opt.trustservernames ? "true" : "false"));
158 DEBUGP ((" --directory-prefix %s\n", quote (opt.dir_prefix ? opt.dir_prefix : "")));
159 DEBUGP (("\n"));
160 DEBUGP ((" Counted metalink file %u\n", mfc));
161 DEBUGP ((" Planned metalink file %s\n", quote (planname ? planname : "")));
162 DEBUGP ((" Trusted metalink file %s\n", quote (trsrname ? trsrname : "")));
163 DEBUGP ((" Current metalink file %s\n", quote (filename ? filename : "")));
164 DEBUGP ((" Cleaned metalink file %s\n", quote (basename ? basename : "")));
165 DEBUGP ((" Secured metalink file %s\n", quote (safename ? safename : "")));
166 DEBUGP (("\n"));
167
168 /* Verify if the planned metalink file name is safe. */
169 if (!safename || strcmp (planname, safename))
170 {
171 logprintf (LOG_NOTQUIET,
172 _("[--trust-server-names %s, --directory-prefix=%s]\n"),
173 (opt.trustservernames ? "true" : "false"),
174 quote (opt.dir_prefix ? opt.dir_prefix : ""));
175 logprintf (LOG_NOTQUIET,
176 _("Planned metalink file: %s\n"),
177 quote (planname ? planname : ""));
178 logprintf (LOG_NOTQUIET,
179 _("Secured metalink file: %s\n"),
180 quote (safename ? safename : ""));
181 if (!safename)
182 {
183 logprintf (LOG_NOTQUIET,
184 _("Rejecting metalink file. Unsafe name.\n"));
185 xfree (planname);
186 xfree (trsrname);
187 xfree (filename);
188 continue;
189 }
190 }
191
192 /* Process the chosen application/metalink4+xml metaurl. */
193 if (opt.metalink_index >= 0)
194 {
195 int _metalink_index = opt.metalink_index;
196
197 metalink_metaurl_t **murl_ptr;
198 int abs_count = 0, meta_count = 0;
199
200 uerr_t x_retr_err = METALINK_MISSING_RESOURCE;
201
202 opt.metalink_index = -1;
203
204 DEBUGP (("Searching application/metalink4+xml ordinal number %d...\n", _metalink_index));
205
206 if (mfile->metaurls && mfile->metaurls[0])
207 for (murl_ptr = mfile->metaurls; *murl_ptr; murl_ptr++)
208 {
209 metalink_t* metaurl_xml;
210 metalink_error_t meta_err;
211 metalink_metaurl_t *murl = *murl_ptr;
212
213 char *_dir_prefix = opt.dir_prefix;
214 char *_input_metalink = opt.input_metalink;
215
216 char *metafile = NULL;
217 char *metadest = NULL;
218 char *metadir = NULL;
219
220 abs_count++;
221
222 if (strcmp (murl->mediatype, "application/metalink4+xml"))
223 continue;
224
225 meta_count++;
226
227 DEBUGP ((" Ordinal number %d: %s\n", meta_count, quote (murl->url)));
228
229 if (_metalink_index > 0)
230 {
231 if (meta_count < _metalink_index)
232 continue;
233 else if (meta_count > _metalink_index)
234 break;
235 }
236
237 logprintf (LOG_NOTQUIET,
238 _("Processing metaurl %s...\n"), quote (murl->url));
239
240 /* Metalink/XML download file name. */
241 metafile = xstrdup (safename);
242
243 if (opt.trustservernames)
244 replace_metalink_basename (&metafile, murl->name ? murl->name : murl->url);
245 else
246 append_suffix_number (&metafile, ".meta#", meta_count);
247
248 if (!metalink_check_safe_path (metafile))
249 {
250 logprintf (LOG_NOTQUIET,
251 _("Rejecting metaurl file %s. Unsafe name.\n"),
252 quote (metafile));
253 xfree (metafile);
254 if (_metalink_index > 0)
255 break;
256 continue;
257 }
258
259 /* For security reasons, always save metalink metaurl
260 files as new unique files. Keep them on failure. */
261 x_retr_err = fetch_metalink_file (murl->url, false, false,
262 metafile, &metadest);
263
264 /* On failure, try the next metalink metaurl. */
265 if (x_retr_err != RETROK)
266 {
267 logprintf (LOG_VERBOSE,
268 _("Failed to download %s. Skipping metaurl.\n"),
269 quote (metadest ? metadest : metafile));
270 inform_exit_status (x_retr_err);
271 xfree (metadest);
272 xfree (metafile);
273 if (_metalink_index > 0)
274 break;
275 continue;
276 }
277
278 /* Parse Metalink/XML. */
279 meta_err = metalink_parse_file (metadest, &metaurl_xml);
280
281 /* On failure, try the next metalink metaurl. */
282 if (meta_err)
283 {
284 logprintf (LOG_NOTQUIET,
285 _("Unable to parse metaurl file %s.\n"), quote (metadest));
286 x_retr_err = METALINK_PARSE_ERROR;
287 inform_exit_status (x_retr_err);
288 xfree (metadest);
289 xfree (metafile);
290 if (_metalink_index > 0)
291 break;
292 continue;
293 }
294
295 /* We need to sort the resources if preferred location
296 was specified by the user. */
297 if (opt.preferred_location && opt.preferred_location[0])
298 {
299 metalink_file_t **x_mfile_ptr;
300 for (x_mfile_ptr = metaurl_xml->files; *x_mfile_ptr; x_mfile_ptr++)
301 {
302 metalink_resource_t **x_mres_ptr;
303 metalink_file_t *x_mfile = *x_mfile_ptr;
304 size_t mres_count = 0;
305
306 for (x_mres_ptr = x_mfile->resources; *x_mres_ptr; x_mres_ptr++)
307 mres_count++;
308
309 stable_sort (x_mfile->resources,
310 mres_count,
311 sizeof (metalink_resource_t *),
312 metalink_res_cmp);
313 }
314 }
315
316 /* Insert the current "Directory Options". */
317 if (metalink->origin)
318 {
319 /* WARNING: Do not use lib/dirname.c (dir_name) to
320 get the directory name, it may append a dot '.'
321 character to the directory name. */
322 metadir = xstrdup (planname);
323 replace_metalink_basename (&metadir, NULL);
324 }
325 else
326 {
327 metadir = xstrdup (opt.dir_prefix);
328 }
329
330 opt.dir_prefix = metadir;
331 opt.input_metalink = metadest;
332
333 x_retr_err = retrieve_from_metalink (metaurl_xml);
334
335 if (x_retr_err != RETROK)
336 logprintf (LOG_NOTQUIET,
337 _("Could not download all resources from %s.\n"),
338 quote (metadest));
339
340 metalink_delete (metaurl_xml);
341 metaurl_xml = NULL;
342
343 opt.input_metalink = _input_metalink;
344 opt.dir_prefix = _dir_prefix;
345
346 xfree (metadir);
347 xfree (metadest);
348 xfree (metafile);
349
350 break;
351 }
352
353 if (x_retr_err != RETROK)
354 logprintf (LOG_NOTQUIET, _("Metaurls processing returned with error.\n"));
355
356 xfree (destname);
357 xfree (filename);
358 xfree (trsrname);
359 xfree (planname);
360
361 opt.output_document = _output_document;
362 output_stream_regular = _output_stream_regular;
363 output_stream = _output_stream;
364
365 opt.metalink_index = _metalink_index;
366
367 return x_retr_err;
368 }
369
370 /* Resources are sorted by priority. */
371 for (mres_ptr = mfile->resources;
372 *mres_ptr && mfile->checksums && !skip_mfile; mres_ptr++)
373 {
374 metalink_resource_t *mres = *mres_ptr;
375 metalink_checksum_t **mchksum_ptr, *mchksum;
376 struct iri *iri;
377 struct url *url;
378 file_stats_t flstats;
379 int url_err;
380
381 clean_metalink_string (&mres->url);
382
383 if (!RES_TYPE_SUPPORTED (mres->type))
384 {
385 logprintf (LOG_VERBOSE,
386 _("Resource type %s not supported, ignoring...\n"),
387 quote (mres->type));
388 continue;
389 }
390
391 /* The file is fully downloaded, but some problems were
392 encountered (checksum failure?). The loop had been
393 continued to switch to the next url. */
394 if (output_stream && retr_err == RETROK)
395 {
396 /* Do not rename/remove a continued file. Skip it. */
397 if (opt.always_rest)
398 {
399 skip_mfile = true;
400 continue;
401 }
402
403 fclose (output_stream);
404 output_stream = NULL;
405 badhash_or_remove (destname);
406 xfree (destname);
407 }
408 else if (!output_stream && destname)
409 {
410 xfree (destname);
411 }
412
413 retr_err = METALINK_RETR_ERROR;
414
415 /* Parse our resource URL. */
416 iri = iri_new ();
417 set_uri_encoding (iri, opt.locale, true);
418 url = url_parse (mres->url, &url_err, iri, false);
419
420 if (!url)
421 {
422 char *error = url_error (mres->url, url_err);
423 logprintf (LOG_NOTQUIET, "%s: %s.\n", mres->url, error);
424 xfree (error);
425 inform_exit_status (URLERROR);
426 iri_free (iri);
427 continue;
428 }
429 else
430 {
431 /* Avoid recursive Metalink from HTTP headers. */
432 bool _metalink_http = opt.metalink_over_http;
433
434 /* If output_stream is not NULL, then we have failed on
435 previous resource and are retrying. Thus, continue
436 with the next resource. Do not close output_stream
437 while iterating over the resources, or the download
438 progress will be lost. */
439 if (output_stream)
440 {
441 DEBUGP (("Previous resource failed, continue with next resource.\n"));
442 }
443 else
444 {
445 /* Assure proper local file name regardless of the URL
446 of particular Metalink resource.
447 To do that we create the local file here and put
448 it as output_stream. We restore the original configuration
449 after we are finished with the file. */
450 if (opt.always_rest)
451 /* continue previous download */
452 output_stream = fopen (safename, "ab");
453 else
454 /* create a file with an unique name */
455 output_stream = unique_create (safename, true, &destname);
456 }
457
458 output_stream_regular = true;
459
460 /*
461 At this point, if output_stream is NULL, the file
462 couldn't be created/opened.
463
464 This happens when the metalink:file has a "path/file"
465 name format and its directory tree cannot be created:
466 * stdio.h (fopen)
467 * src/utils.c (unique_create)
468
469 RFC5854 requires a proper "path/file" format handling,
470 this can be achieved setting opt.output_document while
471 output_stream is left to NULL:
472 * src/http.c (open_output_stream): If output_stream is
473 NULL, create the opt.output_document "path/file"
474 */
475 if (!destname)
476 destname = xstrdup (safename);
477
478 /* Store the real file name for displaying in messages,
479 and for proper RFC5854 "path/file" handling. */
480 opt.output_document = destname;
481
482 opt.metalink_over_http = false;
483 DEBUGP (("Storing to %s\n", destname));
484 retr_err = retrieve_url (url, mres->url, NULL, NULL,
485 NULL, NULL, opt.recursive, iri, false);
486 opt.metalink_over_http = _metalink_http;
487
488 /*
489 Bug: output_stream is NULL, but retrieve_url() somehow
490 created destname.
491
492 Bugfix: point output_stream to destname if it exists.
493 */
494 memset(&flstats, 0, sizeof(flstats));
495 if (!output_stream && file_exists_p (destname, &flstats))
496 output_stream = fopen_stat (destname, "ab", &flstats);
497 }
498 url_free (url);
499 iri_free (iri);
500
501 if (retr_err == RETROK)
502 {
503 FILE *local_file;
504
505 /* Check the digest. */
506 local_file = fopen (destname, "rb");
507 if (!local_file)
508 {
509 logprintf (LOG_NOTQUIET, _("Could not open downloaded file.\n"));
510 continue;
511 }
512
513 size_ok = false;
514 logprintf (LOG_VERBOSE, _("Computing size for %s\n"), quote (destname));
515
516 if (!mfile->size)
517 {
518 size_ok = true;
519 logprintf (LOG_VERBOSE, _("File size not declared. Skipping check.\n"));
520 }
521 else
522 {
523 wgint local_file_size = file_size (destname);
524
525 if (local_file_size == -1)
526 {
527 logprintf (LOG_NOTQUIET, _("Could not get downloaded file's size.\n"));
528 fclose (local_file);
529 local_file = NULL;
530 continue;
531 }
532
533 /* FIXME: what about int64? */
534 DEBUGP (("Declared size: %lld\n", mfile->size));
535 DEBUGP (("Computed size: %lld\n", (long long) local_file_size));
536
537 if (local_file_size != (wgint) mfile->size)
538 {
539 logprintf (LOG_NOTQUIET, _("Size mismatch for file %s.\n"), quote (destname));
540 fclose (local_file);
541 local_file = NULL;
542 continue;
543 }
544 else
545 {
546 size_ok = true;
547 logputs (LOG_VERBOSE, _("Size matches.\n"));
548 }
549 }
550
551 for (mchksum_ptr = mfile->checksums; *mchksum_ptr; mchksum_ptr++)
552 {
553 char md2[MD2_DIGEST_SIZE];
554 char md2_txt[2 * MD2_DIGEST_SIZE + 1];
555
556 char md4[MD4_DIGEST_SIZE];
557 char md4_txt[2 * MD4_DIGEST_SIZE + 1];
558
559 char md5[MD5_DIGEST_SIZE];
560 char md5_txt[2 * MD5_DIGEST_SIZE + 1];
561
562 char sha1[SHA1_DIGEST_SIZE];
563 char sha1_txt[2 * SHA1_DIGEST_SIZE + 1];
564
565 char sha224[SHA224_DIGEST_SIZE];
566 char sha224_txt[2 * SHA224_DIGEST_SIZE + 1];
567
568 char sha256[SHA256_DIGEST_SIZE];
569 char sha256_txt[2 * SHA256_DIGEST_SIZE + 1];
570
571 char sha384[SHA384_DIGEST_SIZE];
572 char sha384_txt[2 * SHA384_DIGEST_SIZE + 1];
573
574 char sha512[SHA512_DIGEST_SIZE];
575 char sha512_txt[2 * SHA512_DIGEST_SIZE + 1];
576
577 hash_ok = false;
578 mchksum = *mchksum_ptr;
579
580 /* I have seen both variants... */
581 if (c_strcasecmp (mchksum->type, "md2")
582 && c_strcasecmp (mchksum->type, "md4")
583 && c_strcasecmp (mchksum->type, "md5")
584 && c_strcasecmp (mchksum->type, "sha1")
585 && c_strcasecmp (mchksum->type, "sha-1")
586 && c_strcasecmp (mchksum->type, "sha224")
587 && c_strcasecmp (mchksum->type, "sha-224")
588 && c_strcasecmp (mchksum->type, "sha256")
589 && c_strcasecmp (mchksum->type, "sha-256")
590 && c_strcasecmp (mchksum->type, "sha384")
591 && c_strcasecmp (mchksum->type, "sha-384")
592 && c_strcasecmp (mchksum->type, "sha512")
593 && c_strcasecmp (mchksum->type, "sha-512"))
594 {
595 DEBUGP (("Ignoring unsupported checksum type %s.\n",
596 quote (mchksum->type)));
597 continue;
598 }
599
600 logprintf (LOG_VERBOSE, _("Computing checksum for %s\n"),
601 quote (destname));
602
603 DEBUGP (("Declared hash: %s\n", mchksum->hash));
604
605 if (c_strcasecmp (mchksum->type, "md2") == 0)
606 {
607 md2_stream (local_file, md2);
608 wg_hex_to_string (md2_txt, md2, MD2_DIGEST_SIZE);
609 DEBUGP (("Computed hash: %s\n", md2_txt));
610 if (!strcmp (md2_txt, mchksum->hash))
611 hash_ok = true;
612 }
613 else if (c_strcasecmp (mchksum->type, "md4") == 0)
614 {
615 md4_stream (local_file, md4);
616 wg_hex_to_string (md4_txt, md4, MD4_DIGEST_SIZE);
617 DEBUGP (("Computed hash: %s\n", md4_txt));
618 if (!strcmp (md4_txt, mchksum->hash))
619 hash_ok = true;
620 }
621 else if (c_strcasecmp (mchksum->type, "md5") == 0)
622 {
623 md5_stream (local_file, md5);
624 wg_hex_to_string (md5_txt, md5, MD5_DIGEST_SIZE);
625 DEBUGP (("Computed hash: %s\n", md5_txt));
626 if (!strcmp (md5_txt, mchksum->hash))
627 hash_ok = true;
628 }
629 else if (c_strcasecmp (mchksum->type, "sha1") == 0
630 || c_strcasecmp (mchksum->type, "sha-1") == 0)
631 {
632 sha1_stream (local_file, sha1);
633 wg_hex_to_string (sha1_txt, sha1, SHA1_DIGEST_SIZE);
634 DEBUGP (("Computed hash: %s\n", sha1_txt));
635 if (!strcmp (sha1_txt, mchksum->hash))
636 hash_ok = true;
637 }
638 else if (c_strcasecmp (mchksum->type, "sha224") == 0
639 || c_strcasecmp (mchksum->type, "sha-224") == 0)
640 {
641 sha224_stream (local_file, sha224);
642 wg_hex_to_string (sha224_txt, sha224, SHA224_DIGEST_SIZE);
643 DEBUGP (("Computed hash: %s\n", sha224_txt));
644 if (!strcmp (sha224_txt, mchksum->hash))
645 hash_ok = true;
646 }
647 else if (c_strcasecmp (mchksum->type, "sha256") == 0
648 || c_strcasecmp (mchksum->type, "sha-256") == 0)
649 {
650 sha256_stream (local_file, sha256);
651 wg_hex_to_string (sha256_txt, sha256, SHA256_DIGEST_SIZE);
652 DEBUGP (("Computed hash: %s\n", sha256_txt));
653 if (!strcmp (sha256_txt, mchksum->hash))
654 hash_ok = true;
655 }
656 else if (c_strcasecmp (mchksum->type, "sha384") == 0
657 || c_strcasecmp (mchksum->type, "sha-384") == 0)
658 {
659 sha384_stream (local_file, sha384);
660 wg_hex_to_string (sha384_txt, sha384, SHA384_DIGEST_SIZE);
661 DEBUGP (("Computed hash: %s\n", sha384_txt));
662 if (!strcmp (sha384_txt, mchksum->hash))
663 hash_ok = true;
664 }
665 else if (c_strcasecmp (mchksum->type, "sha512") == 0
666 || c_strcasecmp (mchksum->type, "sha-512") == 0)
667 {
668 sha512_stream (local_file, sha512);
669 wg_hex_to_string (sha512_txt, sha512, SHA512_DIGEST_SIZE);
670 DEBUGP (("Computed hash: %s\n", sha512_txt));
671 if (!strcmp (sha512_txt, mchksum->hash))
672 hash_ok = true;
673 }
674
675 if (hash_ok)
676 {
677 logputs (LOG_VERBOSE,
678 _("Checksum matches.\n"));
679 }
680 else
681 {
682 logprintf (LOG_NOTQUIET,
683 _("Checksum mismatch for file %s.\n"),
684 quote (destname));
685 }
686
687 /* Stop as soon as we checked the supported checksum. */
688 break;
689 } /* Iterate over available checksums. */
690 fclose (local_file);
691 local_file = NULL;
692
693 if (!hash_ok)
694 continue;
695
696 sig_status = 0; /* Not verified. */
697
698 #ifdef HAVE_GPGME
699 /* Check the crypto signature.
700
701 Note that the signatures from Metalink in XML will not be
702 parsed when using libmetalink version older than 0.1.3.
703 Metalink-over-HTTP is not affected by this problem. */
704 if (mfile->signature)
705 {
706 metalink_signature_t *msig = mfile->signature;
707 gpgme_error_t gpgerr;
708 gpgme_ctx_t gpgctx;
709 gpgme_data_t gpgsigdata, gpgdata;
710 gpgme_verify_result_t gpgres;
711 gpgme_signature_t gpgsig;
712 int fd;
713
714 /* Initialize the library - as name suggests. */
715 gpgme_check_version (NULL);
716
717 /* Open data file. */
718 fd = open (destname, O_RDONLY);
719 if (fd == -1)
720 {
721 logputs (LOG_NOTQUIET,
722 _("Could not open downloaded file for signature "
723 "verification.\n"));
724 goto gpg_skip_verification;
725 }
726
727 /* Assign file descriptor to GPG data structure. */
728 gpgerr = gpgme_data_new_from_fd (&gpgdata, fd);
729 if (gpgerr != GPG_ERR_NO_ERROR)
730 {
731 logprintf (LOG_NOTQUIET,
732 "GPGME data_new_from_fd: %s\n",
733 gpgme_strerror (gpgerr));
734 goto gpg_skip_verification;
735 }
736
737 /* Prepare new GPGME context. */
738 gpgerr = gpgme_new (&gpgctx);
739 if (gpgerr != GPG_ERR_NO_ERROR)
740 {
741 logprintf (LOG_NOTQUIET,
742 "GPGME new: %s\n",
743 gpgme_strerror (gpgerr));
744 gpgme_data_release (gpgdata);
745 goto gpg_skip_verification;
746 }
747
748 DEBUGP (("Verifying signature %s:\n%s\n",
749 quote (msig->mediatype),
750 msig->signature));
751
752 /* Check signature type. */
753 if (strcmp (msig->mediatype, "application/pgp-signature"))
754 {
755 /* Unsupported signature type. */
756 gpgme_release (gpgctx);
757 gpgme_data_release (gpgdata);
758 goto gpg_skip_verification;
759 }
760
761 gpgerr = gpgme_set_protocol (gpgctx, GPGME_PROTOCOL_OpenPGP);
762 if (gpgerr != GPG_ERR_NO_ERROR)
763 {
764 logprintf (LOG_NOTQUIET,
765 "GPGME set_protocol: %s\n",
766 gpgme_strerror (gpgerr));
767 gpgme_release (gpgctx);
768 gpgme_data_release (gpgdata);
769 goto gpg_skip_verification;
770 }
771
772 /* Load the signature. */
773 gpgerr = gpgme_data_new_from_mem (&gpgsigdata,
774 msig->signature,
775 strlen (msig->signature),
776 0);
777 if (gpgerr != GPG_ERR_NO_ERROR)
778 {
779 logprintf (LOG_NOTQUIET,
780 _("GPGME data_new_from_mem: %s\n"),
781 gpgme_strerror (gpgerr));
782 gpgme_release (gpgctx);
783 gpgme_data_release (gpgdata);
784 goto gpg_skip_verification;
785 }
786
787 /* Verify the signature. */
788 gpgerr = gpgme_op_verify (gpgctx, gpgsigdata, gpgdata, NULL);
789 if (gpgerr != GPG_ERR_NO_ERROR)
790 {
791 logprintf (LOG_NOTQUIET,
792 _("GPGME op_verify: %s\n"),
793 gpgme_strerror (gpgerr));
794 gpgme_data_release (gpgsigdata);
795 gpgme_release (gpgctx);
796 gpgme_data_release (gpgdata);
797 goto gpg_skip_verification;
798 }
799
800 /* Check the results. */
801 gpgres = gpgme_op_verify_result (gpgctx);
802 if (!gpgres)
803 {
804 logputs (LOG_NOTQUIET,
805 _("GPGME op_verify_result: NULL\n"));
806 gpgme_data_release (gpgsigdata);
807 gpgme_release (gpgctx);
808 gpgme_data_release (gpgdata);
809 goto gpg_skip_verification;
810 }
811
812 /* The list is null-terminated. */
813 for (gpgsig = gpgres->signatures; gpgsig; gpgsig = gpgsig->next)
814 {
815 DEBUGP (("Checking signature %s\n", gpgsig->fpr));
816
817 if (gpgsig->summary
818 & (GPGME_SIGSUM_VALID | GPGME_SIGSUM_GREEN))
819 {
820 logputs (LOG_VERBOSE,
821 _("Signature validation succeeded.\n"));
822 sig_status = 1;
823 break;
824 }
825
826 if (gpgsig->summary & GPGME_SIGSUM_RED)
827 {
828 logputs (LOG_NOTQUIET,
829 _("Invalid signature. Rejecting resource.\n"));
830 sig_status = -1;
831 break;
832 }
833
834 if (gpgsig->summary == 0
835 && (gpgsig->status & 0xFFFF) == GPG_ERR_NO_ERROR)
836 {
837 logputs (LOG_VERBOSE,
838 _("Data matches signature, but signature "
839 "is not trusted.\n"));
840 }
841
842 if ((gpgsig->status & 0xFFFF) != GPG_ERR_NO_ERROR)
843 {
844 logprintf (LOG_NOTQUIET,
845 "GPGME: %s\n",
846 gpgme_strerror (gpgsig->status & 0xFFFF));
847 }
848 }
849 gpgme_data_release (gpgsigdata);
850 gpgme_release (gpgctx);
851 gpgme_data_release (gpgdata);
852 gpg_skip_verification:
853 if (fd != -1)
854 close (fd);
855 } /* endif (mfile->signature) */
856 #endif
857 /* Stop if file was downloaded with success. */
858 if (sig_status >= 0)
859 break;
860 } /* endif RETR_OK. */
861 } /* Iterate over resources. */
862
863 if (!mfile->checksums)
864 {
865 logprintf (LOG_NOTQUIET, _("No checksums found.\n"));
866 retr_err = METALINK_CHKSUM_ERROR;
867 }
868
869 if (retr_err != RETROK)
870 {
871 logprintf (LOG_VERBOSE, _("Failed to download %s. Skipping resource.\n"),
872 quote (destname ? destname : safename));
873 }
874 else if (!size_ok)
875 {
876 retr_err = METALINK_SIZE_ERROR;
877 logprintf (LOG_NOTQUIET,
878 _("File %s retrieved but size does not match. "
879 "\n"), quote (destname));
880 }
881 else if (!hash_ok)
882 {
883 retr_err = METALINK_CHKSUM_ERROR;
884 logprintf (LOG_NOTQUIET,
885 _("File %s retrieved but checksum does not match. "
886 "\n"), quote (destname));
887 }
888 #ifdef HAVE_GPGME
889 /* Signature will be only validated if hash check was successful. */
890 else if (sig_status < 0)
891 {
892 retr_err = METALINK_SIG_ERROR;
893 logprintf (LOG_NOTQUIET,
894 _("File %s retrieved but signature does not match. "
895 "\n"), quote (destname));
896 }
897 #endif
898 last_retr_err = retr_err == RETROK ? last_retr_err : retr_err;
899
900 /* Rename the file if error encountered; remove if option specified.
901 Note: the file has been downloaded using *_loop. Therefore, it
902 is not necessary to keep the file for continuated download. */
903 if (((retr_err != RETROK && !opt.always_rest) || opt.delete_after)
904 && destname != NULL && file_exists_p (destname, NULL))
905 {
906 badhash_or_remove (destname);
907 }
908 if (output_stream)
909 {
910 fclose (output_stream);
911 output_stream = NULL;
912 }
913 xfree (destname);
914 xfree (filename);
915 xfree (trsrname);
916 xfree (planname);
917 } /* Iterate over files. */
918
919 /* Restore original values. */
920 opt.output_document = _output_document;
921 output_stream_regular = _output_stream_regular;
922 output_stream = _output_stream;
923
924 return last_retr_err;
925 }
926
927 /*
928 Replace/remove the basename of a file name.
929
930 The file name is permanently modified.
931
932 Always set NAME to a string, even an empty one.
933
934 Use REF's basename as replacement. If REF is NULL or if it doesn't
935 provide a valid basename candidate, then remove NAME's basename.
936 */
937 void
replace_metalink_basename(char ** name,char * ref)938 replace_metalink_basename (char **name, char *ref)
939 {
940 int n;
941 char *p, *new, *basename;
942
943 if (!name)
944 return;
945
946 /* Strip old basename. */
947 if (*name)
948 {
949 basename = last_component (*name);
950
951 if (basename == *name)
952 xfree (*name);
953 else
954 *basename = '\0';
955 }
956
957 /* New basename from file name reference. */
958 if (ref)
959 ref = last_component (ref);
960
961 /* Replace the old basename. */
962 new = aprintf ("%s%s", *name ? *name : "", ref ? ref : "");
963 xfree (*name);
964 *name = new;
965
966 /* Remove prefix drive letters if required, i.e. when in w32
967 environments. */
968 p = new;
969 while (p[0] != '\0')
970 {
971 while ((n = FILE_SYSTEM_PREFIX_LEN (p)) > 0)
972 p += n;
973
974 if (p != new)
975 {
976 while (ISSLASH (p[0]))
977 ++p;
978 new = p;
979 continue;
980 }
981
982 break;
983 }
984
985 if (*name != new)
986 {
987 new = xstrdup (new);
988 xfree (*name);
989 *name = new;
990 }
991 }
992
993 /*
994 Strip the directory components from the given name.
995
996 Return a pointer to the end of the leading directory components.
997 Return NULL if the resulting name is unsafe or invalid.
998
999 Due to security issues posed by saving files with unsafe names, here
1000 the use of libmetalink's metalink_check_safe_path() is enforced. If
1001 this appears redundant because the given name was already verified,
1002 just remember to never underestimate unsafe file names.
1003 */
1004 char *
get_metalink_basename(char * name)1005 get_metalink_basename (char *name)
1006 {
1007 int n;
1008 char *basename;
1009
1010 if (!name)
1011 return NULL;
1012
1013 basename = last_component (name);
1014
1015 while ((n = FILE_SYSTEM_PREFIX_LEN (basename)) > 0)
1016 basename += n;
1017
1018 return metalink_check_safe_path (basename) ? basename : NULL;
1019 }
1020
1021 /*
1022 Append a separator and a numeric suffix to a string.
1023
1024 The string is permanently modified.
1025 */
1026 void
append_suffix_number(char ** str,const char * sep,wgint num)1027 append_suffix_number (char **str, const char *sep, wgint num)
1028 {
1029 char *new, buf[24];
1030
1031 number_to_string (buf, num);
1032 new = aprintf ("%s%s%s", *str ? *str : "", sep ? sep : "", buf);
1033 xfree (*str);
1034 *str = new;
1035 }
1036
1037 /*
1038 Remove the string's trailing/leading whitespaces and line breaks.
1039
1040 The string is permanently modified.
1041 */
1042 void
clean_metalink_string(char ** str)1043 clean_metalink_string (char **str)
1044 {
1045 int c;
1046 size_t len;
1047 char *new, *beg, *end;
1048
1049 if (!str || !*str)
1050 return;
1051
1052 beg = *str;
1053
1054 while ((c = *beg) && (c == '\n' || c == '\r' || c == '\t' || c == ' '))
1055 beg++;
1056
1057 end = beg;
1058
1059 /* To not truncate a string containing spaces, search the first '\r'
1060 or '\n' which ipotetically marks the end of the string. */
1061 while ((c = *end) && (c != '\r') && (c != '\n'))
1062 end++;
1063
1064 /* If we are at the end of the string, search the first legit
1065 character going backward. */
1066 if (*end == '\0')
1067 while ((c = *(end - 1)) && (c == '\n' || c == '\r' || c == '\t' || c == ' '))
1068 end--;
1069
1070 len = end - beg;
1071
1072 new = xmemdup0 (beg, len);
1073 xfree (*str);
1074 *str = new;
1075 }
1076
1077 /*
1078 Remove the quotation surrounding a string.
1079
1080 The string is permanently modified.
1081 */
1082 void
dequote_metalink_string(char ** str)1083 dequote_metalink_string (char **str)
1084 {
1085 char *new;
1086 size_t str_len;
1087
1088 if (!str || !*str || ((*str)[0] != '\"' && (*str)[0] != '\''))
1089 return;
1090
1091 str_len = strlen (*str); /* current string length */
1092
1093 /* Verify if the current string is surrounded by quotes. */
1094 if (str_len < 2 || (*str)[0] != (*str)[str_len - 1])
1095 return;
1096
1097 /* Dequoted string. */
1098 new = xmemdup0 (*str + 1, str_len - 2);
1099 xfree (*str);
1100 *str = new;
1101 }
1102
1103 /* Append the suffix ".badhash" to the file NAME, except without
1104 overwriting an existing file with that name and suffix. */
1105 void
badhash_suffix(char * name)1106 badhash_suffix (char *name)
1107 {
1108 char *bhash, *uname;
1109
1110 bhash = concat_strings (name, ".badhash", (char *)0);
1111 uname = unique_name (bhash);
1112
1113 logprintf (LOG_VERBOSE, _("Renaming %s to %s.\n"),
1114 quote_n (0, name), quote_n (1, uname));
1115
1116 if (link (name, uname))
1117 logprintf (LOG_NOTQUIET, "link: %s\n", strerror (errno));
1118 else if (unlink (name))
1119 logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
1120
1121 xfree (bhash);
1122 xfree (uname);
1123 }
1124
1125 /* Append the suffix ".badhash" to the file NAME, except without
1126 overwriting an existing file with that name and suffix.
1127
1128 Remove the file NAME if the option --delete-after is specified, or
1129 if the option --keep-badhash isn't set. */
1130 void
badhash_or_remove(char * name)1131 badhash_or_remove (char *name)
1132 {
1133 if (opt.delete_after || !opt.keep_badhash)
1134 {
1135 logprintf (LOG_VERBOSE, _("Removing %s.\n"), quote (name));
1136 if (unlink (name))
1137 logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
1138 }
1139 else
1140 {
1141 badhash_suffix(name);
1142 }
1143 }
1144
1145 /*
1146 Simple file fetch.
1147
1148 Set DESTNAME to the name of the saved file.
1149
1150 Resume previous download if RESUME is true. To disable
1151 Metalink/HTTP, set METALINK_HTTP to false.
1152 */
1153 uerr_t
fetch_metalink_file(const char * url_str,bool resume,bool metalink_http,const char * filename,char ** destname)1154 fetch_metalink_file (const char *url_str,
1155 bool resume, bool metalink_http,
1156 const char *filename, char **destname)
1157 {
1158 FILE *_output_stream = output_stream;
1159 bool _output_stream_regular = output_stream_regular;
1160 char *_output_document = opt.output_document;
1161 bool _metalink_http = opt.metalink_over_http;
1162
1163 char *local_file = NULL;
1164
1165 uerr_t retr_err = URLERROR;
1166
1167 struct iri *iri;
1168 struct url *url;
1169 int url_err;
1170
1171 /* Parse the URL. */
1172 iri = iri_new ();
1173 set_uri_encoding (iri, opt.locale, true);
1174 url = url_parse (url_str, &url_err, iri, false);
1175
1176 if (!url)
1177 {
1178 char *error = url_error (url_str, url_err);
1179 logprintf (LOG_NOTQUIET, "%s: %s.\n", url_str, error);
1180 inform_exit_status (retr_err);
1181 iri_free (iri);
1182 xfree (error);
1183 return retr_err;
1184 }
1185
1186 output_stream = NULL;
1187
1188 if (resume)
1189 /* continue previous download */
1190 output_stream = fopen (filename, "ab");
1191 else
1192 /* create a file with an unique name */
1193 output_stream = unique_create (filename, true, &local_file);
1194
1195 output_stream_regular = true;
1196
1197 /*
1198 If output_stream is NULL, the file couldn't be created/opened.
1199 This could be due to the impossibility to create a directory tree:
1200 * stdio.h (fopen)
1201 * src/utils.c (unique_create)
1202
1203 A call to retrieve_url() can indirectly create a directory tree,
1204 when opt.output_document is set to the destination file name and
1205 output_stream is left to NULL:
1206 * src/http.c (open_output_stream): If output_stream is NULL,
1207 create the destination opt.output_document "path/file"
1208 */
1209 if (!local_file)
1210 local_file = xstrdup (filename);
1211
1212 /* Store the real file name for displaying in messages, and for
1213 proper "path/file" handling. */
1214 opt.output_document = local_file;
1215
1216 opt.metalink_over_http = metalink_http;
1217
1218 DEBUGP (("Storing to %s\n", local_file));
1219 retr_err = retrieve_url (url, url_str, NULL, NULL,
1220 NULL, NULL, opt.recursive, iri, false);
1221
1222 if (retr_err == RETROK)
1223 {
1224 if (destname)
1225 *destname = local_file;
1226 else
1227 xfree (local_file);
1228 }
1229
1230 if (output_stream)
1231 {
1232 fclose (output_stream);
1233 output_stream = NULL;
1234 }
1235
1236 opt.metalink_over_http = _metalink_http;
1237 opt.output_document = _output_document;
1238 output_stream_regular = _output_stream_regular;
1239 output_stream = _output_stream;
1240
1241 inform_exit_status (retr_err);
1242
1243 iri_free (iri);
1244 url_free (url);
1245
1246 return retr_err;
1247 }
1248
metalink_res_cmp(const void * v1,const void * v2)1249 int metalink_res_cmp (const void* v1, const void* v2)
1250 {
1251 const metalink_resource_t *res1 = *(metalink_resource_t **) v1,
1252 *res2 = *(metalink_resource_t **) v2;
1253 if (res1->preference != res2->preference)
1254 return res2->preference - res1->preference;
1255 if (res1->priority != res2->priority)
1256 return res1->priority - res2->priority;
1257 if (opt.preferred_location)
1258 {
1259 int cmp = 0;
1260 if (res1->location &&
1261 !c_strcasecmp (opt.preferred_location, res1->location))
1262 cmp -= 1;
1263 if (res2->location &&
1264 !c_strcasecmp (opt.preferred_location, res2->location))
1265 cmp += 1;
1266 return cmp;
1267 }
1268 return 0;
1269 }
1270
metalink_meta_cmp(const void * v1,const void * v2)1271 int metalink_meta_cmp (const void* v1, const void* v2)
1272 {
1273 const metalink_metaurl_t *meta1 = *(metalink_metaurl_t **) v1,
1274 *meta2 = *(metalink_metaurl_t **) v2;
1275 if (meta1->priority != meta2->priority)
1276 return meta1->priority - meta2->priority;
1277 return 0;
1278 }
1279
1280 /*
1281 Find value of given key. This is intended for Link header, but will
1282 work with any header that uses ';' as field separator and '=' as key-value
1283 separator.
1284
1285 Link = "Link" ":" #link-value
1286 link-value = "<" URI-Reference ">" *( ";" link-param )
1287 link-param = ( ( "rel" "=" relation-types )
1288 | ( "anchor" "=" <"> URI-Reference <"> )
1289 | ( "rev" "=" relation-types )
1290 | ( "hreflang" "=" Language-Tag )
1291 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
1292 | ( "title" "=" quoted-string )
1293 | ( "title*" "=" ext-value )
1294 | ( "type" "=" ( media-type | quoted-mt ) )
1295 | ( link-extension ) )
1296 link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
1297 | ( ext-name-star "=" ext-value )
1298 ext-name-star = parmname "*" ; reserved for RFC2231-profiled
1299 ; extensions. Whitespace NOT
1300 ; allowed in between.
1301 ptoken = 1*ptokenchar
1302 ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
1303 | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
1304 | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
1305 | "[" | "]" | "^" | "_" | "`" | "{" | "|"
1306 | "}" | "~"
1307 media-type = type-name "/" subtype-name
1308 quoted-mt = <"> media-type <">
1309 relation-types = relation-type
1310 | <"> relation-type *( 1*SP relation-type ) <">
1311 relation-type = reg-rel-type | ext-rel-type
1312 reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
1313 ext-rel-type = URI
1314
1315 See more: rfc5988
1316 */
1317 bool
find_key_value(const char * start,const char * end,const char * key,char ** value)1318 find_key_value (const char *start, const char *end, const char *key, char **value)
1319 {
1320 const char *eq;
1321 size_t key_len = strlen (key);
1322 const char *val_beg, *val_end;
1323 const char *key_beg;
1324
1325 key_beg = start;
1326
1327 while (key_beg + key_len + 1 < end)
1328 {
1329 /* Skip whitespaces. */
1330 while (key_beg + key_len + 1 < end && c_isspace (*key_beg))
1331 key_beg++;
1332 if (strncmp (key_beg, key, key_len))
1333 {
1334 /* Find next token. */
1335 while (key_beg + key_len + 1 < end && *key_beg != ';')
1336 key_beg++;
1337 key_beg++;
1338 continue;
1339 }
1340 else
1341 {
1342 /* Find equals sign. */
1343 eq = key_beg + key_len;
1344 while (eq < end && c_isspace (*eq))
1345 eq++;
1346 if (eq == end)
1347 return false;
1348 if (*eq != '=')
1349 {
1350 key_beg++;
1351 continue;
1352 }
1353
1354 val_beg = eq + 1;
1355 while (val_beg < end && c_isspace (*val_beg))
1356 val_beg++;
1357 if (val_beg == end)
1358 return false;
1359 val_end = val_beg + 1;
1360 while (val_end < end && *val_end != ';' && !c_isspace (*val_end))
1361 val_end++;
1362 *value = xstrndup (val_beg, val_end - val_beg);
1363 dequote_metalink_string (value);
1364 return true;
1365 }
1366 }
1367 *value = NULL;
1368 return false;
1369 }
1370
1371 /* This is to check if given token exists in HTTP header. Tokens are
1372 separated by ';'. */
1373 bool
has_key(const char * start,const char * end,const char * key)1374 has_key (const char *start, const char *end, const char *key)
1375 {
1376 const char *pos; /* Here would the token start. */
1377 size_t key_len = strlen (key);
1378
1379 pos = start;
1380 while (pos + key_len <= end)
1381 {
1382 /* Skip whitespaces at beginning. */
1383 while (pos + key_len <= end && c_isspace (*pos))
1384 pos++;
1385
1386 /* Does the prefix of pos match our key? */
1387 if (strncmp (key, pos, key_len))
1388 {
1389 /* This was not a match.
1390 Skip all characters until beginning of next token. */
1391 while (pos + key_len <= end && *pos != ';')
1392 pos++;
1393 pos++;
1394 continue;
1395 }
1396
1397 /* key is prefix of pos. Is it the exact token or just a prefix? */
1398 pos += key_len;
1399 while (pos < end && c_isspace (*pos))
1400 pos++;
1401 if (pos == end || *pos == ';')
1402 return true;
1403
1404 /* This was not a match (just a prefix).
1405 Skip all characters until beginning of next token. */
1406 while (pos + key_len <= end && *pos != ';')
1407 pos++;
1408 pos++;
1409 }
1410 return false;
1411 }
1412
1413 /* Find all key=value pairs delimited with ';' or ','. This is intended for
1414 Digest header parsing.
1415 The usage is:
1416
1417 const char *pos;
1418 for (pos = header_beg; pos = find_key_values (pos, header_end, &key, &val); pos++)
1419 {
1420 ...
1421 }
1422
1423 */
1424 const char *
find_key_values(const char * start,const char * end,char ** key,char ** value)1425 find_key_values (const char *start, const char *end, char **key, char **value)
1426 {
1427 const char *key_start, *key_end;
1428 const char *eq;
1429 const char *val_start, *val_end;
1430
1431 eq = start;
1432 while (eq < end && *eq != '=')
1433 {
1434 /* Skip tokens without =value part. */
1435 if (*eq == ';' || *eq == ',')
1436 start = eq + 1;
1437 eq++;
1438 }
1439
1440 if (eq >= end)
1441 return NULL;
1442
1443 key_start = start;
1444 while (key_start < eq && c_isspace (*key_start))
1445 key_start++;
1446
1447 key_end = eq - 1;
1448 while (key_end > key_start && c_isspace (*key_end))
1449 key_end--;
1450 key_end++;
1451
1452 val_start = eq + 1;
1453 while (val_start < end && c_isspace (*val_start))
1454 val_start++;
1455
1456 val_end = val_start;
1457
1458 while (val_end < end && *val_end != ';' &&
1459 *val_end != ',' && !c_isspace (*val_end))
1460 val_end++;
1461
1462 *key = xstrndup (key_start, key_end - key_start);
1463 *value = xstrndup (val_start, val_end - val_start);
1464 dequote_metalink_string (value);
1465
1466 /* Skip trailing whitespaces. */
1467 while (val_end < end && c_isspace (*val_end))
1468 val_end++;
1469
1470 return val_end;
1471 }
1472
1473 #ifdef TESTING
1474 const char *
test_find_key_values(void)1475 test_find_key_values (void)
1476 {
1477 static const char *header_data = "key1=val1;key2=\"val2\" ;key3=val3; key4=val4"\
1478 " ; key5=val5;key6 ='val6';key7= val7; "\
1479 "key8 = val8 ; key9 = \"val9\" "\
1480 " ,key10= 'val10',key11,key12=val12";
1481 static const struct
1482 {
1483 const char *key;
1484 const char *val;
1485 } test_array[] =
1486 {
1487 { "key1", "val1" },
1488 { "key2", "val2" },
1489 { "key3", "val3" },
1490 { "key4", "val4" },
1491 { "key5", "val5" },
1492 { "key6", "val6" },
1493 { "key7", "val7" },
1494 { "key8", "val8" },
1495 { "key9", "val9" },
1496 { "key10", "val10" },
1497 { "key12", "val12" }
1498 };
1499 const char *pos;
1500 char *key, *value;
1501 size_t i = 0;
1502
1503 for (pos = header_data; (pos = find_key_values (pos,
1504 header_data + strlen (header_data),
1505 &key, &value)); pos++)
1506 {
1507 mu_assert ("test_find_key_values: wrong result",
1508 !strcmp (test_array[i].val, value) &&
1509 !strcmp (test_array[i].key, key));
1510 xfree (key);
1511 xfree (value);
1512 i++;
1513 }
1514
1515 return NULL;
1516 }
1517
1518 const char *
test_find_key_value(void)1519 test_find_key_value (void)
1520 {
1521 static const char *header_data = "key1=val1;key2=val2 ;key3='val3'; key4=val4"\
1522 " ; key5='val5';key6 =val6;key7= \"val7\"; "\
1523 "key8 = \"val8\" ; key9 = val9 ";
1524 static const struct
1525 {
1526 const char *key;
1527 const char *val;
1528 bool result;
1529 } test_array[] =
1530 {
1531 { "key1", "val1", true },
1532 { "key2", "val2", true },
1533 { "key3", "val3", true },
1534 { "key4", "val4", true },
1535 { "key5", "val5", true },
1536 { "key6", "val6", true },
1537 { "key7", "val7", true },
1538 { "key8", "val8", true },
1539 { "key9", "val9", true },
1540 { "key10", NULL, false },
1541 { "ey1", NULL, false },
1542 { "dey1", NULL, false }
1543 };
1544 size_t i;
1545
1546 for (i=0; i < countof (test_array); ++i)
1547 {
1548 bool result;
1549 char *value;
1550
1551 result = find_key_value (header_data,
1552 header_data + strlen(header_data),
1553 test_array[i].key, &value);
1554
1555 mu_assert ("test_find_key_value: wrong result",
1556 result == test_array[i].result &&
1557 ((!test_array[i].result && !value) ||
1558 !strcmp (value, test_array[i].val)));
1559
1560 xfree (value);
1561 }
1562
1563 return NULL;
1564 }
1565
1566 const char *
test_has_key(void)1567 test_has_key (void)
1568 {
1569 static const char *header_data = "key1=val2;token1;xyz; token2;xyz;token3 ;"\
1570 "xyz; token4 ;xyz; token5 ";
1571 struct
1572 {
1573 const char *token;
1574 bool result;
1575 } test_array[] =
1576 {
1577 { "key1=val2", true },
1578 { "token1", true },
1579 { "token2", true },
1580 { "token3", true },
1581 { "token4", true },
1582 { "token5", true },
1583 { "token6", false },
1584 { "oken1", false },
1585 { "poken1", false },
1586 { "key1=val2", true }
1587 };
1588 size_t i;
1589
1590 for (i = 0; i < countof (test_array); ++i)
1591 mu_assert ("test_has_key: wrong result",
1592 has_key (header_data, header_data + strlen (header_data),
1593 test_array[i].token) == test_array[i].result);
1594
1595 return NULL;
1596 }
1597 #endif
1598
1599 #endif /* HAVE_METALINK */
1600