diff options
| author | Hauke Mehrtens | 2026-05-03 20:51:24 +0000 |
|---|---|---|
| committer | Hauke Mehrtens | 2026-05-03 23:59:50 +0000 |
| commit | 05406f70d05cae77de4e7e4164c79f9c14c2a485 (patch) | |
| tree | 844b37aa89be47c58650be15e305f88ea7998b86 | |
| parent | 07f0afb3bf9160b90111009829676da1758060ff (diff) | |
| download | uhttpd-05406f70d05cae77de4e7e4164c79f9c14c2a485.tar.gz | |
file: scan all entries when matching If-Match / If-None-Match
The list-walking logic in uh_file_if_match and uh_file_if_none_match
was broken in two ways:
1. The for-loop guard recomputed strlen(hdr) on every iteration,
but the body wrote a NUL terminator into the buffer at each
separator. After the first comma or space, strlen returns the
length of the truncated prefix, so the loop exits before any
subsequent tags are reached.
2. After NUL-terminating the current tag the body did
'hdr[i++] = 0; p = &hdr[i];', and the for-loop's own i++ then
advanced past the first character of the next tag.
In addition strcmp(p, ...) was only run on non-separator iterations,
so it never compared a fully NUL-terminated tag against the target
in the general case.
Snapshot the header length once, iterate up to and including the
trailing NUL, and compare each accumulated tag at every separator
boundary. This makes If-Match / If-None-Match honor every entry in
the list, so cache validation produces the correct 304 / 412
response when the matching tag is not the first one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
| -rw-r--r-- | file.c | 43 |
1 files changed, 23 insertions, 20 deletions
@@ -377,20 +377,21 @@ static bool uh_file_if_match(struct client *cl, struct stat *s) char buf[128]; const char *tag = uh_file_mktag(s, buf, sizeof(buf)); char *hdr = uh_file_header(cl, HDR_IF_MATCH); + int hlen, i; char *p; - int i; if (!hdr) return true; + hlen = strlen(hdr); p = &hdr[0]; - for (i = 0; i < strlen(hdr); i++) + for (i = 0; i <= hlen; i++) { - if ((hdr[i] == ' ') || (hdr[i] == ',')) { - hdr[i++] = 0; - p = &hdr[i]; - } else if (!strcmp(p, "*") || !strcmp(p, tag)) { - return true; + if (hdr[i] == ' ' || hdr[i] == ',' || hdr[i] == '\0') { + hdr[i] = 0; + if (*p && (!strcmp(p, "*") || !strcmp(p, tag))) + return true; + p = &hdr[i + 1]; } } @@ -418,25 +419,27 @@ static int uh_file_if_none_match(struct client *cl, struct stat *s) char buf[128]; const char *tag = uh_file_mktag(s, buf, sizeof(buf)); char *hdr = uh_file_header(cl, HDR_IF_NONE_MATCH); + int hlen, i; char *p; - int i; if (!hdr) return true; + hlen = strlen(hdr); p = &hdr[0]; - for (i = 0; i < strlen(hdr); i++) { - if ((hdr[i] == ' ') || (hdr[i] == ',')) { - hdr[i++] = 0; - p = &hdr[i]; - } else if (!strcmp(p, "*") || !strcmp(p, tag)) { - if ((cl->request.method == UH_HTTP_MSG_GET) || - (cl->request.method == UH_HTTP_MSG_HEAD)) - uh_file_response_304(cl, s); - else - uh_file_response_412(cl); - - return false; + for (i = 0; i <= hlen; i++) { + if (hdr[i] == ' ' || hdr[i] == ',' || hdr[i] == '\0') { + hdr[i] = 0; + if (*p && (!strcmp(p, "*") || !strcmp(p, tag))) { + if ((cl->request.method == UH_HTTP_MSG_GET) || + (cl->request.method == UH_HTTP_MSG_HEAD)) + uh_file_response_304(cl, s); + else + uh_file_response_412(cl); + + return false; + } + p = &hdr[i + 1]; } } |