Blame SOURCES/0199-Add-a-url-parser.patch

6b3c76
From d6fd74e8fd4180d7875c766884610809c546fc13 Mon Sep 17 00:00:00 2001
a85e8e
From: Peter Jones <pjones@redhat.com>
a85e8e
Date: Tue, 14 Jun 2016 16:18:44 -0400
6b3c76
Subject: [PATCH 199/261] Add a url parser.
a85e8e
a85e8e
This patch adds a url parser that can parse http, https, tftp, and tftps
a85e8e
urls, and is easily extensible to handle more types.
a85e8e
a85e8e
It's a little ugly in terms of the arguments it takes.
a85e8e
a85e8e
Signed-off-by: Peter Jones <pjones@redhat.com>
a85e8e
---
a85e8e
 grub-core/Makefile.core.def |   1 +
a85e8e
 grub-core/kern/misc.c       |  13 +
a85e8e
 grub-core/net/url.c         | 856 ++++++++++++++++++++++++++++++++++++++++++++
a85e8e
 include/grub/misc.h         |  45 +++
a85e8e
 include/grub/net/url.h      |  28 ++
a85e8e
 5 files changed, 943 insertions(+)
a85e8e
 create mode 100644 grub-core/net/url.c
a85e8e
 create mode 100644 include/grub/net/url.h
a85e8e
a85e8e
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
6b3c76
index 9378c7329..d73ea6f6c 100644
a85e8e
--- a/grub-core/Makefile.core.def
a85e8e
+++ b/grub-core/Makefile.core.def
a85e8e
@@ -2103,6 +2103,7 @@ module = {
a85e8e
   common = net/ethernet.c;
a85e8e
   common = net/arp.c;
a85e8e
   common = net/netbuff.c;
a85e8e
+  common = net/url.c;
a85e8e
 };
a85e8e
 
a85e8e
 module = {
a85e8e
diff --git a/grub-core/kern/misc.c b/grub-core/kern/misc.c
6b3c76
index a3e5056db..392c697db 100644
a85e8e
--- a/grub-core/kern/misc.c
a85e8e
+++ b/grub-core/kern/misc.c
a85e8e
@@ -325,6 +325,19 @@ grub_strrchr (const char *s, int c)
a85e8e
   return p;
a85e8e
 }
a85e8e
 
a85e8e
+char *
a85e8e
+grub_strchrnul (const char *s, int c)
a85e8e
+{
a85e8e
+  do
a85e8e
+    {
a85e8e
+      if (*s == c)
a85e8e
+	break;
a85e8e
+    }
a85e8e
+  while (*s++);
a85e8e
+
a85e8e
+  return (char *) s;
a85e8e
+}
a85e8e
+
a85e8e
 int
a85e8e
 grub_strword (const char *haystack, const char *needle)
a85e8e
 {
a85e8e
diff --git a/grub-core/net/url.c b/grub-core/net/url.c
a85e8e
new file mode 100644
6b3c76
index 000000000..537019f2c
a85e8e
--- /dev/null
a85e8e
+++ b/grub-core/net/url.c
a85e8e
@@ -0,0 +1,856 @@
a85e8e
+/*
a85e8e
+ *  GRUB  --  GRand Unified Bootloader
a85e8e
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
a85e8e
+ *
a85e8e
+ *  GRUB is free software: you can redistribute it and/or modify
a85e8e
+ *  it under the terms of the GNU General Public License as published by
a85e8e
+ *  the Free Software Foundation, either version 3 of the License, or
a85e8e
+ *  (at your option) any later version.
a85e8e
+ *
a85e8e
+ *  GRUB is distributed in the hope that it will be useful,
a85e8e
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
a85e8e
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
a85e8e
+ *  GNU General Public License for more details.
a85e8e
+ *
a85e8e
+ *  You should have received a copy of the GNU General Public License
a85e8e
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
a85e8e
+ */
a85e8e
+
a85e8e
+#ifdef URL_TEST
a85e8e
+
a85e8e
+#define _GNU_SOURCE 1
a85e8e
+
a85e8e
+#include <errno.h>
a85e8e
+#include <limits.h>
a85e8e
+#include <stdio.h>
a85e8e
+#include <stdlib.h>
a85e8e
+#include <string.h>
a85e8e
+#include <sys/types.h>
a85e8e
+#include <unistd.h>
a85e8e
+
a85e8e
+#define N_(x) x
a85e8e
+
a85e8e
+#define grub_malloc(x) malloc(x)
a85e8e
+#define grub_free(x) ({if (x) free(x);})
a85e8e
+#define grub_error(a, fmt, args...) printf(fmt "\n", ## args)
a85e8e
+#define grub_dprintf(a, fmt, args...) printf(a ": " fmt, ## args)
a85e8e
+#define grub_strlen(x) strlen(x)
a85e8e
+#define grub_strdup(x) strdup(x)
a85e8e
+#define grub_strstr(x,y) strstr(x,y)
a85e8e
+#define grub_memcpy(x,y,z) memcpy(x,y,z)
a85e8e
+#define grub_strcmp(x,y) strcmp(x,y)
a85e8e
+#define grub_strncmp(x,y,z) strncmp(x,y,z)
a85e8e
+#define grub_strcasecmp(x,y) strcasecmp(x,y)
a85e8e
+#define grub_strchrnul(x,y) strchrnul(x,y)
a85e8e
+#define grub_strchr(x,y) strchr(x,y)
a85e8e
+#define grub_strndup(x,y) strndup(x,y)
a85e8e
+#define grub_strtoul(x,y,z) strtoul(x,y,z)
a85e8e
+#define grub_memmove(x,y,z) memmove(x,y,z)
a85e8e
+#define grub_size_t size_t
a85e8e
+#define grub_errno errno
a85e8e
+
a85e8e
+#else
a85e8e
+#include <grub/types.h>
a85e8e
+#include <grub/mm.h>
a85e8e
+#include <grub/misc.h>
a85e8e
+#include <grub/net/url.h>
a85e8e
+#endif
a85e8e
+
a85e8e
+static char *
a85e8e
+translate_slashes(char *str)
a85e8e
+{
a85e8e
+  int i, j;
a85e8e
+  if (str == NULL)
a85e8e
+    return str;
a85e8e
+
a85e8e
+  for (i = 0, j = 0; str[i] != '\0'; i++, j++)
a85e8e
+    {
a85e8e
+      if (str[i] == '\\')
a85e8e
+	{
a85e8e
+	  str[j] = '/';
a85e8e
+	  if (str[i+1] == '\\')
a85e8e
+	    i++;
a85e8e
+	}
a85e8e
+    }
a85e8e
+
a85e8e
+  return str;
a85e8e
+}
a85e8e
+
a85e8e
+static inline int
a85e8e
+hex2int (char c)
a85e8e
+{
a85e8e
+  if (c >= '0' && c <= '9')
a85e8e
+    return c - '0';
a85e8e
+  c |= 0x20;
a85e8e
+  if (c >= 'a' && c <= 'f')
a85e8e
+    return c - 'a' + 10;
a85e8e
+  return -1;
a85e8e
+}
a85e8e
+
a85e8e
+static int
a85e8e
+url_unescape (char *buf, grub_size_t len)
a85e8e
+{
a85e8e
+  int c, rc;
a85e8e
+  unsigned int i;
a85e8e
+
a85e8e
+
a85e8e
+  if (len < 3)
a85e8e
+    {
a85e8e
+      for (i = 0; i < len; i++)
a85e8e
+	if (buf[i] == '%')
a85e8e
+	  return -1;
a85e8e
+      return 0;
a85e8e
+    }
a85e8e
+
a85e8e
+  for (i = 0; len > 2 && i < len - 2; i++)
a85e8e
+    {
a85e8e
+      if (buf[i] == '%')
a85e8e
+	{
a85e8e
+	  unsigned int j;
a85e8e
+	  for (j = i+1; j < i+3; j++)
a85e8e
+	    {
a85e8e
+	      if (!(buf[j] >= '0' && buf[j] <= '9') &&
a85e8e
+		  !(buf[j] >= 'a' && buf[j] <= 'f') &&
a85e8e
+		  !(buf[j] >= 'A' && buf[j] <= 'F'))
a85e8e
+		return -1;
a85e8e
+	    }
a85e8e
+	  i += 2;
a85e8e
+	}
a85e8e
+    }
a85e8e
+  if (i == len - 2)
a85e8e
+    {
a85e8e
+      if (buf[i+1] == '%' || buf[i+2] == '%')
a85e8e
+	return -1;
a85e8e
+    }
a85e8e
+  for (i = 0; i < len - 2; i++)
a85e8e
+    {
a85e8e
+      if (buf[i] == '%')
a85e8e
+	{
a85e8e
+	  rc = hex2int (buf[i+1]);
a85e8e
+	  if (rc < 0)
a85e8e
+	    return -1;
a85e8e
+	  c = (rc & 0xf) << 4;
a85e8e
+	  rc = hex2int (buf[i+2]);
a85e8e
+	  if (rc < 0)
a85e8e
+	    return -1;
a85e8e
+	  c |= (rc & 0xf);
a85e8e
+
a85e8e
+	  buf[i] = c;
a85e8e
+	  grub_memmove (buf+i+1, buf+i+3, len-(i+2));
a85e8e
+	  len -= 2;
a85e8e
+	}
a85e8e
+    }
a85e8e
+  return 0;
a85e8e
+}
a85e8e
+
a85e8e
+static int
a85e8e
+extract_http_url_info (char *url, int ssl,
a85e8e
+		       char **userinfo, char **host, int *port,
a85e8e
+		       char **file)
a85e8e
+{
a85e8e
+  char *colon, *slash, *query, *at = NULL, *separator, *auth_end;
a85e8e
+
a85e8e
+  char *userinfo_off = NULL;
a85e8e
+  char *userinfo_end;
a85e8e
+  char *host_off = NULL;
a85e8e
+  char *host_end;
a85e8e
+  char *port_off = NULL;
a85e8e
+  char *port_end;
a85e8e
+  char *file_off = NULL;
a85e8e
+
a85e8e
+  grub_size_t l;
a85e8e
+  int c;
a85e8e
+
a85e8e
+  if (!url || !userinfo || !host || !port || !file)
a85e8e
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid argument");
a85e8e
+
a85e8e
+  *userinfo = *host = *file = NULL;
a85e8e
+  *port = -1;
a85e8e
+
a85e8e
+  userinfo_off = url;
a85e8e
+
a85e8e
+  slash = grub_strchrnul (userinfo_off, '/');
a85e8e
+  query = grub_strchrnul (userinfo_off, '?');
a85e8e
+  auth_end = slash < query ? slash : query;
a85e8e
+  /* auth_end here is one /past/ the last character in the auth section, i.e.
a85e8e
+   * it's the : or / or NUL */
a85e8e
+
a85e8e
+  separator = grub_strchrnul (userinfo_off, '@');
a85e8e
+  if (separator > auth_end)
a85e8e
+    {
a85e8e
+      host_off = userinfo_off;
a85e8e
+      userinfo_off = NULL;
a85e8e
+      userinfo_end = NULL;
a85e8e
+    }
a85e8e
+  else
a85e8e
+    {
a85e8e
+      at = separator;
a85e8e
+      *separator = '\0';
a85e8e
+      userinfo_end = separator;
a85e8e
+      host_off = separator + 1;
a85e8e
+    }
a85e8e
+
a85e8e
+  if (*host_off == '[')
a85e8e
+    {
a85e8e
+      separator = grub_strchrnul (host_off, ']');
a85e8e
+      if (separator >= auth_end)
a85e8e
+	goto fail;
a85e8e
+
a85e8e
+      separator += 1;
a85e8e
+      host_end = separator;
a85e8e
+    }
a85e8e
+  else
a85e8e
+    {
a85e8e
+      host_end = separator = colon = grub_strchrnul (host_off, ':');
a85e8e
+
a85e8e
+      if (colon > auth_end)
a85e8e
+	{
a85e8e
+	  separator = NULL;
a85e8e
+	  host_end = auth_end;
a85e8e
+	}
a85e8e
+    }
a85e8e
+
a85e8e
+  if (separator && separator < auth_end)
a85e8e
+    {
a85e8e
+      if (*separator == ':')
a85e8e
+	{
a85e8e
+	  port_off = separator + 1;
a85e8e
+	  port_end = auth_end;
a85e8e
+
a85e8e
+	  if (auth_end - port_end > 0)
a85e8e
+	    goto fail;
a85e8e
+	  if (port_end - port_off < 1)
a85e8e
+	    goto fail;
a85e8e
+	}
a85e8e
+      else
a85e8e
+	goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  file_off = auth_end;
a85e8e
+  if (port_off)
a85e8e
+    {
a85e8e
+      unsigned long portul;
a85e8e
+
a85e8e
+      separator = NULL;
a85e8e
+      c = *port_end;
a85e8e
+      *port_end = '\0';
a85e8e
+
a85e8e
+      portul = grub_strtoul (port_off, &separator, 10);
a85e8e
+      *port_end = c;
a85e8e
+#ifdef URL_TEST
a85e8e
+      if (portul == ULONG_MAX && errno == ERANGE)
a85e8e
+	goto fail;
a85e8e
+#else
a85e8e
+      if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
a85e8e
+	goto fail;
a85e8e
+#endif
a85e8e
+      if (portul & ~0xfffful)
a85e8e
+	goto fail;
a85e8e
+      if (separator != port_end)
a85e8e
+	goto fail;
a85e8e
+
a85e8e
+      *port = portul & 0xfffful;
a85e8e
+    }
a85e8e
+  else if (ssl)
a85e8e
+    *port = 443;
a85e8e
+  else
a85e8e
+    *port = 80;
a85e8e
+
a85e8e
+  if (userinfo_off && *userinfo_off)
a85e8e
+    {
a85e8e
+      l = userinfo_end - userinfo_off + 1;
a85e8e
+
a85e8e
+      *userinfo = grub_strndup (userinfo_off, l);
a85e8e
+      if (!*userinfo)
a85e8e
+	goto fail;
a85e8e
+      (*userinfo)[l-1]= '\0';
a85e8e
+    }
a85e8e
+
a85e8e
+  l = host_end - host_off;
a85e8e
+  c = *host_end;
a85e8e
+  *host_end = '\0';
a85e8e
+  *host = grub_strndup (host_off, l);
a85e8e
+  *host_end = c;
a85e8e
+  if (!*host)
a85e8e
+    goto fail;
a85e8e
+  (*host)[l] = '\0';
a85e8e
+
a85e8e
+  *file = grub_strdup (file_off);
a85e8e
+  if (!*file)
a85e8e
+    goto fail;
a85e8e
+
a85e8e
+  if (at)
a85e8e
+    *at = '@';
a85e8e
+  return 0;
a85e8e
+fail:
a85e8e
+  if (at)
a85e8e
+    *at = '@';
a85e8e
+  grub_free (*userinfo);
a85e8e
+  grub_free (*host);
a85e8e
+  grub_free (*file);
a85e8e
+
a85e8e
+  return -1;
a85e8e
+}
a85e8e
+
a85e8e
+static int
a85e8e
+extract_tftp_url_info (char *url, int ssl, char **host, char **file, int *port)
a85e8e
+{
a85e8e
+  char *slash, *semi;
a85e8e
+
a85e8e
+  char *host_off = url;
a85e8e
+  char *host_end;
a85e8e
+  char *file_off;
a85e8e
+
a85e8e
+  int c;
a85e8e
+
a85e8e
+  if (!url || !host || !file || !port)
a85e8e
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid argument");
a85e8e
+
a85e8e
+  if (ssl)
a85e8e
+    *port = 3713;
a85e8e
+  else
a85e8e
+    *port = 69;
a85e8e
+
a85e8e
+  slash = grub_strchr (url, '/');
a85e8e
+  if (!slash)
a85e8e
+    return -1;
a85e8e
+
a85e8e
+  host_end = file_off = slash;
a85e8e
+
a85e8e
+  semi = grub_strchrnul (slash, ';');
a85e8e
+  if (!grub_strncmp (semi, ";mode=", 6) && grub_strcmp (semi+6, "octet"))
a85e8e
+    {
a85e8e
+      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
a85e8e
+		  N_("TFTP mode `%s' is not implemented."), semi+6);
a85e8e
+      return -1;
a85e8e
+    }
a85e8e
+
a85e8e
+  /*
a85e8e
+   * Maybe somebody added a new method, I dunno.  Anyway, semi is a reserved
a85e8e
+   * character, so if it's there, it's the start of the mode block or it's
a85e8e
+   * invalid.  So set it to \0 unconditionally, not just for ;mode=octet
a85e8e
+   */
a85e8e
+  *semi = '\0';
a85e8e
+
a85e8e
+  c = *host_end;
a85e8e
+  *host_end = '\0';
a85e8e
+  *host = grub_strdup (host_off);
a85e8e
+  *host_end = c;
a85e8e
+
a85e8e
+  *file = grub_strdup (file_off);
a85e8e
+
a85e8e
+  if (!*file || !*host)
a85e8e
+    {
a85e8e
+      grub_free (*file);
a85e8e
+      grub_free (*host);
a85e8e
+      return -1;
a85e8e
+    }
a85e8e
+
a85e8e
+  return 0;
a85e8e
+}
a85e8e
+
a85e8e
+int
a85e8e
+extract_url_info (const char *urlbuf, grub_size_t buflen,
a85e8e
+		  char **scheme, char **userinfo,
a85e8e
+		  char **host, int *port, char **file)
a85e8e
+{
a85e8e
+  char *url;
a85e8e
+  char *colon;
a85e8e
+
a85e8e
+  char *scheme_off;
a85e8e
+  char *specific_off;
a85e8e
+
a85e8e
+  int rc;
a85e8e
+  int c;
a85e8e
+
a85e8e
+  int https;
a85e8e
+
a85e8e
+  if (!urlbuf || !buflen || !scheme || !userinfo || !host || !port || !file)
a85e8e
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid argument");
a85e8e
+
a85e8e
+  *scheme = *userinfo = *host = *file = NULL;
a85e8e
+  *port = -1;
a85e8e
+
a85e8e
+  /* make sure we have our own coherent grub_string. */
a85e8e
+  url = grub_malloc (buflen + 1);
a85e8e
+  if (!url)
a85e8e
+    return -1;
a85e8e
+
a85e8e
+  grub_memcpy (url, urlbuf, buflen);
a85e8e
+  url[buflen] = '\0';
a85e8e
+
a85e8e
+  grub_dprintf ("net", "dhcpv6 boot-file-url: `%s'\n", url);
a85e8e
+
a85e8e
+  /* get rid of any backslashes */
a85e8e
+  url = translate_slashes (url);
a85e8e
+
a85e8e
+  /* find the constituent parts */
a85e8e
+  colon = grub_strstr (url, "://");
a85e8e
+  if (!colon)
a85e8e
+    goto fail;
a85e8e
+
a85e8e
+  scheme_off = url;
a85e8e
+  c = *colon;
a85e8e
+  *colon = '\0';
a85e8e
+  specific_off = colon + 3;
a85e8e
+
a85e8e
+  https = !grub_strcasecmp (scheme_off, "https");
a85e8e
+
a85e8e
+  rc = 0;
a85e8e
+  if (!grub_strcasecmp (scheme_off, "tftp"))
a85e8e
+    {
a85e8e
+      rc = extract_tftp_url_info (specific_off, 0, host, file, port);
a85e8e
+    }
a85e8e
+#ifdef URL_TEST
a85e8e
+  else if (!grub_strcasecmp (scheme_off, "http") || https)
a85e8e
+#else
a85e8e
+  else if (!grub_strcasecmp (scheme_off, "http"))
a85e8e
+#endif
a85e8e
+    {
a85e8e
+      rc = extract_http_url_info (specific_off,
a85e8e
+				  https, userinfo, host, port, file);
a85e8e
+    }
a85e8e
+#ifdef URL_TEST
a85e8e
+  else if (!grub_strcasecmp (scheme_off, "iscsi"))
a85e8e
+    {
a85e8e
+      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
a85e8e
+		  N_("Unimplemented URL scheme `%s'"), scheme_off);
a85e8e
+      *colon = c;
a85e8e
+      goto fail;
a85e8e
+    }
a85e8e
+  else if (!grub_strcasecmp (scheme_off, "tftps"))
a85e8e
+    {
a85e8e
+      rc = extract_tftp_url_info (specific_off, 1, host, file, port);
a85e8e
+    }
a85e8e
+#endif
a85e8e
+  else
a85e8e
+    {
a85e8e
+      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
a85e8e
+		  N_("Unimplemented URL scheme `%s'"), scheme_off);
a85e8e
+      *colon = c;
a85e8e
+      goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  if (rc < 0)
a85e8e
+    {
a85e8e
+      *colon = c;
a85e8e
+      goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  *scheme = grub_strdup (scheme_off);
a85e8e
+  *colon = c;
a85e8e
+  if (!*scheme)
a85e8e
+    goto fail;
a85e8e
+
a85e8e
+  if (*userinfo)
a85e8e
+    {
a85e8e
+      rc = url_unescape (*userinfo, grub_strlen (*userinfo));
a85e8e
+      if (rc < 0)
a85e8e
+	goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  if (*host)
a85e8e
+    {
a85e8e
+      rc = url_unescape (*host, grub_strlen (*host));
a85e8e
+      if (rc < 0)
a85e8e
+	goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  if (*file)
a85e8e
+    {
a85e8e
+      rc = url_unescape (*file, grub_strlen (*file));
a85e8e
+      if (rc < 0)
a85e8e
+	goto fail;
a85e8e
+    }
a85e8e
+
a85e8e
+  grub_free (url);
a85e8e
+  return 0;
a85e8e
+fail:
a85e8e
+  grub_free (*scheme);
a85e8e
+  grub_free (*userinfo);
a85e8e
+  grub_free (*host);
a85e8e
+  grub_free (*file);
a85e8e
+
a85e8e
+  if (!grub_errno)
a85e8e
+    grub_error (GRUB_ERR_NET_BAD_ADDRESS, N_("Invalid boot-file-url `%s'"),
a85e8e
+		url);
a85e8e
+  grub_free (url);
a85e8e
+  return -1;
a85e8e
+}
a85e8e
+
a85e8e
+#ifdef URL_TEST
a85e8e
+
a85e8e
+struct test {
a85e8e
+    char *url;
a85e8e
+    int rc;
a85e8e
+    char *scheme;
a85e8e
+    char *userinfo;
a85e8e
+    char *host;
a85e8e
+    int port;
a85e8e
+    char *file;
a85e8e
+} tests[] = {
a85e8e
+  {.url = "http://foo.example.com/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo.example.com/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://[foo.example.com/",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "http://[foo.example.com/?foobar",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "http://foo.example.com:/",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "http://foo.example.com:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo.example.com:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://[1234::1]/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://[1234::1]/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://[1234::1]:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://[1234::1]:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@foo.example.com/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@foo.example.com/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[foo.example.com/",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[foo.example.com/?foobar",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "http://foo@foo.example.com:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@foo.example.com:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[1234::1]/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[1234::1]/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 80,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[1234::1]:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "http://foo@[1234::1]:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "http",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://foo.example.com/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo.example.com/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://[foo.example.com/",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "https://[foo.example.com/?foobar",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "https://foo.example.com:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo.example.com:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://[1234::1]/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://[1234::1]/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://[1234::1]:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://[1234::1]:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@foo.example.com/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@foo.example.com/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@[foo.example.com/",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "https://f%6fo@[foo.example.com/?fooba%72",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "https://foo@foo.example.com:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@foo.example.com:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@[1234::1]/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@[1234::1]/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 443,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "https://f%6fo@[12%334::1]:81/",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/",
a85e8e
+  },
a85e8e
+  {.url = "https://foo@[1234::1]:81/?foobar",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "https",
a85e8e
+   .userinfo = "foo",
a85e8e
+   .host = "[1234::1]",
a85e8e
+   .port = 81,
a85e8e
+   .file = "/?foobar",
a85e8e
+  },
a85e8e
+  {.url = "tftp://foo.e%78ample.com/foo/bar/b%61%7a",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "tftp",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 69,
a85e8e
+   .file = "/foo/bar/baz",
a85e8e
+  },
a85e8e
+  {.url = "tftp://foo.example.com/foo/bar/baz",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "tftp",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 69,
a85e8e
+   .file = "/foo/bar/baz",
a85e8e
+  },
a85e8e
+  {.url = "tftps://foo.example.com/foo/bar/baz",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "tftps",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 3713,
a85e8e
+   .file = "/foo/bar/baz",
a85e8e
+  },
a85e8e
+  {.url = "tftps://foo.example.com/foo/bar/baz;mode=netascii",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "tftps://foo.example.com/foo/bar/baz;mode=octet",
a85e8e
+   .rc = 0,
a85e8e
+   .scheme = "tftps",
a85e8e
+   .host = "foo.example.com",
a85e8e
+   .port = 3713,
a85e8e
+   .file = "/foo/bar/baz",
a85e8e
+  },
a85e8e
+  {.url = "tftps://foo.example.com/foo/bar/baz;mode=invalid",
a85e8e
+   .rc = -1,
a85e8e
+  },
a85e8e
+  {.url = "",
a85e8e
+  },
a85e8e
+};
a85e8e
+
a85e8e
+static int
a85e8e
+string_test (char *name, char *a, char *b)
a85e8e
+{
a85e8e
+  if ((a && !b) || (!a && b))
a85e8e
+    {
a85e8e
+      printf("expected %s \"%s\", got \"%s\"\n", name, a, b);
a85e8e
+      return -1;
a85e8e
+    }
a85e8e
+  if (a && b && strcmp(a, b))
a85e8e
+    {
a85e8e
+      printf("expected %s \"%s\", got \"%s\"\n", name, a, b);
a85e8e
+      return -1;
a85e8e
+    }
a85e8e
+  return 0;
a85e8e
+}
a85e8e
+
a85e8e
+int
a85e8e
+main(void)
a85e8e
+{
a85e8e
+	unsigned int i;
a85e8e
+	int rc;
a85e8e
+
a85e8e
+	for (i = 0; tests[i].url[0] != '\0'; i++)
a85e8e
+	{
a85e8e
+		char *scheme, *userinfo, *host, *file;
a85e8e
+		int port;
a85e8e
+
a85e8e
+		printf("======= url: \"%s\"\n", tests[i].url);
a85e8e
+		rc = extract_url_info(tests[i].url, strlen(tests[i].url) + 1,
a85e8e
+				      &scheme, &userinfo, &host, &port, &file;;
a85e8e
+		if (tests[i].rc != rc)
a85e8e
+		  {
a85e8e
+		    printf("  extract_url_info(...) = %d\n", rc);
a85e8e
+		    exit(1);
a85e8e
+		  }
a85e8e
+		else if (rc >= 0)
a85e8e
+		  {
a85e8e
+		    if (string_test("scheme", tests[i].scheme, scheme) < 0)
a85e8e
+		      exit(1);
a85e8e
+		    if (string_test("userinfo", tests[i].userinfo, userinfo) < 0)
a85e8e
+		      exit(1);
a85e8e
+		    if (string_test("host", tests[i].host, host) < 0)
a85e8e
+		      exit(1);
a85e8e
+		    if (port != tests[i].port)
a85e8e
+		      {
a85e8e
+			printf("  bad port \"%d\" should have been \"%d\"\n",
a85e8e
+			       port, tests[i].port);
a85e8e
+			exit(1);
a85e8e
+		      }
a85e8e
+		    if (string_test("file", tests[i].file, file) < 0)
a85e8e
+		      exit(1);
a85e8e
+		  }
a85e8e
+		free(scheme);
a85e8e
+		free(userinfo);
a85e8e
+		free(host);
a85e8e
+		free(file);
a85e8e
+	}
a85e8e
+	printf("everything worked?!?\n");
a85e8e
+}
a85e8e
+#endif
a85e8e
diff --git a/include/grub/misc.h b/include/grub/misc.h
6b3c76
index c6cd4564d..342502919 100644
a85e8e
--- a/include/grub/misc.h
a85e8e
+++ b/include/grub/misc.h
a85e8e
@@ -104,6 +104,7 @@ int EXPORT_FUNC(grub_strncmp) (const char *s1, const char *s2, grub_size_t n);
a85e8e
 
a85e8e
 char *EXPORT_FUNC(grub_strchr) (const char *s, int c);
a85e8e
 char *EXPORT_FUNC(grub_strrchr) (const char *s, int c);
a85e8e
+char *EXPORT_FUNC(grub_strchrnul) (const char *s, int c);
a85e8e
 int EXPORT_FUNC(grub_strword) (const char *s, const char *w);
a85e8e
 
a85e8e
 /* Copied from gnulib.
a85e8e
@@ -226,6 +227,50 @@ grub_toupper (int c)
a85e8e
   return c;
a85e8e
 }
a85e8e
 
a85e8e
+static inline char *
a85e8e
+grub_strcasestr (const char *haystack, const char *needle)
a85e8e
+{
a85e8e
+  /* Be careful not to look at the entire extent of haystack or needle
a85e8e
+     until needed.  This is useful because of these two cases:
a85e8e
+       - haystack may be very long, and a match of needle found early,
a85e8e
+       - needle may be very long, and not even a short initial segment of
a85e8e
+       needle may be found in haystack.  */
a85e8e
+  if (*needle != '\0')
a85e8e
+    {
a85e8e
+      /* Speed up the following searches of needle by caching its first
a85e8e
+	 character.  */
a85e8e
+      char b = *needle++;
a85e8e
+
a85e8e
+      for (;; haystack++)
a85e8e
+	{
a85e8e
+	  if (*haystack == '\0')
a85e8e
+	    /* No match.  */
a85e8e
+	    return 0;
a85e8e
+	  if (grub_tolower(*haystack) == grub_tolower(b))
a85e8e
+	    /* The first character matches.  */
a85e8e
+	    {
a85e8e
+	      const char *rhaystack = haystack + 1;
a85e8e
+	      const char *rneedle = needle;
a85e8e
+
a85e8e
+	      for (;; rhaystack++, rneedle++)
a85e8e
+		{
a85e8e
+		  if (*rneedle == '\0')
a85e8e
+		    /* Found a match.  */
a85e8e
+		    return (char *) haystack;
a85e8e
+		  if (*rhaystack == '\0')
a85e8e
+		    /* No match.  */
a85e8e
+		    return 0;
a85e8e
+		  if (grub_tolower(*rhaystack) != grub_tolower(*rneedle))
a85e8e
+		    /* Nothing in this round.  */
a85e8e
+		    break;
a85e8e
+		}
a85e8e
+	    }
a85e8e
+	}
a85e8e
+    }
a85e8e
+  else
a85e8e
+    return (char *) haystack;
a85e8e
+}
a85e8e
+
a85e8e
 static inline int
a85e8e
 grub_strcasecmp (const char *s1, const char *s2)
a85e8e
 {
a85e8e
diff --git a/include/grub/net/url.h b/include/grub/net/url.h
a85e8e
new file mode 100644
6b3c76
index 000000000..a215fa27d
a85e8e
--- /dev/null
a85e8e
+++ b/include/grub/net/url.h
a85e8e
@@ -0,0 +1,28 @@
a85e8e
+/* url.h - prototypes for url parsing functions */
a85e8e
+/*
a85e8e
+ *  GRUB  --  GRand Unified Bootloader
a85e8e
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
a85e8e
+ *
a85e8e
+ *  GRUB is free software: you can redistribute it and/or modify
a85e8e
+ *  it under the terms of the GNU General Public License as published by
a85e8e
+ *  the Free Software Foundation, either version 3 of the License, or
a85e8e
+ *  (at your option) any later version.
a85e8e
+ *
a85e8e
+ *  GRUB is distributed in the hope that it will be useful,
a85e8e
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
a85e8e
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
a85e8e
+ *  GNU General Public License for more details.
a85e8e
+ *
a85e8e
+ *  You should have received a copy of the GNU General Public License
a85e8e
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
a85e8e
+ */
a85e8e
+
a85e8e
+#ifndef GRUB_URL_HEADER
a85e8e
+#define GRUB_URL_HEADER	1
a85e8e
+
a85e8e
+int
a85e8e
+EXPORT_FUNC(extract_url_info) (const char *urlbuf, grub_size_t buflen,
a85e8e
+			       char **scheme, char **userinfo,
a85e8e
+			       char **host, int *port, char **file);
a85e8e
+
a85e8e
+#endif /* GRUB_URL_HEADER */
6b3c76
-- 
6b3c76
2.13.5
6b3c76