]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
importd, basic/string-util: use case-insensitive comparison for HTTP headers
authorDongsu Park <dongsu@kinvolk.io>
Mon, 3 Sep 2018 18:44:13 +0000 (20:44 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 3 Sep 2018 20:59:27 +0000 (22:59 +0200)
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
src/import/curl-util.c
src/test/test-string-util.c

index c0cc4e78d7e00fad0c200b255a4b1e91b0639288..fcd12f3a305d73d5cc90eb6afeb180bebb9f647c 100644 (file)
@@ -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;
+}
index b85462176d75412b5056d0a77dd6bdaba8c77112..848bbd06692c62d74e8256b289b5ea50b0c9970e 100644 (file)
@@ -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;
 
index 3e72ce2c0a745478b4bdc3399573ea81dde61397..a9f261839cb4207938c6d1c621d578f0536ad9ec 100644 (file)
@@ -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;
 }