From 21224070e802878f81c342835364880ef1a63e88 Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Mon, 3 Sep 2018 20:44:13 +0200 Subject: [PATCH] importd, basic/string-util: use case-insensitive comparison for HTTP headers According to RFC2616[1], HTTP header names are case-insensitive. So it's totally valid to have a header starting with either `Date:` or `date:`. However, when systemd-importd pulls an image from an HTTP server, it parses HTTP headers by comparing header names as-is, without any conversion. That causes failures when some HTTP servers return headers with different combinations of upper-/lower-cases. An example: https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_developer_container.bin.bz2 returns `Etag: "pe89so9oir60"`, while https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2 returns `ETag: "f03372edea9a1e7232e282c346099857"`. Since systemd-importd expects to see `ETag`, the etag for the Container Linux image is correctly interpreted as a part of the hidden file name. However, it cannot parse etag for Flatcar Linux, so the etag the Flatcar Linux image is not appended to the hidden file name. ``` $ sudo ls -al /var/lib/machines/ -r--r--r-- 1 root root 3303014400 Aug 21 20:07 '.raw-https:\x2f\x2falpha\x2erelease\x2ecore-os\x2enet\x2famd64-usr\x2fcurrent\x2fcoreos_developer_container\x2ebin\x2ebz2.\x22f03372edea9a1e7232e282c346099857\x22.raw' -r--r--r-- 1 root root 3303014400 Aug 17 06:15 '.raw-https:\x2f\x2falpha\x2erelease\x2eflatcar-linux\x2enet\x2famd64-usr\x2fcurrent\x2fflatcar_developer_container\x2ebin\x2ebz2.raw' ``` As a result, when the Flatcar image is removed and downloaded again, systemd-importd is not able to determine if the file has been already downloaded, so it always download it again. Then it fails to rename it to an expected name, because there's already a hidden file. To fix this issue, let's introduce a new helper function `memory_startswith_no_case()`, which compares memory regions in a case-insensitive way. Use this function in `curl_header_strdup()`. See also https://github.com/kinvolk/kube-spawn/issues/304 [1]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 --- src/basic/string-util.h | 22 ++++++++++++++++++++++ src/import/curl-util.c | 2 +- src/test/test-string-util.c | 24 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/basic/string-util.h b/src/basic/string-util.h index c0cc4e78d7..fcd12f3a30 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -228,3 +228,25 @@ static inline void *memory_startswith(const void *p, size_t sz, const char *toke return (uint8_t*) p + n; } + +/* Like startswith_no_case(), but operates on arbitrary memory blocks. + * It works only for ASCII strings. + */ +static inline void *memory_startswith_no_case(const void *p, size_t sz, const char *token) { + size_t n, i; + + assert(token); + + n = strlen(token); + if (sz < n) + return NULL; + + assert(p); + + for (i = 0; i < n; i++) { + if (ascii_tolower(((char *)p)[i]) != ascii_tolower(token[i])) + return NULL; + } + + return (uint8_t*) p + n; +} diff --git a/src/import/curl-util.c b/src/import/curl-util.c index b85462176d..848bbd0669 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -363,7 +363,7 @@ int curl_header_strdup(const void *contents, size_t sz, const char *field, char const char *p; char *s; - p = memory_startswith(contents, sz, field); + p = memory_startswith_no_case(contents, sz, field); if (!p) return 0; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 3e72ce2c0a..a9f261839c 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -496,6 +496,29 @@ static void test_memory_startswith(void) { assert_se(!memory_startswith("xxx", 4, "xxxx")); } +static void test_memory_startswith_no_case(void) { + assert_se(streq(memory_startswith_no_case("", 0, ""), "")); + assert_se(streq(memory_startswith_no_case("", 1, ""), "")); + assert_se(streq(memory_startswith_no_case("x", 2, ""), "x")); + assert_se(streq(memory_startswith_no_case("X", 2, ""), "X")); + assert_se(!memory_startswith_no_case("", 1, "X")); + assert_se(!memory_startswith_no_case("", 1, "xxxxXXXX")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "X"), "xx")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "x"), "XX")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "X"), "XX")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "XX"), "x")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "xx"), "X")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "XX"), "X")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "XXX"), "")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "xxx"), "")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "XXX"), "")); + + assert_se(memory_startswith_no_case((char[2]){'x', 'x'}, 2, "xx")); + assert_se(memory_startswith_no_case((char[2]){'x', 'X'}, 2, "xX")); + assert_se(memory_startswith_no_case((char[2]){'X', 'x'}, 2, "Xx")); + assert_se(memory_startswith_no_case((char[2]){'X', 'X'}, 2, "XX")); +} + int main(int argc, char *argv[]) { test_string_erase(); test_ascii_strcasecmp_n(); @@ -525,6 +548,7 @@ int main(int argc, char *argv[]) { test_first_word(); test_strlen_ptr(); test_memory_startswith(); + test_memory_startswith_no_case(); return 0; }