fix: emit inclusive range-end in Cryptify Content-Range header#34
Conversation
The chunked Cryptify upload emitted the exclusive end index in the
Content-Range header (`bytes {offset}-{end}/*`), but per RFC 9110 §14.4
range-end is inclusive. A first 1 MB chunk produced `bytes 0-1048576/*`,
overlapping the next chunk by one byte. Emit `{end - 1}` so the header
pins the last byte actually written.
Add CryptifyContentRangeTests driving UploadAsync through a fake
HttpMessageHandler: one test pins the inclusive end of a single chunk,
the other verifies consecutive chunks are contiguous and non-overlapping.
Refs #28
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Rules Dobby 2 — sign-off (no blocking issues). Consolidated rule check + code review found nothing to fix. (Posted as a COMMENT rather than APPROVE only because GitHub forbids approving one's own PR; treat this as a green light, not a withheld verdict.)
The fix is correct: end is the exclusive chunk-end (offset + chunkLen), and emitting end - 1 yields the RFC 9110 §14.4 inclusive range-end. chunkLen >= 1 guarantees end > offset, so end - 1 never underflows; the empty-payload path skips the chunk loop and finalize is untouched. The two regression tests genuinely pin the new behaviour — bytes 0-4/* for a 5-byte upload (old code emitted 0-5), and contiguous non-overlapping ranges across a ChunkSize + 1 upload.
Rule compliance:
- ✅ tests-required-on-fixes — regression tests added that would have failed pre-fix.
- ✅ conventional-commit-pr-titles —
fix:prefix present. - ✅ no-justification-paragraphs — the code comment documents a real underflow invariant; the longer "Upstream verification" PR section answers a check explicitly requested in #28.
- ✅ Repo conventions — xUnit
[Fact], file-scoped namespace, workspace net8.0-runtime limitation noted in the PR body.
Nice catch on the upstream check: noting that postguard-js carries the same exclusive form (so the server is permissive today) correctly frames this as spec-correctness/robustness rather than a live wire break.
Fixes the off-by-one reported in #28.
Problem
StoreChunkAsyncinsrc/Api/CryptifyClient.csemitted the exclusive end index in theContent-Rangeheader:endisoffset + chunkLen(one past the last byte). Per RFC 9110 §14.4range-endis inclusive, so a first 1 MB chunk producedbytes 0-1048576/*— overlapping the next chunk (1048576-…) by one byte.Fix
Emit the inclusive last-byte index:
end > offsetalways holds (chunkLen >= 1), so this never goes negative.Upstream verification (requested in the issue)
The issue asked to check whether
postguard-js/ pg-fallback emits the inclusive or exclusive form before patching. I checked: postguard-js emits the same exclusive form —src/api/cryptify.ts:204:So the Cryptify server is permissive and accepts the exclusive form today. This change is therefore spec-correctness (and robustness against a stricter server / RFC-enforcing proxy), not a wire-protocol divergence fix — the existing behaviour is not currently broken against the live server. The upstream JS client has the same off-by-one and could be aligned separately.
Tests
Added
tests/E4A.PostGuard.Tests/CryptifyContentRangeTests.cs, drivingUploadAsyncthrough a fakeHttpMessageHandlerthat records each chunk PUT'sContent-Range:StoreChunk_EmitsInclusiveEndByte— a 5-byte upload pinsbytes 0-4/*(regression: old code emittedbytes 0-5/*).StoreChunk_ConsecutiveChunksDoNotOverlap— aChunkSize + 1upload pins the first chunk atbytes 0-1048575/*and the second atbytes 1048576-1048576/*, proving the ranges are contiguous with no overlapping byte.Validation
dotnet test --framework net10.0→ 27 passed, 0 failed.dotnet buildsucceeds for bothnet8.0andnet10.0.