Normally, when using captive mode --run coupled with a Unix socket,
the user is using -U -, and we get lucky that the typcial values of
$TMPDIR plus our use of mktemp produce a name that needs neither shell
quoting nor which requires %-encoding for use in a URI. But a
determined user can use an explicit -U /odd/#path or setting of
$TMPDIR that gets correctly shell-quoted, but which would break
interpretation in a URI parser. In preparation for adding a $uri
variable under --run, it's time to ensure that we can always produce a
valid unambiguous URI by adding a new helper function. It is
intentional that uri_quote of an empty string produces no output, as
the intended audience of uri_quote is always a smaller component
within a larger URI (different from shell_quote which does have use on
a stand-alone empty string).
It's also worth testing the existing shell_quote (including the bug
fixed in bdda3f30) as well as the new uri_quote. The test includes an
XXX comment that matches the counterpart XXX in shell_quote(); an
audit of shell_quote() callers shows that the iso plugin might be
affected if someone ever used prog=a=b, but that is fringe enough to
put off to another day.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
common/utils/utils.h | 1 +
common/utils/test-quotes.c | 108 +++++++++++++++++++++++++++++++++++++
common/utils/utils.c | 27 ++++++++++
.gitignore | 1 +
common/utils/Makefile.am | 11 ++++
5 files changed, 148 insertions(+)
create mode 100644 common/utils/test-quotes.c
diff --git a/common/utils/utils.h b/common/utils/utils.h
index 4d1abf03..3f10cdc0 100644
--- a/common/utils/utils.h
+++ b/common/utils/utils.h
@@ -34,6 +34,7 @@
#define NBDKIT_UTILS_H
extern void shell_quote (const char *str, FILE *fp);
+extern void uri_quote (const char *str, FILE *fp);
extern int exit_status_to_nbd_error (int status, const char *cmd);
#endif /* NBDKIT_UTILS_H */
diff --git a/common/utils/test-quotes.c b/common/utils/test-quotes.c
new file mode 100644
index 00000000..33b8299e
--- /dev/null
+++ b/common/utils/test-quotes.c
@@ -0,0 +1,108 @@
+/* nbdkit
+ * Copyright (C) 2019 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.
+ */
+
+/* Unit tests of utils quoting code. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "utils.h"
+
+static bool
+test (const char *orig, const char *fnname, void (*fn) (const char *, FILE *),
+ const char *exp)
+{
+ char *str = NULL;
+ size_t str_len = 0;
+ FILE *fp;
+
+ fp = open_memstream (&str, &str_len);
+ assert (fp);
+ fn (orig, fp);
+ if (fclose (fp) == EOF)
+ assert (false);
+ if (str_len == 0 && !str)
+ str = strdup ("");
+ assert (str);
+
+ if (strcmp (str, exp)) {
+ fprintf (stderr, "%s failed, got '%s' expected '%s'\n",
+ fnname, str, exp);
+ free (str);
+ return true;
+ }
+ free (str);
+ return false;
+}
+
+int
+main (void)
+{
+ struct {
+ const char *orig;
+ const char *shell;
+ const char *uri;
+ } tests[] = {
+ { "a-b_c.0", "a-b_c.0", "a-b_c.0" },
+ { "/Safe/Path", "/Safe/Path", "/Safe/Path" },
+ { "a~b", "\"a~b\"", "a~b" },
+ { "", "\"\"", "" },
+ { "a=b", "a=b", "a%3Db" }, /* XXX shell wrong if used
as argv[0] */
+ { "a;b", "\"a;b\"", "a%3Bb" },
+ { "a b", "\"a b\"", "a%20b" },
+ { "a%b", "\"a%b\"", "a%25b" },
+ { "a'b\"c$d`e\\f",
"\"a'b\\\"c\\$d\\`e\\\\f\"",
"a%27b%22c%24d%60e%5Cf" },
+ };
+ size_t i;
+ bool fail = false;
+
+ for (i = 0; i < sizeof tests / sizeof tests[0]; i++) {
+ fail |= test (tests[i].orig, "shell_quote", shell_quote, tests[i].shell);
+ fail |= test (tests[i].orig, "uri_quote", uri_quote, tests[i].uri);
+ }
+ return fail ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+/* Unrelated utils code uses nbdkit_error, normally provided by the main
+ * server program. So we have to provide it here.
+ */
+void
+nbdkit_error (const char *fs, ...)
+{
+ /* XXX split utils.c to avoid needing this linker stub? */
+ assert (false);
+}
diff --git a/common/utils/utils.c b/common/utils/utils.c
index 7a584e25..7534a13d 100644
--- a/common/utils/utils.c
+++ b/common/utils/utils.c
@@ -75,6 +75,33 @@ shell_quote (const char *str, FILE *fp)
fputc ('"', fp);
}
+/* Print str to fp, URI quoting if necessary.
+ * The resulting string is safe for use in a URI path or query component,
+ * and can be passed through the shell without further quoting.
+ */
+void
+uri_quote (const char *str, FILE *fp)
+{
+ /* safe_chars contains the RFC 3986 unreserved characters plus '/'. */
+ const char *safe_chars =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~/";
+ size_t i, len;
+
+ /* If the string consists only of safe characters, output it as-is. */
+ len = strlen (str);
+ if (len == strspn (str, safe_chars)) {
+ fputs (str, fp);
+ return;
+ }
+
+ for (i = 0; i < len; ++i) {
+ if (strchr (safe_chars, str[i]))
+ fputc (str[i], fp);
+ else
+ fprintf (fp, "%%%02X", str[i] & 0xff);
+ }
+}
+
/* Convert exit status to nbd_error. If the exit status was nonzero
* or another failure then -1 is returned.
*/
diff --git a/.gitignore b/.gitignore
index 3770d3ba..46ab5dc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ Makefile.in
/common/include/test-random
/common/include/test-tvdiff
/common/protocol/protostrings.c
+/common/utils/test-quotes
/compile
/config.guess
/config.h
diff --git a/common/utils/Makefile.am b/common/utils/Makefile.am
index f4f17005..cc364300 100644
--- a/common/utils/Makefile.am
+++ b/common/utils/Makefile.am
@@ -43,3 +43,14 @@ libutils_la_CPPFLAGS = \
-I$(top_srcdir)/include
libutils_la_CFLAGS = \
$(WARNINGS_CFLAGS)
+
+# Unit tests.
+
+TESTS = test-quotes
+check_PROGRAMS = test-quotes
+
+test_quotes_SOURCES = test-quotes.c utils.c utils.h
+test_quotes_CPPFLAGS = \
+ -I$(top_srcdir)/common/utils
+test_quotes_CFLAGS = \
+ $(WARNINGS_CFLAGS)
--
2.20.1