package aptrepo import ( "crypto/sha256" "encoding/hex" "errors" "strconv" "strings" "time" "unicode" "unicode/utf8" "server-deploy/internal/installplan" ) var metadataError = errors.New("invalid Docker repository metadata") var pinnedPackageNames = [...]string{"docker-ce", "docker-ce-cli", "containerd.io", "docker-buildx-plugin", "docker-compose-plugin"} // Resolve parses Release bytes already authenticated by the caller and binds // explicitly pinned packages to that Release. It does not verify signatures. func Resolve(release, index []byte, suite, arch string, versions map[string]string, now time.Time) (installplan.Lock, error) { if len(release) > 1<<20 || len(index) > 16<<20 || len(versions) != len(pinnedPackageNames) { return installplan.Lock{}, metadataError } for _, name := range pinnedPackageNames { if versions[name] == "" { return installplan.Lock{}, metadataError } } var fields map[string]string if err := parseControl(release, func(stanza map[string]string) error { if fields != nil { return metadataError } fields = stanza return nil }); err != nil { return installplan.Lock{}, metadataError } if fields["suite"] != suite || fields["origin"] != "Docker" || fields["label"] != "Docker CE" || !hasWord(fields["architectures"], arch) || !hasWord(fields["components"], "stable") { return installplan.Lock{}, metadataError } date, err := time.Parse(time.RFC1123Z, fields["date"]) if err != nil || date.After(now.Add(10*time.Minute)) || date.Before(now.Add(-30*24*time.Hour)) { return installplan.Lock{}, metadataError } if value, ok := fields["valid-until"]; ok { expiry, err := time.Parse(time.RFC1123Z, value) if err != nil || !expiry.After(now) || expiry.Before(date) { return installplan.Lock{}, metadataError } } // Bind the exact uncompressed index bytes before interpreting any records. indexSum := sha256.Sum256(index) expectedPath := "stable/binary-" + arch + "/Packages" seenPaths := make(map[string]bool) matched := false for _, line := range strings.Split(fields["sha256"], "\n") { if strings.TrimSpace(line) == "" { continue } entry := strings.Fields(line) if len(entry) != 3 || !validSHA256(entry[0]) || seenPaths[entry[2]] { return installplan.Lock{}, metadataError } seenPaths[entry[2]] = true size, err := decimalSize(entry[1]) if err != nil { return installplan.Lock{}, metadataError } if entry[2] == expectedPath { if size != uint64(len(index)) || entry[0] != hex.EncodeToString(indexSum[:]) { return installplan.Lock{}, metadataError } matched = true } } if !matched { return installplan.Lock{}, metadataError } selected := make(map[string]installplan.Package) seenRecords := make(map[[3]string]bool) if err := parseControl(index, func(record map[string]string) error { identity := [3]string{record["package"], record["version"], record["architecture"]} if identity[0] != "" && identity[1] != "" && identity[2] != "" { if seenRecords[identity] { return metadataError } seenRecords[identity] = true } version, target := versions[identity[0]] if !target { return nil } for _, field := range []string{"package", "version", "architecture", "filename", "size", "sha256"} { if record[field] == "" || strings.Contains(record[field], "\n") { return metadataError } } if identity[1] != version || identity[2] != arch { return nil } size, err := decimalSize(record["size"]) if err != nil { return metadataError } selected[identity[0]] = installplan.Package{ Name: identity[0], Version: version, Filename: record["filename"], Size: size, Digest: "sha256:" + record["sha256"], } return nil }); err != nil { return installplan.Lock{}, metadataError } releaseSum := sha256.Sum256(release) lock := installplan.Lock{ ProtocolVersion: 1, Repository: "https://download.docker.com/linux/ubuntu", Suite: suite, Architecture: arch, ReleaseDigest: "sha256:" + hex.EncodeToString(releaseSum[:]), } for _, name := range pinnedPackageNames { p, ok := selected[name] if !ok { return installplan.Lock{}, metadataError } lock.Packages = append(lock.Packages, p) } if _, err := installplan.Validate(lock, suite, arch); err != nil { return installplan.Lock{}, metadataError } return lock, nil } // parseControl preserves continuation boundaries and rejects duplicate fields // before invoking visit. Processing one stanza at a time bounds retained values. func parseControl(raw []byte, visit func(map[string]string) error) error { if !utf8.Valid(raw) { return metadataError } text := strings.ReplaceAll(string(raw), "\r\n", "\n") for _, r := range text { if unicode.IsControl(r) && r != '\n' && r != '\t' { return metadataError } } fields := make(map[string]*strings.Builder) current := "" flush := func() error { if len(fields) == 0 { return nil } stanza := make(map[string]string, len(fields)) for name, value := range fields { stanza[name] = value.String() } if err := visit(stanza); err != nil { return err } fields = make(map[string]*strings.Builder) current = "" return nil } for line := range strings.SplitSeq(text, "\n") { if line == "" { if err := flush(); err != nil { return err } continue } if line[0] == ' ' || line[0] == '\t' { if current == "" { return metadataError } fields[current].WriteByte('\n') fields[current].WriteString(strings.Trim(line, " \t")) continue } name, value, ok := strings.Cut(line, ":") if !ok || name == "" { return metadataError } for _, r := range name { if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-') { return metadataError } } current = strings.ToLower(name) if _, exists := fields[current]; exists { return metadataError } fields[current] = &strings.Builder{} fields[current].WriteString(strings.Trim(value, " \t")) } return flush() } func hasWord(value, word string) bool { for _, item := range strings.Fields(value) { if item == word { return true } } return false } func decimalSize(value string) (uint64, error) { for _, r := range value { if r < '0' || r > '9' { return 0, metadataError } } size, err := strconv.ParseUint(value, 10, 64) if err != nil { return 0, metadataError } return size, nil } func validSHA256(value string) bool { if len(value) != 64 { return false } for _, r := range value { if !(r >= '0' && r <= '9' || r >= 'a' && r <= 'f') { return false } } return true }