Skip to content

Send S3 conditional headers with quoted entity-tags - #106

Open
jilio wants to merge 1 commit into
tobi:mainfrom
jilio:fix/s3-quoted-etag
Open

jilio wants to merge 1 commit into
tobi:mainfrom
jilio:fix/s3-quoted-etag

Conversation

@jilio

@jilio jilio commented Oct 11, 2026 •

Copy link
Copy Markdown

Problem

S3Store stores ETags unquoted in Version and sends them back unquoted in conditional headers:

  • If-Match: abc on the manifest CAS (PutMode::Update) and on GETs with if_match;
  • If-None-Match: abc on the conditional manifest GET.

HTTP defines both headers over quoted entity-tags (RFC 9110 §8.8.3 and §13.1.1; the S3 API reference points to RFC 7232). The ETag header of every S3 response carries that quoted form. AWS S3 accepts a bare value (its CLI example sends one), and so does rustfs. Neither behaviour is documented for S3-compatible stores in general, and Cloudflare R2's docs are silent on it. A store that compares the tag strictly would refuse every manifest CAS with 412, so no push could ever commit. It would also never answer the conditional GET with 304.

Fix

entity_tag() renders a Version as a quoted entity-tag. A value that is already quoted is left alone. All three conditional headers use it. Versions stay unquoted, so nothing stored or compared changes. GCS is unaffected because it uses generations.

Checked

  • The store contract suite (cargo test -p walgit-store --test contract, s3_contract) passes against rustfs 1.0.1. A curl probe shows rustfs honours both the quoted and the bare form, for If-Match (200/412) and for If-None-Match (304).
  • Against Cloudflare R2, a quoted If-Match returned 200 on a match and 412 on a mismatch.
  • conditional_headers_carry_quoted_entity_tags records the headers sent to a fake S3. Before this change it sees If-Match: abc.
  • On Linux (Rust 1.97.1, git 2.47.3, 2-CPU container, non-root), this branch alone passes fmt, clippy (walgit-store, all targets) and the walgit-store unit and contract tests. just ci passes on a merge of this branch with my two other open fixes (Keep a 412'd log segment unless the manifest proves it uncommitted #104, Resolve a {"repo": …} events notify under a store prefix #105).

🤖 Generated with Claude Code

S3Store keeps ETags unquoted in Version (quotes are stripped on read) and
put them on the wire as they are: `If-Match: abc` on the manifest CAS and
on GETs, `If-None-Match: abc` on the conditional manifest GET. HTTP
defines both headers over quoted entity-tags (RFC 9110 sections 8.8.3 and
13.1.1; the S3 API docs defer to RFC 7232), and the ETag header of every
S3 response carries that form. AWS S3 (its CLI example sends a bare
value) and rustfs accept the bare value too, but that is not documented
for S3-compatible stores in general, and Cloudflare R2's docs are silent
on it. A store that compares the tag strictly would refuse every manifest
CAS (no push could ever commit) and never answer the conditional GET
with 304.

entity_tag() renders a Version as a quoted entity-tag (once; an already
quoted value is left alone) and every conditional header uses it.
Versions stay unquoted, so nothing stored or compared changes.

Checked: the store contract suite passes against rustfs 1.0.1, which
honours both forms; against Cloudflare R2, a quoted If-Match returned 200
on a match and 412 on a mismatch. A new unit test records the headers
walgit sends to a fake S3; before this change it saw `If-Match: abc`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant