From 9a99549f5df6ef69dd1d2b509c13aaff4e431742 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 19 May 2020 16:11:28 +0100 Subject: [PATCH 19/19] common/include: Add locale-safe ascii_strcasecmp and ascii_strncasecmp. These are derived from the FreeBSD functions here: https://github.com/freebsd/freebsd/blob/master/sys/libkern/strcasecmp.c Thanks: Eric Blake. (cherry picked from commit 46a29b8e91d69e812d78df53e91b526a560000fe) --- .gitignore | 1 + common/include/Makefile.am | 6 +++ common/include/ascii-ctype.h | 6 +++ common/include/ascii-string.h | 77 ++++++++++++++++++++++++++++ common/include/test-ascii-string.c | 79 +++++++++++++++++++++++++++++ plugins/curl/curl.c | 7 +-- plugins/info/info.c | 17 ++++--- plugins/nbd/nbd.c | 8 +-- plugins/partitioning/partitioning.c | 10 ++-- plugins/sh/call.c | 25 ++++----- server/main.c | 8 +-- server/public.c | 21 ++++---- 12 files changed, 222 insertions(+), 43 deletions(-) create mode 100644 common/include/ascii-string.h create mode 100644 common/include/test-ascii-string.c diff --git a/.gitignore b/.gitignore index 523894b7..aa148f04 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ Makefile.in /autom4te.cache /common/bitmap/test-bitmap /common/include/test-ascii-ctype +/common/include/test-ascii-string /common/include/test-byte-swapping /common/include/test-current-dir-name /common/include/test-isaligned diff --git a/common/include/Makefile.am b/common/include/Makefile.am index d7b0d7a8..eff71863 100644 --- a/common/include/Makefile.am +++ b/common/include/Makefile.am @@ -35,6 +35,7 @@ include $(top_srcdir)/common-rules.mk # plugins and/or filters. They are not installed. EXTRA_DIST = \ ascii-ctype.h \ + ascii-string.h \ byte-swapping.h \ exit-with-parent.h \ get-current-dir-name.h \ @@ -52,6 +53,7 @@ EXTRA_DIST = \ TESTS = \ test-ascii-ctype \ + test-ascii-string \ test-byte-swapping \ test-current-dir-name \ test-isaligned \ @@ -68,6 +70,10 @@ test_ascii_ctype_SOURCES = test-ascii-ctype.c ascii-ctype.h test_ascii_ctype_CPPFLAGS = -I$(srcdir) test_ascii_ctype_CFLAGS = $(WARNINGS_CFLAGS) +test_ascii_string_SOURCES = test-ascii-string.c ascii-string.h +test_ascii_string_CPPFLAGS = -I$(srcdir) +test_ascii_string_CFLAGS = $(WARNINGS_CFLAGS) + test_byte_swapping_SOURCES = test-byte-swapping.c byte-swapping.h test_byte_swapping_CPPFLAGS = -I$(srcdir) test_byte_swapping_CFLAGS = $(WARNINGS_CFLAGS) diff --git a/common/include/ascii-ctype.h b/common/include/ascii-ctype.h index 5e8bf237..a700563e 100644 --- a/common/include/ascii-ctype.h +++ b/common/include/ascii-ctype.h @@ -49,6 +49,9 @@ #define ascii_isspace(c) \ ((c) == '\t' || (c) == '\n' || (c) == '\f' || (c) == '\r' || (c) == ' ') +#define ascii_isupper(c) \ + ((c) >= 'A' && (c) <= 'Z') + #define ascii_isxdigit(c) \ ((c) == '0' || (c) == '1' || (c) == '2' || (c) == '3' || (c) == '4' || \ (c) == '5' || (c) == '6' || (c) == '7' || (c) == '8' || (c) == '9' || \ @@ -57,4 +60,7 @@ (c) == 'A' || (c) == 'B' || (c) == 'C' || \ (c) == 'D' || (c) == 'E' || (c) == 'F') +#define ascii_tolower(c) \ + (ascii_isupper ((c)) ? (c) - 'A' + 'a' : (c)) + #endif /* NBDKIT_ASCII_CTYPE_H */ diff --git a/common/include/ascii-string.h b/common/include/ascii-string.h new file mode 100644 index 00000000..0a60d5f4 --- /dev/null +++ b/common/include/ascii-string.h @@ -0,0 +1,77 @@ +/* nbdkit + * Copyright (C) 2013-2020 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Case insensitive string comparison functions (like strcasecmp, + * strncasecmp) which work correctly in any locale. They can only be + * used for comparison when one or both strings is 7 bit ASCII. + */ + +#ifndef NBDKIT_ASCII_STRING_H +#define NBDKIT_ASCII_STRING_H + +#include "ascii-ctype.h" + +static inline int +ascii_strcasecmp (const char *s1, const char *s2) +{ + const unsigned char *us1 = (const unsigned char *)s1; + const unsigned char *us2 = (const unsigned char *)s2; + + while (ascii_tolower (*us1) == ascii_tolower (*us2)) { + if (*us1++ == '\0') + return 0; + us2++; + } + + return ascii_tolower (*us1) - ascii_tolower (*us2); +} + +static inline int +ascii_strncasecmp (const char *s1, const char *s2, size_t n) +{ + if (n != 0) { + const unsigned char *us1 = (const unsigned char *)s1; + const unsigned char *us2 = (const unsigned char *)s2; + + do { + if (ascii_tolower (*us1) != ascii_tolower (*us2)) + return ascii_tolower (*us1) - ascii_tolower (*us2); + if (*us1++ == '\0') + break; + us2++; + } while (--n != 0); + } + + return 0; +} + +#endif /* NBDKIT_ASCII_STRING_H */ diff --git a/common/include/test-ascii-string.c b/common/include/test-ascii-string.c new file mode 100644 index 00000000..0fa4a483 --- /dev/null +++ b/common/include/test-ascii-string.c @@ -0,0 +1,79 @@ +/* nbdkit + * Copyright (C) 2020 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "ascii-string.h" + +int +main (void) +{ + assert (ascii_strcasecmp ("", "") == 0); + assert (ascii_strcasecmp ("a", "a") == 0); + assert (ascii_strcasecmp ("abc", "abc") == 0); + assert (ascii_strcasecmp ("a", "b") < 0); + assert (ascii_strcasecmp ("b", "a") > 0); + assert (ascii_strcasecmp ("aa", "a") > 0); + + /* Second string contains Turkish dotless lowercase letter ı. */ + assert (ascii_strcasecmp ("hi", "hı") != 0); + + /* Check that we got our rounding behaviour correct. */ + assert (ascii_strcasecmp ("\x1", "\x7f") < 0); + assert (ascii_strcasecmp ("\x1", "\x80") < 0); + assert (ascii_strcasecmp ("\x1", "\x81") < 0); + assert (ascii_strcasecmp ("\x1", "\xff") < 0); + + assert (ascii_strncasecmp ("", "", 0) == 0); + assert (ascii_strncasecmp ("a", "a", 1) == 0); + assert (ascii_strncasecmp ("abc", "abc", 3) == 0); + assert (ascii_strncasecmp ("abc", "def", 0) == 0); + assert (ascii_strncasecmp ("abc", "abd", 2) == 0); + assert (ascii_strncasecmp ("a", "b", 1) < 0); + assert (ascii_strncasecmp ("b", "a", 1) > 0); + assert (ascii_strncasecmp ("aa", "a", 2) > 0); + assert (ascii_strncasecmp ("aa", "a", 100) > 0); + + assert (ascii_strncasecmp ("hi", "hı", 1) == 0); + assert (ascii_strncasecmp ("hi", "hı", 2) != 0); + + assert (ascii_strncasecmp ("\x1", "\x7f", 1) < 0); + assert (ascii_strncasecmp ("\x1", "\x80", 1) < 0); + assert (ascii_strncasecmp ("\x1", "\x81", 1) < 0); + assert (ascii_strncasecmp ("\x1", "\xff", 1) < 0); + + exit (EXIT_SUCCESS); +} diff --git a/plugins/curl/curl.c b/plugins/curl/curl.c index ac30cbdd..00f0628a 100644 --- a/plugins/curl/curl.c +++ b/plugins/curl/curl.c @@ -58,6 +58,7 @@ #include "cleanup.h" #include "ascii-ctype.h" +#include "ascii-string.h" static const char *url = NULL; static const char *user = NULL; @@ -419,8 +420,8 @@ curl_open (int readonly) #endif nbdkit_debug ("content length: %" PRIi64, h->exportsize); - if (strncasecmp (url, "http://", strlen ("http://")) == 0 || - strncasecmp (url, "https://", strlen ("https://")) == 0) { + if (ascii_strncasecmp (url, "http://", strlen ("http://")) == 0 || + ascii_strncasecmp (url, "https://", strlen ("https://")) == 0) { if (!h->accept_range) { nbdkit_error ("server does not support 'range' (byte range) requests"); goto err; @@ -504,7 +505,7 @@ header_cb (void *ptr, size_t size, size_t nmemb, void *opaque) const char *bytes = "bytes"; if (realsize >= strlen (accept_ranges) && - strncasecmp (header, accept_ranges, strlen (accept_ranges)) == 0) { + ascii_strncasecmp (header, accept_ranges, strlen (accept_ranges)) == 0) { const char *p = strchr (header, ':') + 1; /* Skip whitespace between the header name and value. */ diff --git a/plugins/info/info.c b/plugins/info/info.c index 329a3684..e04d672b 100644 --- a/plugins/info/info.c +++ b/plugins/info/info.c @@ -49,6 +49,7 @@ #include +#include "ascii-string.h" #include "byte-swapping.h" #include "tvdiff.h" @@ -76,12 +77,12 @@ static int info_config (const char *key, const char *value) { if (strcmp (key, "mode") == 0) { - if (strcasecmp (value, "exportname") == 0 || - strcasecmp (value, "export-name") == 0) { + if (ascii_strcasecmp (value, "exportname") == 0 || + ascii_strcasecmp (value, "export-name") == 0) { mode = MODE_EXPORTNAME; } - else if (strcasecmp (value, "base64exportname") == 0 || - strcasecmp (value, "base64-export-name") == 0) { + else if (ascii_strcasecmp (value, "base64exportname") == 0 || + ascii_strcasecmp (value, "base64-export-name") == 0) { #ifdef HAVE_BASE64 mode = MODE_BASE64EXPORTNAME; #else @@ -89,13 +90,13 @@ info_config (const char *key, const char *value) return -1; #endif } - else if (strcasecmp (value, "address") == 0) + else if (ascii_strcasecmp (value, "address") == 0) mode = MODE_ADDRESS; - else if (strcasecmp (value, "time") == 0) + else if (ascii_strcasecmp (value, "time") == 0) mode = MODE_TIME; - else if (strcasecmp (value, "uptime") == 0) + else if (ascii_strcasecmp (value, "uptime") == 0) mode = MODE_UPTIME; - else if (strcasecmp (value, "conntime") == 0) + else if (ascii_strcasecmp (value, "conntime") == 0) mode = MODE_CONNTIME; else { nbdkit_error ("unknown mode: '%s'", value); diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c index d020beec..980ce8ec 100644 --- a/plugins/nbd/nbd.c +++ b/plugins/nbd/nbd.c @@ -52,6 +52,8 @@ #define NBDKIT_API_VERSION 2 #include + +#include "ascii-string.h" #include "byte-swapping.h" #include "cleanup.h" #include "utils.h" @@ -152,9 +154,9 @@ nbdplug_config (const char *key, const char *value) shared = r; } else if (strcmp (key, "tls") == 0) { - if (strcasecmp (value, "require") == 0 || - strcasecmp (value, "required") == 0 || - strcasecmp (value, "force") == 0) + if (ascii_strcasecmp (value, "require") == 0 || + ascii_strcasecmp (value, "required") == 0 || + ascii_strcasecmp (value, "force") == 0) tls = LIBNBD_TLS_REQUIRE; else { r = nbdkit_parse_bool (value); diff --git a/plugins/partitioning/partitioning.c b/plugins/partitioning/partitioning.c index 6e426b93..e35764dc 100644 --- a/plugins/partitioning/partitioning.c +++ b/plugins/partitioning/partitioning.c @@ -48,6 +48,7 @@ #include +#include "ascii-string.h" #include "byte-swapping.h" #include "isaligned.h" #include "iszero.h" @@ -176,9 +177,10 @@ partitioning_config (const char *key, const char *value) nr_files++; } else if (strcmp (key, "partition-type") == 0) { - if (strcasecmp (value, "mbr") == 0 || strcasecmp (value, "dos") == 0) + if (ascii_strcasecmp (value, "mbr") == 0 || + ascii_strcasecmp (value, "dos") == 0) parttype = PARTTYPE_MBR; - else if (strcasecmp (value, "gpt") == 0) + else if (ascii_strcasecmp (value, "gpt") == 0) parttype = PARTTYPE_GPT; else { nbdkit_error ("unknown partition-type: %s", value); @@ -209,13 +211,13 @@ partitioning_config (const char *key, const char *value) alignment = r; } else if (strcmp (key, "mbr-id") == 0) { - if (strcasecmp (value, "default") == 0) + if (ascii_strcasecmp (value, "default") == 0) mbr_id = DEFAULT_MBR_ID; else if (nbdkit_parse_uint8_t ("mbr-id", value, &mbr_id) == -1) return -1; } else if (strcmp (key, "type-guid") == 0) { - if (strcasecmp (value, "default") == 0) + if (ascii_strcasecmp (value, "default") == 0) parse_guid (DEFAULT_TYPE_GUID, type_guid); else if (parse_guid (value, type_guid) == -1) { nbdkit_error ("could not validate GUID: %s", value); diff --git a/plugins/sh/call.c b/plugins/sh/call.c index ba9f055f..554e2f78 100644 --- a/plugins/sh/call.c +++ b/plugins/sh/call.c @@ -48,6 +48,7 @@ #include #include "ascii-ctype.h" +#include "ascii-string.h" #include "cleanup.h" #include "utils.h" @@ -332,48 +333,48 @@ handle_script_error (const char *argv0, char *ebuf, size_t len) } /* Recognize the errno values that match NBD protocol errors */ - if (strncasecmp (ebuf, "EPERM", 5) == 0) { + if (ascii_strncasecmp (ebuf, "EPERM", 5) == 0) { err = EPERM; skip = 5; } - else if (strncasecmp (ebuf, "EIO", 3) == 0) { + else if (ascii_strncasecmp (ebuf, "EIO", 3) == 0) { err = EIO; skip = 3; } - else if (strncasecmp (ebuf, "ENOMEM", 6) == 0) { + else if (ascii_strncasecmp (ebuf, "ENOMEM", 6) == 0) { err = ENOMEM; skip = 6; } - else if (strncasecmp (ebuf, "EINVAL", 6) == 0) { + else if (ascii_strncasecmp (ebuf, "EINVAL", 6) == 0) { err = EINVAL; skip = 6; } - else if (strncasecmp (ebuf, "ENOSPC", 6) == 0) { + else if (ascii_strncasecmp (ebuf, "ENOSPC", 6) == 0) { err = ENOSPC; skip = 6; } - else if (strncasecmp (ebuf, "EOVERFLOW", 9) == 0) { + else if (ascii_strncasecmp (ebuf, "EOVERFLOW", 9) == 0) { err = EOVERFLOW; skip = 9; } - else if (strncasecmp (ebuf, "ESHUTDOWN", 9) == 0) { + else if (ascii_strncasecmp (ebuf, "ESHUTDOWN", 9) == 0) { err = ESHUTDOWN; skip = 9; } - else if (strncasecmp (ebuf, "ENOTSUP", 7) == 0) { + else if (ascii_strncasecmp (ebuf, "ENOTSUP", 7) == 0) { err = ENOTSUP; skip = 7; } - else if (strncasecmp (ebuf, "EOPNOTSUPP", 10) == 0) { + else if (ascii_strncasecmp (ebuf, "EOPNOTSUPP", 10) == 0) { err = EOPNOTSUPP; skip = 10; } /* Other errno values that server/protocol.c treats specially */ - else if (strncasecmp (ebuf, "EROFS", 5) == 0) { + else if (ascii_strncasecmp (ebuf, "EROFS", 5) == 0) { err = EROFS; skip = 5; } - else if (strncasecmp (ebuf, "EDQUOT", 6) == 0) { + else if (ascii_strncasecmp (ebuf, "EDQUOT", 6) == 0) { #ifdef EDQUOT err = EDQUOT; #else @@ -381,7 +382,7 @@ handle_script_error (const char *argv0, char *ebuf, size_t len) #endif skip = 6; } - else if (strncasecmp (ebuf, "EFBIG", 5) == 0) { + else if (ascii_strncasecmp (ebuf, "EFBIG", 5) == 0) { err = EFBIG; skip = 5; } diff --git a/server/main.c b/server/main.c index 11ba1e6d..08116d74 100644 --- a/server/main.c +++ b/server/main.c @@ -55,6 +55,8 @@ #include +#include "ascii-string.h" + #include "internal.h" #include "nbd-protocol.h" #include "options.h" @@ -282,9 +284,9 @@ main (int argc, char *argv[]) case TLS_OPTION: tls_set_on_cli = true; - if (strcasecmp (optarg, "require") == 0 || - strcasecmp (optarg, "required") == 0 || - strcasecmp (optarg, "force") == 0) + if (ascii_strcasecmp (optarg, "require") == 0 || + ascii_strcasecmp (optarg, "required") == 0 || + ascii_strcasecmp (optarg, "force") == 0) tls = 2; else { tls = nbdkit_parse_bool (optarg); diff --git a/server/public.c b/server/public.c index 98b78482..919f082d 100644 --- a/server/public.c +++ b/server/public.c @@ -52,6 +52,7 @@ #include #include "ascii-ctype.h" +#include "ascii-string.h" #include "get-current-dir-name.h" #include "internal.h" @@ -385,19 +386,19 @@ int nbdkit_parse_bool (const char *str) { if (!strcmp (str, "1") || - !strcasecmp (str, "true") || - !strcasecmp (str, "t") || - !strcasecmp (str, "yes") || - !strcasecmp (str, "y") || - !strcasecmp (str, "on")) + !ascii_strcasecmp (str, "true") || + !ascii_strcasecmp (str, "t") || + !ascii_strcasecmp (str, "yes") || + !ascii_strcasecmp (str, "y") || + !ascii_strcasecmp (str, "on")) return 1; if (!strcmp (str, "0") || - !strcasecmp (str, "false") || - !strcasecmp (str, "f") || - !strcasecmp (str, "no") || - !strcasecmp (str, "n") || - !strcasecmp (str, "off")) + !ascii_strcasecmp (str, "false") || + !ascii_strcasecmp (str, "f") || + !ascii_strcasecmp (str, "no") || + !ascii_strcasecmp (str, "n") || + !ascii_strcasecmp (str, "off")) return 0; nbdkit_error ("could not decipher boolean (%s)", str); -- 2.18.2