summaryrefslogtreecommitdiffstats
path: root/src/metalink.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/metalink.c1599
1 files changed, 1599 insertions, 0 deletions
diff --git a/src/metalink.c b/src/metalink.c
new file mode 100644
index 0000000..47c8acc
--- /dev/null
+++ b/src/metalink.c
@@ -0,0 +1,1599 @@
+/* Metalink module.
+ Copyright (C) 2015, 2018 Free Software Foundation, Inc.
+
+This file is part of GNU Wget.
+
+GNU Wget is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or (at
+your option) any later version.
+
+GNU Wget is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Wget. If not, see <http://www.gnu.org/licenses/>.
+
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work. */
+
+#include "wget.h"
+#ifdef HAVE_METALINK
+
+#include "metalink.h"
+#include "retr.h"
+#include "exits.h"
+#include "utils.h"
+#include "md2.h"
+#include "md4.h"
+#include "md5.h"
+#include "sha1.h"
+#include "sha256.h"
+#include "sha512.h"
+#include "dosname.h"
+#include "xmemdup0.h"
+#include "xstrndup.h"
+#include "c-strcase.h"
+#include <errno.h>
+#include <unistd.h> /* For unlink. */
+#include <metalink/metalink_parser.h>
+#ifdef HAVE_GPGME
+#include <gpgme.h>
+#include <fcntl.h> /* For open and close. */
+#endif
+
+#ifdef TESTING
+#include "../tests/unit-tests.h"
+#endif
+
+/* Loop through all files in metalink structure and retrieve them.
+ Returns RETROK if all files were downloaded.
+ Returns last retrieval error (from retrieve_url) if some files
+ could not be downloaded. */
+uerr_t
+retrieve_from_metalink (const metalink_t* metalink)
+{
+ metalink_file_t **mfile_ptr;
+ uerr_t last_retr_err = RETROK; /* Store last encountered retrieve error. */
+
+ FILE *_output_stream = output_stream;
+ bool _output_stream_regular = output_stream_regular;
+ char *_output_document = opt.output_document;
+
+ /* metalink file counter */
+ unsigned mfc = 0;
+
+ /* metalink retrieval type */
+ const char *metatpy = metalink->origin ? "Metalink/HTTP" : "Metalink/XML";
+
+ /* metalink mother source */
+ char *metasrc = metalink->origin ? metalink->origin : opt.input_metalink;
+
+ DEBUGP (("Retrieving from Metalink %s\n", quote (metasrc)));
+
+ /* No files to download. */
+ if (!metalink->files)
+ return RETROK;
+
+ if (opt.output_document)
+ {
+ /* We cannot support output_document as we need to compute checksum
+ of downloaded file, and to remove it if the checksum is bad. */
+ logputs (LOG_NOTQUIET,
+ _("-O not supported for metalink download. Ignoring.\n"));
+ }
+
+ for (mfile_ptr = metalink->files; *mfile_ptr; mfile_ptr++)
+ {
+ metalink_file_t *mfile = *mfile_ptr;
+ metalink_resource_t **mres_ptr;
+ char *planname = NULL;
+ char *trsrname = NULL;
+ char *filename;
+ char *basename;
+ char *safename = NULL;
+ char *destname = NULL;
+ bool size_ok = false;
+ bool hash_ok = false;
+
+ uerr_t retr_err = METALINK_MISSING_RESOURCE;
+
+ /* -1 -> file should be rejected
+ 0 -> could not verify
+ 1 -> verified successfully */
+ char sig_status = 0;
+
+ bool skip_mfile = false;
+
+ output_stream = NULL;
+
+ mfc++;
+
+ /* The directory prefix for opt.metalink_over_http is handled by
+ src/url.c (url_file_name), do not add it a second time. */
+ if (!metalink->origin && opt.dir_prefix && strlen (opt.dir_prefix))
+ planname = aprintf ("%s/%s", opt.dir_prefix, mfile->name);
+ else
+ planname = xstrdup (mfile->name);
+
+ /* With Metalink/HTTP, trust the metalink file name (from cli).
+ With --trust-server-names, trust the Metalink/XML file name,
+ otherwise, use the basename of --input-metalink followed by
+ the metalink file counter as suffix. */
+ if (metalink->origin || opt.trustservernames)
+ {
+ trsrname = xstrdup (mfile->name);
+ }
+ else
+ {
+ trsrname = xstrdup (get_metalink_basename (opt.input_metalink));
+ append_suffix_number (&trsrname, ".#", mfc);
+ }
+
+ /* Add the directory prefix for opt.input_metalink. */
+ if (!metalink->origin && opt.dir_prefix && strlen (opt.dir_prefix))
+ filename = aprintf ("%s/%s", opt.dir_prefix, trsrname);
+ else
+ filename = xstrdup (trsrname);
+
+ /* Enforce libmetalink's metalink_check_safe_path(). */
+ basename = get_metalink_basename (filename);
+ safename = metalink_check_safe_path (filename) ? filename : basename;
+
+ DEBUGP (("Processing metalink file %s...\n", quote (mfile->name)));
+ DEBUGP (("\n"));
+ DEBUGP ((" %s\n", metatpy));
+ DEBUGP (("\n"));
+ DEBUGP ((" --trust-server-names %s\n", opt.trustservernames ? "true" : "false"));
+ DEBUGP ((" --directory-prefix %s\n", quote (opt.dir_prefix ? opt.dir_prefix : "")));
+ DEBUGP (("\n"));
+ DEBUGP ((" Counted metalink file %u\n", mfc));
+ DEBUGP ((" Planned metalink file %s\n", quote (planname ? planname : "")));
+ DEBUGP ((" Trusted metalink file %s\n", quote (trsrname ? trsrname : "")));
+ DEBUGP ((" Current metalink file %s\n", quote (filename ? filename : "")));
+ DEBUGP ((" Cleaned metalink file %s\n", quote (basename ? basename : "")));
+ DEBUGP ((" Secured metalink file %s\n", quote (safename ? safename : "")));
+ DEBUGP (("\n"));
+
+ /* Verify if the planned metalink file name is safe. */
+ if (!safename || strcmp (planname, safename))
+ {
+ logprintf (LOG_NOTQUIET,
+ _("[--trust-server-names %s, --directory-prefix=%s]\n"),
+ (opt.trustservernames ? "true" : "false"),
+ quote (opt.dir_prefix ? opt.dir_prefix : ""));
+ logprintf (LOG_NOTQUIET,
+ _("Planned metalink file: %s\n"),
+ quote (planname ? planname : ""));
+ logprintf (LOG_NOTQUIET,
+ _("Secured metalink file: %s\n"),
+ quote (safename ? safename : ""));
+ if (!safename)
+ {
+ logprintf (LOG_NOTQUIET,
+ _("Rejecting metalink file. Unsafe name.\n"));
+ xfree (planname);
+ xfree (trsrname);
+ xfree (filename);
+ continue;
+ }
+ }
+
+ /* Process the chosen application/metalink4+xml metaurl. */
+ if (opt.metalink_index >= 0)
+ {
+ int _metalink_index = opt.metalink_index;
+
+ metalink_metaurl_t **murl_ptr;
+ int abs_count = 0, meta_count = 0;
+
+ uerr_t x_retr_err = METALINK_MISSING_RESOURCE;
+
+ opt.metalink_index = -1;
+
+ DEBUGP (("Searching application/metalink4+xml ordinal number %d...\n", _metalink_index));
+
+ if (mfile->metaurls && mfile->metaurls[0])
+ for (murl_ptr = mfile->metaurls; *murl_ptr; murl_ptr++)
+ {
+ metalink_t* metaurl_xml;
+ metalink_error_t meta_err;
+ metalink_metaurl_t *murl = *murl_ptr;
+
+ char *_dir_prefix = opt.dir_prefix;
+ char *_input_metalink = opt.input_metalink;
+
+ char *metafile = NULL;
+ char *metadest = NULL;
+ char *metadir = NULL;
+
+ abs_count++;
+
+ if (strcmp (murl->mediatype, "application/metalink4+xml"))
+ continue;
+
+ meta_count++;
+
+ DEBUGP ((" Ordinal number %d: %s\n", meta_count, quote (murl->url)));
+
+ if (_metalink_index > 0)
+ {
+ if (meta_count < _metalink_index)
+ continue;
+ else if (meta_count > _metalink_index)
+ break;
+ }
+
+ logprintf (LOG_NOTQUIET,
+ _("Processing metaurl %s...\n"), quote (murl->url));
+
+ /* Metalink/XML download file name. */
+ metafile = xstrdup (safename);
+
+ if (opt.trustservernames)
+ replace_metalink_basename (&metafile, murl->name ? murl->name : murl->url);
+ else
+ append_suffix_number (&metafile, ".meta#", meta_count);
+
+ if (!metalink_check_safe_path (metafile))
+ {
+ logprintf (LOG_NOTQUIET,
+ _("Rejecting metaurl file %s. Unsafe name.\n"),
+ quote (metafile));
+ xfree (metafile);
+ if (_metalink_index > 0)
+ break;
+ continue;
+ }
+
+ /* For security reasons, always save metalink metaurl
+ files as new unique files. Keep them on failure. */
+ x_retr_err = fetch_metalink_file (murl->url, false, false,
+ metafile, &metadest);
+
+ /* On failure, try the next metalink metaurl. */
+ if (x_retr_err != RETROK)
+ {
+ logprintf (LOG_VERBOSE,
+ _("Failed to download %s. Skipping metaurl.\n"),
+ quote (metadest ? metadest : metafile));
+ inform_exit_status (x_retr_err);
+ xfree (metadest);
+ xfree (metafile);
+ if (_metalink_index > 0)
+ break;
+ continue;
+ }
+
+ /* Parse Metalink/XML. */
+ meta_err = metalink_parse_file (metadest, &metaurl_xml);
+
+ /* On failure, try the next metalink metaurl. */
+ if (meta_err)
+ {
+ logprintf (LOG_NOTQUIET,
+ _("Unable to parse metaurl file %s.\n"), quote (metadest));
+ x_retr_err = METALINK_PARSE_ERROR;
+ inform_exit_status (x_retr_err);
+ xfree (metadest);
+ xfree (metafile);
+ if (_metalink_index > 0)
+ break;
+ continue;
+ }
+
+ /* We need to sort the resources if preferred location
+ was specified by the user. */
+ if (opt.preferred_location && opt.preferred_location[0])
+ {
+ metalink_file_t **x_mfile_ptr;
+ for (x_mfile_ptr = metaurl_xml->files; *x_mfile_ptr; x_mfile_ptr++)
+ {
+ metalink_resource_t **x_mres_ptr;
+ metalink_file_t *x_mfile = *x_mfile_ptr;
+ size_t mres_count = 0;
+
+ for (x_mres_ptr = x_mfile->resources; *x_mres_ptr; x_mres_ptr++)
+ mres_count++;
+
+ stable_sort (x_mfile->resources,
+ mres_count,
+ sizeof (metalink_resource_t *),
+ metalink_res_cmp);
+ }
+ }
+
+ /* Insert the current "Directory Options". */
+ if (metalink->origin)
+ {
+ /* WARNING: Do not use lib/dirname.c (dir_name) to
+ get the directory name, it may append a dot '.'
+ character to the directory name. */
+ metadir = xstrdup (planname);
+ replace_metalink_basename (&metadir, NULL);
+ }
+ else
+ {
+ metadir = xstrdup (opt.dir_prefix);
+ }
+
+ opt.dir_prefix = metadir;
+ opt.input_metalink = metadest;
+
+ x_retr_err = retrieve_from_metalink (metaurl_xml);
+
+ if (x_retr_err != RETROK)
+ logprintf (LOG_NOTQUIET,
+ _("Could not download all resources from %s.\n"),
+ quote (metadest));
+
+ metalink_delete (metaurl_xml);
+ metaurl_xml = NULL;
+
+ opt.input_metalink = _input_metalink;
+ opt.dir_prefix = _dir_prefix;
+
+ xfree (metadir);
+ xfree (metadest);
+ xfree (metafile);
+
+ break;
+ }
+
+ if (x_retr_err != RETROK)
+ logprintf (LOG_NOTQUIET, _("Metaurls processing returned with error.\n"));
+
+ xfree (destname);
+ xfree (filename);
+ xfree (trsrname);
+ xfree (planname);
+
+ opt.output_document = _output_document;
+ output_stream_regular = _output_stream_regular;
+ output_stream = _output_stream;
+
+ opt.metalink_index = _metalink_index;
+
+ return x_retr_err;
+ }
+
+ /* Resources are sorted by priority. */
+ for (mres_ptr = mfile->resources;
+ *mres_ptr && mfile->checksums && !skip_mfile; mres_ptr++)
+ {
+ metalink_resource_t *mres = *mres_ptr;
+ metalink_checksum_t **mchksum_ptr, *mchksum;
+ struct iri *iri;
+ struct url *url;
+ file_stats_t flstats;
+ int url_err;
+
+ clean_metalink_string (&mres->url);
+
+ if (!RES_TYPE_SUPPORTED (mres->type))
+ {
+ logprintf (LOG_VERBOSE,
+ _("Resource type %s not supported, ignoring...\n"),
+ quote (mres->type));
+ continue;
+ }
+
+ /* The file is fully downloaded, but some problems were
+ encountered (checksum failure?). The loop had been
+ continued to switch to the next url. */
+ if (output_stream && retr_err == RETROK)
+ {
+ /* Do not rename/remove a continued file. Skip it. */
+ if (opt.always_rest)
+ {
+ skip_mfile = true;
+ continue;
+ }
+
+ fclose (output_stream);
+ output_stream = NULL;
+ badhash_or_remove (destname);
+ xfree (destname);
+ }
+ else if (!output_stream && destname)
+ {
+ xfree (destname);
+ }
+
+ retr_err = METALINK_RETR_ERROR;
+
+ /* Parse our resource URL. */
+ iri = iri_new ();
+ set_uri_encoding (iri, opt.locale, true);
+ url = url_parse (mres->url, &url_err, iri, false);
+
+ if (!url)
+ {
+ char *error = url_error (mres->url, url_err);
+ logprintf (LOG_NOTQUIET, "%s: %s.\n", mres->url, error);
+ xfree (error);
+ inform_exit_status (URLERROR);
+ iri_free (iri);
+ continue;
+ }
+ else
+ {
+ /* Avoid recursive Metalink from HTTP headers. */
+ bool _metalink_http = opt.metalink_over_http;
+
+ /* If output_stream is not NULL, then we have failed on
+ previous resource and are retrying. Thus, continue
+ with the next resource. Do not close output_stream
+ while iterating over the resources, or the download
+ progress will be lost. */
+ if (output_stream)
+ {
+ DEBUGP (("Previous resource failed, continue with next resource.\n"));
+ }
+ else
+ {
+ /* Assure proper local file name regardless of the URL
+ of particular Metalink resource.
+ To do that we create the local file here and put
+ it as output_stream. We restore the original configuration
+ after we are finished with the file. */
+ if (opt.always_rest)
+ /* continue previous download */
+ output_stream = fopen (safename, "ab");
+ else
+ /* create a file with an unique name */
+ output_stream = unique_create (safename, true, &destname);
+ }
+
+ output_stream_regular = true;
+
+ /*
+ At this point, if output_stream is NULL, the file
+ couldn't be created/opened.
+
+ This happens when the metalink:file has a "path/file"
+ name format and its directory tree cannot be created:
+ * stdio.h (fopen)
+ * src/utils.c (unique_create)
+
+ RFC5854 requires a proper "path/file" format handling,
+ this can be achieved setting opt.output_document while
+ output_stream is left to NULL:
+ * src/http.c (open_output_stream): If output_stream is
+ NULL, create the opt.output_document "path/file"
+ */
+ if (!destname)
+ destname = xstrdup (safename);
+
+ /* Store the real file name for displaying in messages,
+ and for proper RFC5854 "path/file" handling. */
+ opt.output_document = destname;
+
+ opt.metalink_over_http = false;
+ DEBUGP (("Storing to %s\n", destname));
+ retr_err = retrieve_url (url, mres->url, NULL, NULL,
+ NULL, NULL, opt.recursive, iri, false);
+ opt.metalink_over_http = _metalink_http;
+
+ /*
+ Bug: output_stream is NULL, but retrieve_url() somehow
+ created destname.
+
+ Bugfix: point output_stream to destname if it exists.
+ */
+ memset(&flstats, 0, sizeof(flstats));
+ if (!output_stream && file_exists_p (destname, &flstats))
+ output_stream = fopen_stat (destname, "ab", &flstats);
+ }
+ url_free (url);
+ iri_free (iri);
+
+ if (retr_err == RETROK)
+ {
+ FILE *local_file;
+
+ /* Check the digest. */
+ local_file = fopen (destname, "rb");
+ if (!local_file)
+ {
+ logprintf (LOG_NOTQUIET, _("Could not open downloaded file.\n"));
+ continue;
+ }
+
+ size_ok = false;
+ logprintf (LOG_VERBOSE, _("Computing size for %s\n"), quote (destname));
+
+ if (!mfile->size)
+ {
+ size_ok = true;
+ logprintf (LOG_VERBOSE, _("File size not declared. Skipping check.\n"));
+ }
+ else
+ {
+ wgint local_file_size = file_size (destname);
+
+ if (local_file_size == -1)
+ {
+ logprintf (LOG_NOTQUIET, _("Could not get downloaded file's size.\n"));
+ fclose (local_file);
+ local_file = NULL;
+ continue;
+ }
+
+ /* FIXME: what about int64? */
+ DEBUGP (("Declared size: %lld\n", mfile->size));
+ DEBUGP (("Computed size: %lld\n", (long long) local_file_size));
+
+ if (local_file_size != (wgint) mfile->size)
+ {
+ logprintf (LOG_NOTQUIET, _("Size mismatch for file %s.\n"), quote (destname));
+ fclose (local_file);
+ local_file = NULL;
+ continue;
+ }
+ else
+ {
+ size_ok = true;
+ logputs (LOG_VERBOSE, _("Size matches.\n"));
+ }
+ }
+
+ for (mchksum_ptr = mfile->checksums; *mchksum_ptr; mchksum_ptr++)
+ {
+ char md2[MD2_DIGEST_SIZE];
+ char md2_txt[2 * MD2_DIGEST_SIZE + 1];
+
+ char md4[MD4_DIGEST_SIZE];
+ char md4_txt[2 * MD4_DIGEST_SIZE + 1];
+
+ char md5[MD5_DIGEST_SIZE];
+ char md5_txt[2 * MD5_DIGEST_SIZE + 1];
+
+ char sha1[SHA1_DIGEST_SIZE];
+ char sha1_txt[2 * SHA1_DIGEST_SIZE + 1];
+
+ char sha224[SHA224_DIGEST_SIZE];
+ char sha224_txt[2 * SHA224_DIGEST_SIZE + 1];
+
+ char sha256[SHA256_DIGEST_SIZE];
+ char sha256_txt[2 * SHA256_DIGEST_SIZE + 1];
+
+ char sha384[SHA384_DIGEST_SIZE];
+ char sha384_txt[2 * SHA384_DIGEST_SIZE + 1];
+
+ char sha512[SHA512_DIGEST_SIZE];
+ char sha512_txt[2 * SHA512_DIGEST_SIZE + 1];
+
+ hash_ok = false;
+ mchksum = *mchksum_ptr;
+
+ /* I have seen both variants... */
+ if (c_strcasecmp (mchksum->type, "md2")
+ && c_strcasecmp (mchksum->type, "md4")
+ && c_strcasecmp (mchksum->type, "md5")
+ && c_strcasecmp (mchksum->type, "sha1")
+ && c_strcasecmp (mchksum->type, "sha-1")
+ && c_strcasecmp (mchksum->type, "sha224")
+ && c_strcasecmp (mchksum->type, "sha-224")
+ && c_strcasecmp (mchksum->type, "sha256")
+ && c_strcasecmp (mchksum->type, "sha-256")
+ && c_strcasecmp (mchksum->type, "sha384")
+ && c_strcasecmp (mchksum->type, "sha-384")
+ && c_strcasecmp (mchksum->type, "sha512")
+ && c_strcasecmp (mchksum->type, "sha-512"))
+ {
+ DEBUGP (("Ignoring unsupported checksum type %s.\n",
+ quote (mchksum->type)));
+ continue;
+ }
+
+ logprintf (LOG_VERBOSE, _("Computing checksum for %s\n"),
+ quote (destname));
+
+ DEBUGP (("Declared hash: %s\n", mchksum->hash));
+
+ if (c_strcasecmp (mchksum->type, "md2") == 0)
+ {
+ md2_stream (local_file, md2);
+ wg_hex_to_string (md2_txt, md2, MD2_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", md2_txt));
+ if (!strcmp (md2_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "md4") == 0)
+ {
+ md4_stream (local_file, md4);
+ wg_hex_to_string (md4_txt, md4, MD4_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", md4_txt));
+ if (!strcmp (md4_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "md5") == 0)
+ {
+ md5_stream (local_file, md5);
+ wg_hex_to_string (md5_txt, md5, MD5_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", md5_txt));
+ if (!strcmp (md5_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "sha1") == 0
+ || c_strcasecmp (mchksum->type, "sha-1") == 0)
+ {
+ sha1_stream (local_file, sha1);
+ wg_hex_to_string (sha1_txt, sha1, SHA1_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", sha1_txt));
+ if (!strcmp (sha1_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "sha224") == 0
+ || c_strcasecmp (mchksum->type, "sha-224") == 0)
+ {
+ sha224_stream (local_file, sha224);
+ wg_hex_to_string (sha224_txt, sha224, SHA224_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", sha224_txt));
+ if (!strcmp (sha224_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "sha256") == 0
+ || c_strcasecmp (mchksum->type, "sha-256") == 0)
+ {
+ sha256_stream (local_file, sha256);
+ wg_hex_to_string (sha256_txt, sha256, SHA256_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", sha256_txt));
+ if (!strcmp (sha256_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "sha384") == 0
+ || c_strcasecmp (mchksum->type, "sha-384") == 0)
+ {
+ sha384_stream (local_file, sha384);
+ wg_hex_to_string (sha384_txt, sha384, SHA384_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", sha384_txt));
+ if (!strcmp (sha384_txt, mchksum->hash))
+ hash_ok = true;
+ }
+ else if (c_strcasecmp (mchksum->type, "sha512") == 0
+ || c_strcasecmp (mchksum->type, "sha-512") == 0)
+ {
+ sha512_stream (local_file, sha512);
+ wg_hex_to_string (sha512_txt, sha512, SHA512_DIGEST_SIZE);
+ DEBUGP (("Computed hash: %s\n", sha512_txt));
+ if (!strcmp (sha512_txt, mchksum->hash))
+ hash_ok = true;
+ }
+
+ if (hash_ok)
+ {
+ logputs (LOG_VERBOSE,
+ _("Checksum matches.\n"));
+ }
+ else
+ {
+ logprintf (LOG_NOTQUIET,
+ _("Checksum mismatch for file %s.\n"),
+ quote (destname));
+ }
+
+ /* Stop as soon as we checked the supported checksum. */
+ break;
+ } /* Iterate over available checksums. */
+ fclose (local_file);
+ local_file = NULL;
+
+ if (!hash_ok)
+ continue;
+
+ sig_status = 0; /* Not verified. */
+
+#ifdef HAVE_GPGME
+ /* Check the crypto signature.
+
+ Note that the signatures from Metalink in XML will not be
+ parsed when using libmetalink version older than 0.1.3.
+ Metalink-over-HTTP is not affected by this problem. */
+ if (mfile->signature)
+ {
+ metalink_signature_t *msig = mfile->signature;
+ gpgme_error_t gpgerr;
+ gpgme_ctx_t gpgctx;
+ gpgme_data_t gpgsigdata, gpgdata;
+ gpgme_verify_result_t gpgres;
+ gpgme_signature_t gpgsig;
+ int fd;
+
+ /* Initialize the library - as name suggests. */
+ gpgme_check_version (NULL);
+
+ /* Open data file. */
+ fd = open (destname, O_RDONLY);
+ if (fd == -1)
+ {
+ logputs (LOG_NOTQUIET,
+ _("Could not open downloaded file for signature "
+ "verification.\n"));
+ goto gpg_skip_verification;
+ }
+
+ /* Assign file descriptor to GPG data structure. */
+ gpgerr = gpgme_data_new_from_fd (&gpgdata, fd);
+ if (gpgerr != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ "GPGME data_new_from_fd: %s\n",
+ gpgme_strerror (gpgerr));
+ goto gpg_skip_verification;
+ }
+
+ /* Prepare new GPGME context. */
+ gpgerr = gpgme_new (&gpgctx);
+ if (gpgerr != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ "GPGME new: %s\n",
+ gpgme_strerror (gpgerr));
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ DEBUGP (("Verifying signature %s:\n%s\n",
+ quote (msig->mediatype),
+ msig->signature));
+
+ /* Check signature type. */
+ if (strcmp (msig->mediatype, "application/pgp-signature"))
+ {
+ /* Unsupported signature type. */
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ gpgerr = gpgme_set_protocol (gpgctx, GPGME_PROTOCOL_OpenPGP);
+ if (gpgerr != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ "GPGME set_protocol: %s\n",
+ gpgme_strerror (gpgerr));
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ /* Load the signature. */
+ gpgerr = gpgme_data_new_from_mem (&gpgsigdata,
+ msig->signature,
+ strlen (msig->signature),
+ 0);
+ if (gpgerr != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ _("GPGME data_new_from_mem: %s\n"),
+ gpgme_strerror (gpgerr));
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ /* Verify the signature. */
+ gpgerr = gpgme_op_verify (gpgctx, gpgsigdata, gpgdata, NULL);
+ if (gpgerr != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ _("GPGME op_verify: %s\n"),
+ gpgme_strerror (gpgerr));
+ gpgme_data_release (gpgsigdata);
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ /* Check the results. */
+ gpgres = gpgme_op_verify_result (gpgctx);
+ if (!gpgres)
+ {
+ logputs (LOG_NOTQUIET,
+ _("GPGME op_verify_result: NULL\n"));
+ gpgme_data_release (gpgsigdata);
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+ goto gpg_skip_verification;
+ }
+
+ /* The list is null-terminated. */
+ for (gpgsig = gpgres->signatures; gpgsig; gpgsig = gpgsig->next)
+ {
+ DEBUGP (("Checking signature %s\n", gpgsig->fpr));
+
+ if (gpgsig->summary
+ & (GPGME_SIGSUM_VALID | GPGME_SIGSUM_GREEN))
+ {
+ logputs (LOG_VERBOSE,
+ _("Signature validation succeeded.\n"));
+ sig_status = 1;
+ break;
+ }
+
+ if (gpgsig->summary & GPGME_SIGSUM_RED)
+ {
+ logputs (LOG_NOTQUIET,
+ _("Invalid signature. Rejecting resource.\n"));
+ sig_status = -1;
+ break;
+ }
+
+ if (gpgsig->summary == 0
+ && (gpgsig->status & 0xFFFF) == GPG_ERR_NO_ERROR)
+ {
+ logputs (LOG_VERBOSE,
+ _("Data matches signature, but signature "
+ "is not trusted.\n"));
+ }
+
+ if ((gpgsig->status & 0xFFFF) != GPG_ERR_NO_ERROR)
+ {
+ logprintf (LOG_NOTQUIET,
+ "GPGME: %s\n",
+ gpgme_strerror (gpgsig->status & 0xFFFF));
+ }
+ }
+ gpgme_data_release (gpgsigdata);
+ gpgme_release (gpgctx);
+ gpgme_data_release (gpgdata);
+gpg_skip_verification:
+ if (fd != -1)
+ close (fd);
+ } /* endif (mfile->signature) */
+#endif
+ /* Stop if file was downloaded with success. */
+ if (sig_status >= 0)
+ break;
+ } /* endif RETR_OK. */
+ } /* Iterate over resources. */
+
+ if (!mfile->checksums)
+ {
+ logprintf (LOG_NOTQUIET, _("No checksums found.\n"));
+ retr_err = METALINK_CHKSUM_ERROR;
+ }
+
+ if (retr_err != RETROK)
+ {
+ logprintf (LOG_VERBOSE, _("Failed to download %s. Skipping resource.\n"),
+ quote (destname ? destname : safename));
+ }
+ else if (!size_ok)
+ {
+ retr_err = METALINK_SIZE_ERROR;
+ logprintf (LOG_NOTQUIET,
+ _("File %s retrieved but size does not match. "
+ "\n"), quote (destname));
+ }
+ else if (!hash_ok)
+ {
+ retr_err = METALINK_CHKSUM_ERROR;
+ logprintf (LOG_NOTQUIET,
+ _("File %s retrieved but checksum does not match. "
+ "\n"), quote (destname));
+ }
+#ifdef HAVE_GPGME
+ /* Signature will be only validated if hash check was successful. */
+ else if (sig_status < 0)
+ {
+ retr_err = METALINK_SIG_ERROR;
+ logprintf (LOG_NOTQUIET,
+ _("File %s retrieved but signature does not match. "
+ "\n"), quote (destname));
+ }
+#endif
+ last_retr_err = retr_err == RETROK ? last_retr_err : retr_err;
+
+ /* Rename the file if error encountered; remove if option specified.
+ Note: the file has been downloaded using *_loop. Therefore, it
+ is not necessary to keep the file for continuated download. */
+ if (((retr_err != RETROK && !opt.always_rest) || opt.delete_after)
+ && destname != NULL && file_exists_p (destname, NULL))
+ {
+ badhash_or_remove (destname);
+ }
+ if (output_stream)
+ {
+ fclose (output_stream);
+ output_stream = NULL;
+ }
+ xfree (destname);
+ xfree (filename);
+ xfree (trsrname);
+ xfree (planname);
+ } /* Iterate over files. */
+
+ /* Restore original values. */
+ opt.output_document = _output_document;
+ output_stream_regular = _output_stream_regular;
+ output_stream = _output_stream;
+
+ return last_retr_err;
+}
+
+/*
+ Replace/remove the basename of a file name.
+
+ The file name is permanently modified.
+
+ Always set NAME to a string, even an empty one.
+
+ Use REF's basename as replacement. If REF is NULL or if it doesn't
+ provide a valid basename candidate, then remove NAME's basename.
+*/
+void
+replace_metalink_basename (char **name, char *ref)
+{
+ int n;
+ char *p, *new, *basename;
+
+ if (!name)
+ return;
+
+ /* Strip old basename. */
+ if (*name)
+ {
+ basename = last_component (*name);
+
+ if (basename == *name)
+ xfree (*name);
+ else
+ *basename = '\0';
+ }
+
+ /* New basename from file name reference. */
+ if (ref)
+ ref = last_component (ref);
+
+ /* Replace the old basename. */
+ new = aprintf ("%s%s", *name ? *name : "", ref ? ref : "");
+ xfree (*name);
+ *name = new;
+
+ /* Remove prefix drive letters if required, i.e. when in w32
+ environments. */
+ p = new;
+ while (p[0] != '\0')
+ {
+ while ((n = FILE_SYSTEM_PREFIX_LEN (p)) > 0)
+ p += n;
+
+ if (p != new)
+ {
+ while (ISSLASH (p[0]))
+ ++p;
+ new = p;
+ continue;
+ }
+
+ break;
+ }
+
+ if (*name != new)
+ {
+ new = xstrdup (new);
+ xfree (*name);
+ *name = new;
+ }
+}
+
+/*
+ Strip the directory components from the given name.
+
+ Return a pointer to the end of the leading directory components.
+ Return NULL if the resulting name is unsafe or invalid.
+
+ Due to security issues posed by saving files with unsafe names, here
+ the use of libmetalink's metalink_check_safe_path() is enforced. If
+ this appears redundant because the given name was already verified,
+ just remember to never underestimate unsafe file names.
+*/
+char *
+get_metalink_basename (char *name)
+{
+ int n;
+ char *basename;
+
+ if (!name)
+ return NULL;
+
+ basename = last_component (name);
+
+ while ((n = FILE_SYSTEM_PREFIX_LEN (basename)) > 0)
+ basename += n;
+
+ return metalink_check_safe_path (basename) ? basename : NULL;
+}
+
+/*
+ Append a separator and a numeric suffix to a string.
+
+ The string is permanently modified.
+*/
+void
+append_suffix_number (char **str, const char *sep, wgint num)
+{
+ char *new, buf[24];
+
+ number_to_string (buf, num);
+ new = aprintf ("%s%s%s", *str ? *str : "", sep ? sep : "", buf);
+ xfree (*str);
+ *str = new;
+}
+
+/*
+ Remove the string's trailing/leading whitespaces and line breaks.
+
+ The string is permanently modified.
+*/
+void
+clean_metalink_string (char **str)
+{
+ int c;
+ size_t len;
+ char *new, *beg, *end;
+
+ if (!str || !*str)
+ return;
+
+ beg = *str;
+
+ while ((c = *beg) && (c == '\n' || c == '\r' || c == '\t' || c == ' '))
+ beg++;
+
+ end = beg;
+
+ /* To not truncate a string containing spaces, search the first '\r'
+ or '\n' which ipotetically marks the end of the string. */
+ while ((c = *end) && (c != '\r') && (c != '\n'))
+ end++;
+
+ /* If we are at the end of the string, search the first legit
+ character going backward. */
+ if (*end == '\0')
+ while ((c = *(end - 1)) && (c == '\n' || c == '\r' || c == '\t' || c == ' '))
+ end--;
+
+ len = end - beg;
+
+ new = xmemdup0 (beg, len);
+ xfree (*str);
+ *str = new;
+}
+
+/*
+ Remove the quotation surrounding a string.
+
+ The string is permanently modified.
+ */
+void
+dequote_metalink_string (char **str)
+{
+ char *new;
+ size_t str_len;
+
+ if (!str || !*str || ((*str)[0] != '\"' && (*str)[0] != '\''))
+ return;
+
+ str_len = strlen (*str); /* current string length */
+
+ /* Verify if the current string is surrounded by quotes. */
+ if (str_len < 2 || (*str)[0] != (*str)[str_len - 1])
+ return;
+
+ /* Dequoted string. */
+ new = xmemdup0 (*str + 1, str_len - 2);
+ xfree (*str);
+ *str = new;
+}
+
+/* Append the suffix ".badhash" to the file NAME, except without
+ overwriting an existing file with that name and suffix. */
+void
+badhash_suffix (char *name)
+{
+ char *bhash, *uname;
+
+ bhash = concat_strings (name, ".badhash", (char *)0);
+ uname = unique_name (bhash, false);
+
+ logprintf (LOG_VERBOSE, _("Renaming %s to %s.\n"),
+ quote_n (0, name), quote_n (1, uname));
+
+ if (link (name, uname))
+ logprintf (LOG_NOTQUIET, "link: %s\n", strerror (errno));
+ else if (unlink (name))
+ logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
+
+ xfree (bhash);
+ xfree (uname);
+}
+
+/* Append the suffix ".badhash" to the file NAME, except without
+ overwriting an existing file with that name and suffix.
+
+ Remove the file NAME if the option --delete-after is specified, or
+ if the option --keep-badhash isn't set. */
+void
+badhash_or_remove (char *name)
+{
+ if (opt.delete_after || !opt.keep_badhash)
+ {
+ logprintf (LOG_VERBOSE, _("Removing %s.\n"), quote (name));
+ if (unlink (name))
+ logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
+ }
+ else
+ {
+ badhash_suffix(name);
+ }
+}
+
+/*
+ Simple file fetch.
+
+ Set DESTNAME to the name of the saved file.
+
+ Resume previous download if RESUME is true. To disable
+ Metalink/HTTP, set METALINK_HTTP to false.
+*/
+uerr_t
+fetch_metalink_file (const char *url_str,
+ bool resume, bool metalink_http,
+ const char *filename, char **destname)
+{
+ FILE *_output_stream = output_stream;
+ bool _output_stream_regular = output_stream_regular;
+ char *_output_document = opt.output_document;
+ bool _metalink_http = opt.metalink_over_http;
+
+ char *local_file = NULL;
+
+ uerr_t retr_err = URLERROR;
+
+ struct iri *iri;
+ struct url *url;
+ int url_err;
+
+ /* Parse the URL. */
+ iri = iri_new ();
+ set_uri_encoding (iri, opt.locale, true);
+ url = url_parse (url_str, &url_err, iri, false);
+
+ if (!url)
+ {
+ char *error = url_error (url_str, url_err);
+ logprintf (LOG_NOTQUIET, "%s: %s.\n", url_str, error);
+ inform_exit_status (retr_err);
+ iri_free (iri);
+ xfree (error);
+ return retr_err;
+ }
+
+ output_stream = NULL;
+
+ if (resume)
+ /* continue previous download */
+ output_stream = fopen (filename, "ab");
+ else
+ /* create a file with an unique name */
+ output_stream = unique_create (filename, true, &local_file);
+
+ output_stream_regular = true;
+
+ /*
+ If output_stream is NULL, the file couldn't be created/opened.
+ This could be due to the impossibility to create a directory tree:
+ * stdio.h (fopen)
+ * src/utils.c (unique_create)
+
+ A call to retrieve_url() can indirectly create a directory tree,
+ when opt.output_document is set to the destination file name and
+ output_stream is left to NULL:
+ * src/http.c (open_output_stream): If output_stream is NULL,
+ create the destination opt.output_document "path/file"
+ */
+ if (!local_file)
+ local_file = xstrdup (filename);
+
+ /* Store the real file name for displaying in messages, and for
+ proper "path/file" handling. */
+ opt.output_document = local_file;
+
+ opt.metalink_over_http = metalink_http;
+
+ DEBUGP (("Storing to %s\n", local_file));
+ retr_err = retrieve_url (url, url_str, NULL, NULL,
+ NULL, NULL, opt.recursive, iri, false);
+
+ if (retr_err == RETROK)
+ {
+ if (destname)
+ *destname = local_file;
+ else
+ xfree (local_file);
+ }
+
+ if (output_stream)
+ {
+ fclose (output_stream);
+ output_stream = NULL;
+ }
+
+ opt.metalink_over_http = _metalink_http;
+ opt.output_document = _output_document;
+ output_stream_regular = _output_stream_regular;
+ output_stream = _output_stream;
+
+ inform_exit_status (retr_err);
+
+ iri_free (iri);
+ url_free (url);
+
+ return retr_err;
+}
+
+int metalink_res_cmp (const void* v1, const void* v2)
+{
+ const metalink_resource_t *res1 = *(metalink_resource_t **) v1,
+ *res2 = *(metalink_resource_t **) v2;
+ if (res1->preference != res2->preference)
+ return res2->preference - res1->preference;
+ if (res1->priority != res2->priority)
+ return res1->priority - res2->priority;
+ if (opt.preferred_location)
+ {
+ int cmp = 0;
+ if (res1->location &&
+ !c_strcasecmp (opt.preferred_location, res1->location))
+ cmp -= 1;
+ if (res2->location &&
+ !c_strcasecmp (opt.preferred_location, res2->location))
+ cmp += 1;
+ return cmp;
+ }
+ return 0;
+}
+
+int metalink_meta_cmp (const void* v1, const void* v2)
+{
+ const metalink_metaurl_t *meta1 = *(metalink_metaurl_t **) v1,
+ *meta2 = *(metalink_metaurl_t **) v2;
+ if (meta1->priority != meta2->priority)
+ return meta1->priority - meta2->priority;
+ return 0;
+}
+
+/*
+ Find value of given key. This is intended for Link header, but will
+ work with any header that uses ';' as field separator and '=' as key-value
+ separator.
+
+ Link = "Link" ":" #link-value
+ link-value = "<" URI-Reference ">" *( ";" link-param )
+ link-param = ( ( "rel" "=" relation-types )
+ | ( "anchor" "=" <"> URI-Reference <"> )
+ | ( "rev" "=" relation-types )
+ | ( "hreflang" "=" Language-Tag )
+ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+ | ( "title" "=" quoted-string )
+ | ( "title*" "=" ext-value )
+ | ( "type" "=" ( media-type | quoted-mt ) )
+ | ( link-extension ) )
+ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+ | ( ext-name-star "=" ext-value )
+ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
+ ; extensions. Whitespace NOT
+ ; allowed in between.
+ ptoken = 1*ptokenchar
+ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
+ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+ | "}" | "~"
+ media-type = type-name "/" subtype-name
+ quoted-mt = <"> media-type <">
+ relation-types = relation-type
+ | <"> relation-type *( 1*SP relation-type ) <">
+ relation-type = reg-rel-type | ext-rel-type
+ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+ ext-rel-type = URI
+
+ See more: rfc5988
+*/
+bool
+find_key_value (const char *start, const char *end, const char *key, char **value)
+{
+ const char *eq;
+ size_t key_len = strlen (key);
+ const char *val_beg, *val_end;
+ const char *key_beg;
+
+ key_beg = start;
+
+ while (key_beg + key_len + 1 < end)
+ {
+ /* Skip whitespaces. */
+ while (key_beg + key_len + 1 < end && c_isspace (*key_beg))
+ key_beg++;
+ if (strncmp (key_beg, key, key_len))
+ {
+ /* Find next token. */
+ while (key_beg + key_len + 1 < end && *key_beg != ';')
+ key_beg++;
+ key_beg++;
+ continue;
+ }
+ else
+ {
+ /* Find equals sign. */
+ eq = key_beg + key_len;
+ while (eq < end && c_isspace (*eq))
+ eq++;
+ if (eq == end)
+ return false;
+ if (*eq != '=')
+ {
+ key_beg++;
+ continue;
+ }
+
+ val_beg = eq + 1;
+ while (val_beg < end && c_isspace (*val_beg))
+ val_beg++;
+ if (val_beg == end)
+ return false;
+ val_end = val_beg + 1;
+ while (val_end < end && *val_end != ';' && !c_isspace (*val_end))
+ val_end++;
+ *value = xstrndup (val_beg, val_end - val_beg);
+ dequote_metalink_string (value);
+ return true;
+ }
+ }
+ *value = NULL;
+ return false;
+}
+
+/* This is to check if given token exists in HTTP header. Tokens are
+ separated by ';'. */
+bool
+has_key (const char *start, const char *end, const char *key)
+{
+ const char *pos; /* Here would the token start. */
+ size_t key_len = strlen (key);
+
+ pos = start;
+ while (pos + key_len <= end)
+ {
+ /* Skip whitespaces at beginning. */
+ while (pos + key_len <= end && c_isspace (*pos))
+ pos++;
+
+ /* Does the prefix of pos match our key? */
+ if (strncmp (key, pos, key_len))
+ {
+ /* This was not a match.
+ Skip all characters until beginning of next token. */
+ while (pos + key_len <= end && *pos != ';')
+ pos++;
+ pos++;
+ continue;
+ }
+
+ /* key is prefix of pos. Is it the exact token or just a prefix? */
+ pos += key_len;
+ while (pos < end && c_isspace (*pos))
+ pos++;
+ if (pos == end || *pos == ';')
+ return true;
+
+ /* This was not a match (just a prefix).
+ Skip all characters until beginning of next token. */
+ while (pos + key_len <= end && *pos != ';')
+ pos++;
+ pos++;
+ }
+ return false;
+}
+
+/* Find all key=value pairs delimited with ';' or ','. This is intended for
+ Digest header parsing.
+ The usage is:
+
+ const char *pos;
+ for (pos = header_beg; pos = find_key_values (pos, header_end, &key, &val); pos++)
+ {
+ ...
+ }
+
+ */
+const char *
+find_key_values (const char *start, const char *end, char **key, char **value)
+{
+ const char *key_start, *key_end;
+ const char *eq;
+ const char *val_start, *val_end;
+
+ eq = start;
+ while (eq < end && *eq != '=')
+ {
+ /* Skip tokens without =value part. */
+ if (*eq == ';' || *eq == ',')
+ start = eq + 1;
+ eq++;
+ }
+
+ if (eq >= end)
+ return NULL;
+
+ key_start = start;
+ while (key_start < eq && c_isspace (*key_start))
+ key_start++;
+
+ key_end = eq - 1;
+ while (key_end > key_start && c_isspace (*key_end))
+ key_end--;
+ key_end++;
+
+ val_start = eq + 1;
+ while (val_start < end && c_isspace (*val_start))
+ val_start++;
+
+ val_end = val_start;
+
+ while (val_end < end && *val_end != ';' &&
+ *val_end != ',' && !c_isspace (*val_end))
+ val_end++;
+
+ *key = xstrndup (key_start, key_end - key_start);
+ *value = xstrndup (val_start, val_end - val_start);
+ dequote_metalink_string (value);
+
+ /* Skip trailing whitespaces. */
+ while (val_end < end && c_isspace (*val_end))
+ val_end++;
+
+ return val_end;
+}
+
+#ifdef TESTING
+const char *
+test_find_key_values (void)
+{
+ static const char *header_data = "key1=val1;key2=\"val2\" ;key3=val3; key4=val4"\
+ " ; key5=val5;key6 ='val6';key7= val7; "\
+ "key8 = val8 ; key9 = \"val9\" "\
+ " ,key10= 'val10',key11,key12=val12";
+ static const struct
+ {
+ const char *key;
+ const char *val;
+ } test_array[] =
+ {
+ { "key1", "val1" },
+ { "key2", "val2" },
+ { "key3", "val3" },
+ { "key4", "val4" },
+ { "key5", "val5" },
+ { "key6", "val6" },
+ { "key7", "val7" },
+ { "key8", "val8" },
+ { "key9", "val9" },
+ { "key10", "val10" },
+ { "key12", "val12" }
+ };
+ const char *pos;
+ char *key, *value;
+ size_t i = 0;
+
+ for (pos = header_data; (pos = find_key_values (pos,
+ header_data + strlen (header_data),
+ &key, &value)); pos++)
+ {
+ mu_assert ("test_find_key_values: wrong result",
+ !strcmp (test_array[i].val, value) &&
+ !strcmp (test_array[i].key, key));
+ xfree (key);
+ xfree (value);
+ i++;
+ }
+
+ return NULL;
+}
+
+const char *
+test_find_key_value (void)
+{
+ static const char *header_data = "key1=val1;key2=val2 ;key3='val3'; key4=val4"\
+ " ; key5='val5';key6 =val6;key7= \"val7\"; "\
+ "key8 = \"val8\" ; key9 = val9 ";
+ static const struct
+ {
+ const char *key;
+ const char *val;
+ bool result;
+ } test_array[] =
+ {
+ { "key1", "val1", true },
+ { "key2", "val2", true },
+ { "key3", "val3", true },
+ { "key4", "val4", true },
+ { "key5", "val5", true },
+ { "key6", "val6", true },
+ { "key7", "val7", true },
+ { "key8", "val8", true },
+ { "key9", "val9", true },
+ { "key10", NULL, false },
+ { "ey1", NULL, false },
+ { "dey1", NULL, false }
+ };
+ size_t i;
+
+ for (i=0; i < countof (test_array); ++i)
+ {
+ bool result;
+ char *value;
+
+ result = find_key_value (header_data,
+ header_data + strlen(header_data),
+ test_array[i].key, &value);
+
+ mu_assert ("test_find_key_value: wrong result",
+ result == test_array[i].result &&
+ ((!test_array[i].result && !value) ||
+ !strcmp (value, test_array[i].val)));
+
+ xfree (value);
+ }
+
+ return NULL;
+}
+
+const char *
+test_has_key (void)
+{
+ static const char *header_data = "key1=val2;token1;xyz; token2;xyz;token3 ;"\
+ "xyz; token4 ;xyz; token5 ";
+ struct
+ {
+ const char *token;
+ bool result;
+ } test_array[] =
+ {
+ { "key1=val2", true },
+ { "token1", true },
+ { "token2", true },
+ { "token3", true },
+ { "token4", true },
+ { "token5", true },
+ { "token6", false },
+ { "oken1", false },
+ { "poken1", false },
+ { "key1=val2", true }
+ };
+ size_t i;
+
+ for (i = 0; i < countof (test_array); ++i)
+ mu_assert ("test_has_key: wrong result",
+ has_key (header_data, header_data + strlen (header_data),
+ test_array[i].token) == test_array[i].result);
+
+ return NULL;
+}
+#endif
+
+#endif /* HAVE_METALINK */