Artifact edb850a0a436d217e92f175f844c99b3f841c50d90ea40deccd435289ab71f54:
- File
internal/xfer/login_cookie.go
— part of check-in
[6963b0a9dc]
at
2026-10-12 23:10:13
on branch main
— fix(sync): exchange server-version and gate SHA3 content by peer release (#303) (#358)
fossil's page_xfer answers a client-version that carries DATE and TIME with pragma server-version, and both sides record the peer's release; send_file, send_compressed_file and send_unversioned_file then append "error Fossil version 2.0 or later required." in place of SHA3-named content for a peer older than 2.0, with a server treating a client that announced no release as one (sections 7.6, 9.1). The server sent no server-version and neither side kept or gated on the peer's release.
The server gates gimme, clone and uvgimme; the client gates its answer to a gimme. Because a server-version from a current release turns fossil's client to logging in through the x-f-l-c cookie (section 6.5), the HTTP server now reads that cookie: xfer.DecodeRequest puts its login card first with the nonce bound to the whole body, as page_xfer does. Without it a real fossil client's later rounds arrived as nobody. That moves sync.6.5.server-cookie-login, marked for #304, here. (user: [email protected], size: 1850) [annotate] [blame] [check-ins using]
package xfer import ( "net/url" "strings" "github.com/danmestas/go-libfossil/internal/deck" "github.com/danmestas/go-libfossil/internal/hash" ) // LoginCookieName is the HTTP cookie in which a client may carry its login // card instead of the body, once the server's release accepts it (§6.5). const LoginCookieName = "x-f-l-c" // DecodeRequest decodes a sync request as Decode does. When loginCookie, the // value of the request's LoginCookieName cookie, holds a login card, that card // is put first, its nonce bound to the whole decompressed body: fossil's // page_xfer handles a cookie login as a login line ahead of the body, so the // "rest of the message" its nonce covers is all of it (§6.5, check_tail_hash). // A cookie that is not a login card is ignored, as page_xfer ignores it. func DecodeRequest(data []byte, contentType, loginCookie string) (*Message, error) { raw := data if len(data) > 0 && contentTypeIsCompressed(contentType) { var err error if raw, err = decompressContainer(data); err != nil { return nil, err } } msg, err := DecodeUncompressed(raw) if err != nil { return nil, err } if lc := parseLoginCookie(loginCookie); lc != nil { lc.tailSHA1 = hash.SHA1(raw) msg.Cards = append([]Card{lc}, msg.Cards...) } return msg, nil } // parseLoginCookie reads a login card from a cookie value: URL-encoded, as // fossil's %T writes it, then "login USER NONCE SIGNATURE" split on white // space, as page_xfer tokenizes it. It returns nil for anything else. func parseLoginCookie(v string) *LoginCard { if v == "" { return nil } line, err := url.QueryUnescape(v) if err != nil { return nil } tokens := strings.Fields(line) if len(tokens) != 4 || tokens[0] != "login" { return nil } return &LoginCard{ User: deck.FossilDecode(tokens[1]), Nonce: tokens[2], Signature: tokens[3], } }