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