package updater import ( "bufio" "fmt" "strings" ) // ParseSHA256Sums turns the body of a sha256sum-format file into a // filename → hex-digest map. Format per line: // // <64 hex chars> // // Anything else (blank lines, comments starting with '#') is // tolerated. Returns an error only when a line that LOOKS like an // entry is malformed — silent skipping of garbage would be the wrong // failure mode for a security-relevant input. // // Used by `banger update` after downloading the SHA256SUMS file // alongside the release tarball: look up the tarball's basename in // the resulting map to get its expected hash. func ParseSHA256Sums(body []byte) (map[string]string, error) { out := map[string]string{} scanner := bufio.NewScanner(strings.NewReader(string(body))) scanner.Buffer(make([]byte, 64*1024), 64*1024) lineNo := 0 for scanner.Scan() { lineNo++ line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } // Tolerate the BSD-style `SHA256 (file) = hex` form too — // some signing pipelines emit it. The GNU-style is what // `sha256sum` defaults to. if rest, ok := strings.CutPrefix(line, "SHA256 ("); ok { closingParen := strings.Index(rest, ")") eq := strings.LastIndex(rest, "= ") if closingParen <= 0 || eq <= closingParen { return nil, fmt.Errorf("line %d: malformed BSD-style sum line", lineNo) } file := strings.TrimSpace(rest[:closingParen]) digest := strings.TrimSpace(rest[eq+2:]) if !looksLikeSHA256(digest) { return nil, fmt.Errorf("line %d: digest %q is not a 64-char hex sha256", lineNo, digest) } out[file] = strings.ToLower(digest) continue } fields := strings.Fields(line) if len(fields) < 2 { return nil, fmt.Errorf("line %d: expected ` `, got %q", lineNo, line) } digest := fields[0] // GNU format may prefix the filename with `*` for binary mode // (` *file`) or a leading space; trim it. filename := strings.TrimSpace(strings.Join(fields[1:], " ")) filename = strings.TrimPrefix(filename, "*") if !looksLikeSHA256(digest) { return nil, fmt.Errorf("line %d: digest %q is not a 64-char hex sha256", lineNo, digest) } out[filename] = strings.ToLower(digest) } if err := scanner.Err(); err != nil { return nil, err } if len(out) == 0 { return nil, fmt.Errorf("SHA256SUMS body contained no entries") } return out, nil } // looksLikeSHA256 returns true when s is exactly 64 hex characters. // Doesn't check that those bytes are themselves a valid digest of // anything — that's the cryptographic verifier's job, not the // parser's. func looksLikeSHA256(s string) bool { if len(s) != 64 { return false } for _, c := range s { switch { case c >= '0' && c <= '9': case c >= 'a' && c <= 'f': case c >= 'A' && c <= 'F': default: return false } } return true }