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
This commit is contained in:
Dongsu Park 2018-09-03 20:44:13 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent 8b247b43c8
commit 21224070e8
3 changed files with 47 additions and 1 deletions

View 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;
}

View 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;

View 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;
}