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