package bundle import ( "fmt" "os" "path/filepath" ) func ValidateManifest(manifest Manifest) error { if manifest.SchemaVersion != SchemaVersion { return fmt.Errorf("schema_version must be %d", SchemaVersion) } if manifest.ID == "" { return fmt.Errorf("id is required") } if err := validateDigest(manifest.Digest); err != nil { return fmt.Errorf("digest: %w", err) } if manifest.Created.IsZero() { return fmt.Errorf("created is required") } if len(manifest.Files) == 0 { return fmt.Errorf("files is required") } seen := make(map[string]struct{}, len(manifest.Files)) for index, file := range manifest.Files { if err := ValidateSourcePath(file.Path); err != nil { return fmt.Errorf("files[%d].path: %w", index, err) } if err := validateDigest(file.SHA256); err != nil { return fmt.Errorf("files[%d].sha256: %w", index, err) } if file.Size < 0 { return fmt.Errorf("files[%d].size must be non-negative", index) } if _, exists := seen[file.Path]; exists { return fmt.Errorf("files[%d].path duplicates %q", index, file.Path) } seen[file.Path] = struct{}{} } if actual := BundleDigest(manifest.Files); actual != manifest.Digest { return fmt.Errorf("digest mismatch: got %s want %s", actual, manifest.Digest) } return nil } func ValidateBundle(root string, manifest Manifest) error { if err := ValidateManifest(manifest); err != nil { return err } files := append([]ManifestFile(nil), manifest.Files...) for index, manifestFile := range files { fullPath := filepath.Join(root, filepath.FromSlash(manifestFile.Path)) info, err := os.Lstat(fullPath) if err != nil { return fmt.Errorf("file %q stat: %w", manifestFile.Path, err) } if !info.Mode().IsRegular() { return fmt.Errorf("file %q must be a regular file", manifestFile.Path) } if info.Size() != manifestFile.Size { return fmt.Errorf("file %q size mismatch: got %d want %d", manifestFile.Path, info.Size(), manifestFile.Size) } data, err := os.ReadFile(fullPath) if err != nil { return fmt.Errorf("file %q read: %w", manifestFile.Path, err) } actualDigest := FileDigest(data) if actualDigest != manifestFile.SHA256 { return fmt.Errorf("file %q sha256 mismatch: got %s want %s", manifestFile.Path, actualDigest, manifestFile.SHA256) } files[index].SHA256 = actualDigest files[index].Size = int64(len(data)) } if actualDigest := BundleDigest(files); actualDigest != manifest.Digest { return fmt.Errorf("digest mismatch: got %s want %s", actualDigest, manifest.Digest) } return nil }