Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

787 lines
26 KiB
Go

package artifact
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"os"
"path"
"path/filepath"
"strings"
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
)
const packagedManifestPath = "connector-manifest.json"
const receiptFilename = ".tutti-connector-receipt.json"
type Fetcher interface {
Fetch(ctx context.Context, request FetchRequest) (FetchResponse, error)
}
type FetchRequest struct {
OperationID string
Scope market.OperationScope
Generation market.HostGeneration
Release market.Release
}
type FetchResponse struct {
Body io.ReadCloser
ContentLength int64
MediaType string
}
type Limits struct {
MaxDownloadBytes int64
MaxFiles int
MaxFileBytes int64
MaxExpandedBytes int64
MaxCompressionRatio int64
}
func DefaultLimits() Limits {
return Limits{
MaxDownloadBytes: 256 << 20,
MaxFiles: 10_000,
MaxFileBytes: 64 << 20,
MaxExpandedBytes: 512 << 20,
MaxCompressionRatio: 200,
}
}
type Config struct {
RootDir string
Fetcher Fetcher
Limits Limits
}
type ImporterConfig struct {
RootDir string
Limits Limits
}
type ImportArchiveRequest struct {
OperationID string
Scope market.OperationScope
Generation market.HostGeneration
Release market.Release
ArchivePath string
}
// Importer installs an already-synchronized archive. It has no Fetcher and
// therefore cannot perform network I/O; remote runtime owners use it after
// their host data plane has transferred the verified candidate.
type Importer struct {
mechanics *Preparer
}
type Preparer struct {
rootDir string
cache *DownloadCache
limits Limits
}
var _ market.ArtifactPreparer = (*Preparer)(nil)
func NewImporter(config ImporterConfig) (*Importer, error) {
root, limits, err := validateArtifactRoot(config.RootDir, config.Limits)
if err != nil {
return nil, err
}
return &Importer{mechanics: &Preparer{rootDir: root, limits: limits}}, nil
}
func (importer *Importer) Import(
ctx context.Context,
request ImportArchiveRequest,
) (market.PreparedArtifactReceipt, error) {
prepareRequest := market.PrepareArtifactRequest{OperationID: request.OperationID, Scope: request.Scope,
Generation: request.Generation, Release: request.Release}
if err := validatePrepareRequest(prepareRequest); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if !filepath.IsAbs(request.ArchivePath) {
return market.PreparedArtifactReceipt{}, errors.New("connector synchronized archive path must be absolute")
}
if err := verifyArtifactFile(request.ArchivePath, request.Release.Artifact); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("verify synchronized connector artifact: %w", err)
}
return importer.mechanics.importArchive(ctx, prepareRequest, request.ArchivePath)
}
func (importer *Importer) ResolvePrepared(
ctx context.Context,
release market.Release,
) (market.PreparedArtifactReceipt, error) {
if err := ctx.Err(); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if err := market.ValidateRuntimeReleaseShape(release); err != nil {
return market.PreparedArtifactReceipt{}, err
}
target, err := importer.mechanics.preparedPath(release)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
if receipt, ok := readExistingReceipt(target, market.PrepareArtifactRequest{Release: release}); ok {
return receipt, nil
}
if _, statErr := os.Stat(target); errors.Is(statErr, os.ErrNotExist) {
return market.PreparedArtifactReceipt{}, market.ErrReleaseInstallationAbsent
} else if statErr != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("inspect prepared connector artifact: %w", statErr)
}
return market.PreparedArtifactReceipt{}, market.ErrReleaseInstallationInvalid
}
func (importer *Importer) Remove(ctx context.Context, request market.RemoveArtifactRequest) error {
return importer.mechanics.removePrepared(ctx, request)
}
func (importer *Importer) RemoveConnector(ctx context.Context, request market.RemoveConnectorInstallationRequest) error {
return importer.mechanics.removePreparedConnector(ctx, request.ConnectorKey)
}
// ResolvePrepared revalidates the installed receipt, packaged
// manifest, and full inventory before a durable connector runtime is restored.
// An invalid prepared tree is rebuilt from the verified artifact blob; a
// receipt is never treated as an authenticity root by itself.
func (preparer *Preparer) ResolvePrepared(ctx context.Context, release market.Release) (market.PreparedArtifactReceipt, error) {
if err := ctx.Err(); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if err := market.ValidateRuntimeReleaseShape(release); err != nil {
return market.PreparedArtifactReceipt{}, err
}
target, err := preparer.preparedPath(release)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
receipt, ok := readExistingReceipt(target, market.PrepareArtifactRequest{Release: release})
if ok {
return receipt, nil
}
if _, statErr := os.Stat(target); errors.Is(statErr, os.ErrNotExist) {
return market.PreparedArtifactReceipt{}, market.ErrReleaseInstallationAbsent
} else if statErr != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("inspect prepared connector artifact: %w", statErr)
}
return market.PreparedArtifactReceipt{}, market.ErrReleaseInstallationInvalid
}
func NewPreparer(config Config) (*Preparer, error) {
if config.Fetcher == nil {
return nil, errors.New("connector artifact fetcher is required")
}
root, limits, err := validateArtifactRoot(config.RootDir, config.Limits)
if err != nil {
return nil, err
}
cache, err := NewDownloadCache(DownloadCacheConfig{RootDir: filepath.Join(root, "cache"),
Fetcher: config.Fetcher, MaxDownloadBytes: limits.MaxDownloadBytes})
if err != nil {
return nil, err
}
return &Preparer{rootDir: root, cache: cache, limits: limits}, nil
}
func (preparer *Preparer) Prepare(
ctx context.Context,
request market.PrepareArtifactRequest,
) (market.PreparedArtifactReceipt, error) {
if err := validatePrepareRequest(request); err != nil {
return market.PreparedArtifactReceipt{}, err
}
cached, err := preparer.cache.PrepareCandidate(ctx, request)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
receipt, err := preparer.importArchive(ctx, request, cached.Path)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
if _, err := preparer.cache.PromoteCandidate(ctx, cached, request.Release); err != nil {
return market.PreparedArtifactReceipt{}, err
}
return receipt, nil
}
func (preparer *Preparer) importArchive(
ctx context.Context,
request market.PrepareArtifactRequest,
archivePath string,
) (market.PreparedArtifactReceipt, error) {
if err := ctx.Err(); err != nil {
return market.PreparedArtifactReceipt{}, err
}
target, err := preparer.preparedPath(request.Release)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
staging := filepath.Join(preparer.rootDir, "staging", request.OperationID)
if err := ensureWithin(preparer.rootDir, staging); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if err := os.RemoveAll(staging); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("reset connector artifact staging directory: %w", err)
}
if err := os.MkdirAll(staging, 0o700); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("create connector artifact staging directory: %w", err)
}
defer func() { _ = os.RemoveAll(staging) }()
extracted := filepath.Join(staging, "extracted")
if err := os.MkdirAll(extracted, 0o700); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("create connector artifact extraction directory: %w", err)
}
if err := preparer.extract(archivePath, request.Release.Artifact.MediaType, extracted); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if err := verifyPackagedManifest(extracted, request.Release.ManifestDigest); err != nil {
return market.PreparedArtifactReceipt{}, err
}
inventoryDigest, err := inventoryDigest(extracted)
if err != nil {
return market.PreparedArtifactReceipt{}, err
}
// A local receipt is not an authenticity root. Reuse is allowed only after
// extracting the digest-verified artifact blob for this attempt and proving
// that the prepared tree has the same inventory as those freshly verified bytes.
if existing, ok := readExistingReceipt(target, request); ok && existing.InventoryDigest == inventoryDigest {
existing.OperationID = request.OperationID
return existing, nil
}
receipt := market.PreparedArtifactReceipt{
OperationID: request.OperationID,
ConnectorKey: request.Release.ConnectorKey,
Version: request.Release.Version,
ReleaseDigest: request.Release.ReleaseDigest,
ArtifactSHA256: request.Release.Artifact.SHA256,
InventoryDigest: inventoryDigest,
PreparedPath: target,
}
if err := writeReceipt(extracted, receipt); err != nil {
return market.PreparedArtifactReceipt{}, err
}
if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("create connector prepared parent directory: %w", err)
}
if err := os.RemoveAll(target); err != nil {
return market.PreparedArtifactReceipt{}, fmt.Errorf("replace invalid connector prepared artifact: %w", err)
}
if err := os.Rename(extracted, target); err != nil {
if existing, ok := readExistingReceipt(target, request); ok && existing.InventoryDigest == inventoryDigest {
existing.OperationID = request.OperationID
return existing, nil
}
return market.PreparedArtifactReceipt{}, fmt.Errorf("promote connector prepared artifact: %w", err)
}
if err := syncDirectory(filepath.Dir(target)); err != nil {
return market.PreparedArtifactReceipt{}, err
}
return receipt, nil
}
func (preparer *Preparer) Remove(ctx context.Context, request market.RemoveArtifactRequest) error {
if err := preparer.removePrepared(ctx, request); err != nil {
return err
}
return preparer.cache.RemoveConnector(ctx, request.ConnectorKey)
}
func (preparer *Preparer) RemoveConnector(ctx context.Context, request market.RemoveConnectorInstallationRequest) error {
if err := preparer.removePreparedConnector(ctx, request.ConnectorKey); err != nil {
return err
}
return preparer.cache.RemoveConnector(ctx, request.ConnectorKey)
}
func (preparer *Preparer) removePreparedConnector(ctx context.Context, connectorKey string) error {
if err := ctx.Err(); err != nil {
return err
}
if !safeSegment(connectorKey) {
return errors.New("connector artifact removal identity is invalid")
}
target := filepath.Join(preparer.rootDir, "prepared", connectorKey)
if err := ensureWithin(preparer.rootDir, target); err != nil {
return err
}
if err := removeAllWithin(preparer.rootDir, target); err != nil {
return fmt.Errorf("remove Connector prepared artifacts: %w", err)
}
return nil
}
func (preparer *Preparer) removePrepared(ctx context.Context, request market.RemoveArtifactRequest) error {
if err := ctx.Err(); err != nil {
return err
}
if !safeSegment(request.ConnectorKey) || !safeSegment(request.Version) || !isSHA256(request.ReleaseDigest) {
return errors.New("connector artifact removal identity is invalid")
}
target := filepath.Join(preparer.rootDir, "prepared", request.ConnectorKey, request.Version, request.ReleaseDigest)
if err := ensureWithin(preparer.rootDir, target); err != nil {
return err
}
if err := removeAllWithin(preparer.rootDir, target); err != nil {
return fmt.Errorf("remove connector prepared artifact: %w", err)
}
return nil
}
func (preparer *Preparer) extract(archivePath, mediaType, destination string) error {
mediaType, _, _ = mime.ParseMediaType(mediaType)
switch strings.ToLower(mediaType) {
case "application/zip", "application/vnd.tutti.connector+zip":
return preparer.extractZIP(archivePath, destination)
case "application/gzip", "application/x-gzip", "application/vnd.tutti.connector+tar+gzip":
return preparer.extractTarGzip(archivePath, destination)
default:
return fmt.Errorf("unsupported connector artifact media type %q", mediaType)
}
}
func (preparer *Preparer) extractZIP(archivePath, destination string) error {
reader, err := zip.OpenReader(archivePath)
if err != nil {
return fmt.Errorf("open connector zip artifact: %w", err)
}
defer reader.Close()
if len(reader.File) > preparer.limits.MaxFiles {
return fmt.Errorf("connector artifact contains more than %d entries", preparer.limits.MaxFiles)
}
var expanded int64
seenEntries := make(map[string]struct{}, len(reader.File))
for _, entry := range reader.File {
if entry.Mode()&os.ModeSymlink != 0 || (!entry.FileInfo().IsDir() && !entry.Mode().IsRegular()) {
return fmt.Errorf("connector artifact entry %q is not a regular file or directory", entry.Name)
}
entryKey, err := safeArchiveEntryKey(entry.Name)
if err != nil {
return err
}
if _, duplicate := seenEntries[entryKey]; duplicate {
return fmt.Errorf("connector artifact contains duplicate or case-colliding entry %q", entry.Name)
}
seenEntries[entryKey] = struct{}{}
target, err := safeArchiveTarget(destination, entry.Name)
if err != nil {
return err
}
if entry.FileInfo().IsDir() {
if err := os.MkdirAll(target, 0o700); err != nil {
return err
}
continue
}
declared := int64(entry.UncompressedSize64)
if err := preparer.checkExpandedSize(declared, &expanded, int64(entry.CompressedSize64)); err != nil {
return fmt.Errorf("connector artifact entry %q: %w", entry.Name, err)
}
body, err := entry.Open()
if err != nil {
return err
}
if err := writeArchiveFile(target, body, declared); err != nil {
body.Close()
return err
}
if err := body.Close(); err != nil {
return err
}
}
return nil
}
func (preparer *Preparer) extractTarGzip(archivePath, destination string) error {
file, err := os.Open(archivePath)
if err != nil {
return err
}
defer file.Close()
gzipReader, err := gzip.NewReader(file)
if err != nil {
return fmt.Errorf("open connector gzip artifact: %w", err)
}
defer gzipReader.Close()
reader := tar.NewReader(gzipReader)
var expanded int64
entries := 0
seenEntries := make(map[string]struct{})
for {
header, err := reader.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return fmt.Errorf("read connector tar artifact: %w", err)
}
entries++
if entries > preparer.limits.MaxFiles {
return fmt.Errorf("connector artifact contains more than %d entries", preparer.limits.MaxFiles)
}
entryKey, err := safeArchiveEntryKey(header.Name)
if err != nil {
return err
}
if _, duplicate := seenEntries[entryKey]; duplicate {
return fmt.Errorf("connector artifact contains duplicate or case-colliding entry %q", header.Name)
}
seenEntries[entryKey] = struct{}{}
target, err := safeArchiveTarget(destination, header.Name)
if err != nil {
return err
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0o700); err != nil {
return err
}
case tar.TypeReg, byte(0):
if err := preparer.checkExpandedSize(header.Size, &expanded, 0); err != nil {
return fmt.Errorf("connector artifact entry %q: %w", header.Name, err)
}
if err := writeArchiveFile(target, reader, header.Size); err != nil {
return err
}
default:
return fmt.Errorf("connector artifact entry %q has unsupported link or device type", header.Name)
}
}
compressed, err := file.Stat()
if err != nil {
return err
}
if compressed.Size() <= 0 || (expanded > 0 && expanded/compressed.Size() > preparer.limits.MaxCompressionRatio) {
return fmt.Errorf("connector artifact compression ratio exceeds limit of %d", preparer.limits.MaxCompressionRatio)
}
return nil
}
func (preparer *Preparer) checkExpandedSize(size int64, total *int64, compressed int64) error {
if size < 0 || size > preparer.limits.MaxFileBytes {
return fmt.Errorf("expanded file size %d exceeds limit", size)
}
if compressed > 0 && size > 0 && size/compressed > preparer.limits.MaxCompressionRatio {
return fmt.Errorf("compression ratio exceeds limit of %d", preparer.limits.MaxCompressionRatio)
}
*total += size
if *total > preparer.limits.MaxExpandedBytes {
return fmt.Errorf("expanded artifact size exceeds limit of %d bytes", preparer.limits.MaxExpandedBytes)
}
return nil
}
func (preparer *Preparer) preparedPath(release market.Release) (string, error) {
if !safeSegment(release.ConnectorKey) || !safeSegment(release.Version) || !isSHA256(release.ReleaseDigest) {
return "", errors.New("connector release identity is not safe for an artifact path")
}
target := filepath.Join(preparer.rootDir, "prepared", release.ConnectorKey, release.Version, release.ReleaseDigest)
if err := ensureWithin(preparer.rootDir, target); err != nil {
return "", err
}
return target, nil
}
func validatePrepareRequest(request market.PrepareArtifactRequest) error {
if strings.TrimSpace(request.OperationID) == "" || !safeSegment(request.OperationID) {
return errors.New("connector artifact operation id is invalid")
}
return market.ValidateReleaseShape(request.Release)
}
func validateLimits(limits Limits) error {
if limits.MaxDownloadBytes <= 0 || limits.MaxFiles <= 0 || limits.MaxFileBytes <= 0 ||
limits.MaxExpandedBytes <= 0 || limits.MaxCompressionRatio <= 0 {
return errors.New("connector artifact limits must all be positive")
}
return nil
}
func validateArtifactRoot(rootDir string, limits Limits) (string, Limits, error) {
if strings.TrimSpace(rootDir) == "" {
return "", Limits{}, errors.New("connector artifact root directory is required")
}
if !filepath.IsAbs(rootDir) {
return "", Limits{}, errors.New("connector artifact root directory must be absolute")
}
if limits == (Limits{}) {
limits = DefaultLimits()
}
if err := validateLimits(limits); err != nil {
return "", Limits{}, err
}
return filepath.Clean(rootDir), limits, nil
}
func safeArchiveTarget(root, name string) (string, error) {
if _, err := safeArchiveEntryKey(name); err != nil {
return "", err
}
clean := filepath.Clean(filepath.FromSlash(strings.TrimSuffix(name, "/")))
if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("connector artifact entry %q escapes the extraction root", name)
}
target := filepath.Join(root, clean)
if err := ensureWithin(root, target); err != nil {
return "", fmt.Errorf("connector artifact entry %q escapes the extraction root", name)
}
return target, nil
}
func safeArchiveEntryKey(name string) (string, error) {
canonical := strings.TrimSuffix(name, "/")
if canonical == "" || strings.HasPrefix(canonical, "/") || strings.ContainsAny(canonical, "\\:\x00") ||
path.Clean(canonical) != canonical {
return "", fmt.Errorf("connector artifact entry %q has an unsafe portable path", name)
}
for _, segment := range strings.Split(canonical, "/") {
trimmed := strings.TrimRight(segment, ". ")
base := strings.ToLower(strings.SplitN(trimmed, ".", 2)[0])
reserved := base == "con" || base == "prn" || base == "aux" || base == "nul" ||
(len(base) == 4 && (strings.HasPrefix(base, "com") || strings.HasPrefix(base, "lpt")) && base[3] >= '1' && base[3] <= '9')
if segment == "" || trimmed != segment || reserved {
return "", fmt.Errorf("connector artifact entry %q is not portable to Windows", name)
}
}
return strings.ToLower(canonical), nil
}
func ensureWithin(root, target string) error {
relative, err := filepath.Rel(filepath.Clean(root), filepath.Clean(target))
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return errors.New("connector artifact path escapes configured root")
}
return nil
}
func removeAllWithin(root, target string) error {
root = filepath.Clean(root)
target = filepath.Clean(target)
if err := ensureWithin(root, target); err != nil {
return err
}
relative, err := filepath.Rel(root, target)
if err != nil || relative == "." {
return errors.New("connector artifact removal target is invalid")
}
current := root
parts := strings.Split(relative, string(filepath.Separator))
for index, part := range append([]string{""}, parts...) {
if index > 0 {
current = filepath.Join(current, part)
}
info, statErr := os.Lstat(current)
if errors.Is(statErr, os.ErrNotExist) {
return nil
}
if statErr != nil {
return statErr
}
if info.Mode()&os.ModeSymlink != 0 {
return errors.New("connector artifact removal path contains a symbolic link")
}
if index < len(parts) && !info.IsDir() {
return errors.New("connector artifact removal parent is not a directory")
}
}
return os.RemoveAll(target)
}
func writeArchiveFile(target string, body io.Reader, declared int64) error {
if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
return err
}
file, err := os.OpenFile(target, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
return err
}
written, copyErr := io.Copy(file, io.LimitReader(body, declared+1))
syncErr := file.Sync()
closeErr := file.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}
if syncErr != nil {
return syncErr
}
if written != declared {
return fmt.Errorf("connector artifact entry wrote %d bytes, expected %d", written, declared)
}
return nil
}
func verifyPackagedManifest(root, expectedDigest string) error {
path := filepath.Join(root, packagedManifestPath)
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read packaged connector manifest: %w", err)
}
digest := sha256.Sum256(data)
if hex.EncodeToString(digest[:]) != expectedDigest {
return errors.New("packaged connector manifest digest does not match release")
}
return nil
}
func verifyArtifactFile(path string, artifact market.Artifact) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
if !info.Mode().IsRegular() || info.Size() != artifact.SizeBytes {
return errors.New("connector artifact blob size is invalid")
}
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return err
}
if hex.EncodeToString(hash.Sum(nil)) != artifact.SHA256 {
return errors.New("connector artifact blob digest is invalid")
}
return nil
}
func writeReceipt(root string, receipt market.PreparedArtifactReceipt) error {
data, err := json.Marshal(receipt)
if err != nil {
return err
}
path := filepath.Join(root, receiptFilename)
file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("create connector artifact receipt: %w", err)
}
if _, err := file.Write(data); err != nil {
_ = file.Close()
return fmt.Errorf("write connector artifact receipt: %w", err)
}
if err := file.Sync(); err != nil {
_ = file.Close()
return fmt.Errorf("sync connector artifact receipt: %w", err)
}
if err := file.Close(); err != nil {
return fmt.Errorf("write connector artifact receipt: %w", err)
}
return syncDirectory(root)
}
func readExistingReceipt(
target string,
request market.PrepareArtifactRequest,
) (market.PreparedArtifactReceipt, bool) {
data, err := os.ReadFile(filepath.Join(target, receiptFilename))
if err != nil {
return market.PreparedArtifactReceipt{}, false
}
var receipt market.PreparedArtifactReceipt
if json.Unmarshal(data, &receipt) != nil {
return market.PreparedArtifactReceipt{}, false
}
valid := receipt.ConnectorKey == request.Release.ConnectorKey &&
receipt.Version == request.Release.Version &&
receipt.ReleaseDigest == request.Release.ReleaseDigest &&
receipt.ArtifactSHA256 == request.Release.Artifact.SHA256 &&
receipt.PreparedPath == target && receipt.InventoryDigest != ""
if !valid {
return market.PreparedArtifactReceipt{}, false
}
actualInventory, err := inventoryDigest(target)
if err != nil || actualInventory != receipt.InventoryDigest ||
verifyPackagedManifest(target, request.Release.ManifestDigest) != nil {
return market.PreparedArtifactReceipt{}, false
}
// The prepared artifact is content-addressed and may be reused by a retry
// with a new operation id. Return a receipt fenced to the current attempt.
receipt.OperationID = request.OperationID
return receipt, true
}
func inventoryDigest(root string) (string, error) {
hash := sha256.New()
err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
if relative == "." || relative == receiptFilename {
return nil
}
info, err := entry.Info()
if err != nil {
return err
}
if entry.Type()&os.ModeSymlink != 0 || (!entry.IsDir() && !info.Mode().IsRegular()) {
return errors.New("prepared connector inventory contains an unsupported file type")
}
_, _ = io.WriteString(hash, filepath.ToSlash(relative))
_, _ = hash.Write([]byte{0})
if entry.IsDir() {
_, _ = hash.Write([]byte("dir\x00"))
return nil
}
_, _ = io.WriteString(hash, fmt.Sprintf("file\x00%d\x00", info.Size()))
file, err := os.Open(path)
if err != nil {
return err
}
_, copyErr := io.Copy(hash, file)
closeErr := file.Close()
if copyErr != nil {
return copyErr
}
return closeErr
})
if err != nil {
return "", fmt.Errorf("verify prepared connector inventory: %w", err)
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
func sameMediaType(actual, expected string) bool {
actualType, _, actualErr := mime.ParseMediaType(actual)
expectedType, _, expectedErr := mime.ParseMediaType(expected)
return actualErr == nil && expectedErr == nil && strings.EqualFold(actualType, expectedType)
}
func safeSegment(value string) bool {
return value != "" && value != "." && value != ".." &&
!strings.ContainsAny(value, `/\\`) && !strings.ContainsRune(value, '\x00')
}
func isSHA256(value string) bool {
if len(value) != sha256.Size*2 {
return false
}
decoded, err := hex.DecodeString(value)
return err == nil && len(decoded) == sha256.Size && strings.ToLower(value) == value
}
func minPositive(left, right int64) int64 {
if left < right {
return left
}
return right
}