Artifact dacca141d7c0e634067a8d19d33e7869f79960c783cde7d2e807242c8dc74fc4:
- File docs/rfcs/erratum/sync-protocol-00-s4-redirect-xfer-strip-once.md — part of check-in [275e884be5] at 2026-10-11 21:47:48 on branch trunk — errata: sync-protocol behaviours where Fossil departs from the draft (user: dmestas, size: 4102) [annotate] [blame] [check-ins using]
Erratum — draft-fossil-sync-protocol-00 §4 (redirect, single /xfer strip)
| Document | draft-fossil-sync-protocol-00 |
| Section | 4 — Transport (redirect following) |
| Type | Technical (correction — Fossil strips one trailing /xfer, not repeatedly) |
| Status | Proposed — operator decision pending |
| Reported | 2026-10-11 |
| Submitter | Dan Mestas |
| Found via | go-libfossil's conformance suite against Fossil 2.28 (scope note SP-4-6, case sync.4.redirect-strips-xfer); confirmed by reading Fossil's source and by redirecting a Fossil client |
| Amends | sync-protocol-00-s4-redirect-xfer-strip.md (2026-07-10), whose "repeated while such a segment remains" wording zit's draft carries |
1. Summary
§4 says to "Repeatedly strip trailing /xfer from the redirect target". Fossil
strips exactly one: a redirect to /b/xfer/xfer is followed to /b/xfer.
The source has a while loop, but the loop cannot run twice, so the
observable rule is a single strip.
The 10 July erratum that introduced the /xfer rule said the strip "is
repeated while such a segment remains". It was right about /xfer and wrong
about the repetition; zit's draft and the 29 July copy both carry that
wording.
2. Location
§4, redirect paragraph (~lines 847-849):
Repeatedly strip trailing /xfer from the redirect target, then parse the result as the new base; apply no other path transformation.
3. The divergence
Source, Fossil 2.28 http_exchange() (src/http.c:633-637):
j = strlen(zLine) - 1;
while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){
j -= 4;
zLine[j] = 0;
}
j starts on the last character, and the test compares the five characters
ending at j. A match truncates at the /, which is j-4, but leaves j on
the new terminator rather than the new last character. The next test then
compares a four-character tail with the five-character "/xfer", which can
never match. One strip, whatever the target.
Run: a scripted HTTP peer answered the first POST of fossil pull with
307 Location: http://127.0.0.1:PORT/b/xfer/xfer. Fossil printed
redirect with status 307 to http://127.0.0.1:PORT/b/xfer and sent its second
POST to /b/xfer.
4. Impact
Only targets ending in /xfer/xfer are affected, which in practice means a
repository whose own URL ends in /xfer. For those, a client following the
draft goes one level too far (/b) where Fossil goes to /b/xfer. Fossil's
result is the one that keeps the repository path: only the endpoint suffix
the server appended is removed.
5. Correction
- or local-file transports. Repeatedly strip trailing /xfer from the
- redirect target, then parse the result as the new base; apply no
- other path transformation. For an accepted redirect, Fossil parses
+ or local-file transports. Strip one trailing /xfer, if present, from
+ the redirect target, then parse the result as the new base; apply no
+ other path transformation. For an accepted redirect, Fossil parses
6. Rationale
- It is what Fossil does, and the case cannot be written to match the current text.
- It is the better rule. The suffix being undone is the single
/xfera server appends to name its sync endpoint. Repeating the strip can remove part of a repository path that happens to end inxfer. - Keeping the loop's apparent intent would buy nothing. No server needs a second strip, so describing the loop as written by its effect is safe.
7. Verification
- Source: Fossil 2.28
src/http.c:616-637(the 301/302/307/308 branch and the strip loop). - Run: Fossil 2.28
fossil pullagainst a scripted peer that redirects the first request to/b/xfer/xfer; the second request's path is/b/xfer. - go-libfossil's case uses a single
/xferfor this reason (comment abovestripXferinconformance/sync_http_cases_test.go).
Prepared against draft-fossil-sync-protocol-00. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.