diff --git a/S3-Conditional-Operations.md b/S3-Conditional-Operations.md index e913278..ba59ffa 100644 --- a/S3-Conditional-Operations.md +++ b/S3-Conditional-Operations.md @@ -14,12 +14,49 @@ ring changes. ## Supported Conditional Headers -| Header | Applies To | Condition | Use Case | -|--------|------------|-----------|----------| -| **If-Match** | GET, PUT, COPY | ETag matches | Ensure object hasn't changed | -| **If-None-Match** | GET, PUT, COPY | ETag doesn't match | Prevent overwrites, caching | -| **If-Modified-Since** | GET, COPY | Modified after date | Conditional downloads | -| **If-Unmodified-Since** | GET, PUT, COPY | Not modified after date | Safe modifications | +These conditions act on the **target object** (the object being read or +written). All four are supported on GET, HEAD, PUT, POST +(`CompleteMultipartUpload`), and COPY. DELETE supports only `If-Match`. + +| Header | Type | Applies to | Special values | +|--------|------|------------|----------------| +| `If-Match` | ETag | GET, HEAD, PUT, POST, COPY, **DELETE** | `*` matches any existing object; comma-separated ETag list is honored | +| `If-None-Match` | ETag | GET, HEAD, PUT, POST, COPY | `*` matches only when the object does **not** exist (compare-and-create); comma-separated ETag list is honored | +| `If-Modified-Since` | RFC 1123 date | GET, HEAD, PUT, POST, COPY | — | +| `If-Unmodified-Since` | RFC 1123 date | GET, HEAD, PUT, POST, COPY | — | + +### Copy-source conditionals + +`CopyObject` and `UploadPartCopy` accept a second set of headers that +condition on the **source** object. They are independent of the four +standard headers above (which condition on the destination), so a single +copy request can carry conditions for both ends. + +| Header | Type | +|--------|------| +| `x-amz-copy-source-if-match` | ETag | +| `x-amz-copy-source-if-none-match` | ETag | +| `x-amz-copy-source-if-modified-since` | RFC 1123 date | +| `x-amz-copy-source-if-unmodified-since` | RFC 1123 date | + +### Evaluation order + +Per [RFC 7232 §6](https://datatracker.ietf.org/doc/html/rfc7232#section-6) +and AWS S3, the conditions are evaluated in this order; later conditions +are skipped when an earlier one is present: + +1. `If-Match` +2. `If-Unmodified-Since` — ignored if `If-Match` is present + ([RFC 7232 §3.4](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4)) +3. `If-None-Match` +4. `If-Modified-Since` — ignored if `If-None-Match` is present + ([RFC 7232 §3.3](https://datatracker.ietf.org/doc/html/rfc7232#section-3.3)) + +### Not supported + +* `If-Range`. A `Range` request without `If-Range` works normally; there is + no header to fall back to a full-object response when the source has + changed. ## HTTP Examples @@ -78,15 +115,35 @@ curl -X PUT \ -H "x-amz-copy-source: /source-bucket/source-file.txt" \ -H "x-amz-copy-source-if-match: \"source-etag\"" \ "http://localhost:8333/dest-bucket/dest-file.txt" + +# Same copy, but also refuse to overwrite the destination +curl -X PUT \ + -H "x-amz-copy-source: /source-bucket/source-file.txt" \ + -H "x-amz-copy-source-if-match: \"source-etag\"" \ + -H "If-None-Match: *" \ + "http://localhost:8333/dest-bucket/dest-file.txt" ``` +### Conditional DELETE + +```bash +# Delete only if the object's ETag still matches what we read +curl -X DELETE \ + -H "If-Match: \"d41d8cd98f00b204e9800998ecf8427e\"" \ + "http://localhost:8333/mybucket/myfile.txt" +``` + +DELETE supports `If-Match` only; the other three conditional headers are +ignored on DELETE per the AWS S3 spec. + ## HTTP Status Codes -| Status Code | Condition | Meaning | -|-------------|-----------|---------| -| **200 OK** | Condition met | Operation succeeded | -| **304 Not Modified** | If-None-Match matched | Object unchanged (GET only) | -| **412 Precondition Failed** | Condition not met | Operation blocked by condition | +| Status Code | When | Meaning | +|-------------|------|---------| +| **200 OK** | All conditions met | Operation succeeded normally | +| **304 Not Modified** | GET/HEAD with `If-None-Match` match, or `If-Modified-Since` not exceeded | Object unchanged; response carries the ETag header but no body | +| **400 Invalid Request** | `If-Modified-Since` or `If-Unmodified-Since` is not a valid RFC 1123 date | Header is malformed | +| **412 Precondition Failed** | Any other failed condition (all writes; GET/HEAD with `If-Match` mismatch or `If-Unmodified-Since` exceeded) | Operation blocked by condition | ## Atomicity and concurrency