|
|
|
|
@@ -5,45 +5,12 @@ import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
|
|
"github.com/multiformats/go-multihash"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ValidatePath checks that a file path conforms to manifest path invariants:
|
|
|
|
|
// - Must be valid UTF-8
|
|
|
|
|
// - Must use forward slashes only (no backslashes)
|
|
|
|
|
// - Must be relative (no leading /)
|
|
|
|
|
// - Must not contain ".." segments
|
|
|
|
|
// - Must not contain empty segments (no "//")
|
|
|
|
|
// - Must not be empty
|
|
|
|
|
func ValidatePath(p string) error {
|
|
|
|
|
if p == "" {
|
|
|
|
|
return errors.New("path cannot be empty")
|
|
|
|
|
}
|
|
|
|
|
if !utf8.ValidString(p) {
|
|
|
|
|
return fmt.Errorf("path %q is not valid UTF-8", p)
|
|
|
|
|
}
|
|
|
|
|
if strings.ContainsRune(p, '\\') {
|
|
|
|
|
return fmt.Errorf("path %q contains backslash; use forward slashes only", p)
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(p, "/") {
|
|
|
|
|
return fmt.Errorf("path %q is absolute; must be relative", p)
|
|
|
|
|
}
|
|
|
|
|
for _, seg := range strings.Split(p, "/") {
|
|
|
|
|
if seg == "" {
|
|
|
|
|
return fmt.Errorf("path %q contains empty segment", p)
|
|
|
|
|
}
|
|
|
|
|
if seg == ".." {
|
|
|
|
|
return fmt.Errorf("path %q contains '..' segment", p)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RelFilePath represents a relative file path within a manifest.
|
|
|
|
|
type RelFilePath string
|
|
|
|
|
|
|
|
|
|
@@ -108,10 +75,6 @@ func (b *Builder) AddFile(
|
|
|
|
|
reader io.Reader,
|
|
|
|
|
progress chan<- FileHashProgress,
|
|
|
|
|
) (FileSize, error) {
|
|
|
|
|
if err := ValidatePath(string(path)); err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create hash writer
|
|
|
|
|
h := sha256.New()
|
|
|
|
|
|
|
|
|
|
@@ -184,8 +147,8 @@ func (b *Builder) FileCount() int {
|
|
|
|
|
// This is useful when the hash is already known (e.g., from an existing manifest).
|
|
|
|
|
// Returns an error if path is empty, size is negative, or hash is nil/empty.
|
|
|
|
|
func (b *Builder) AddFileWithHash(path RelFilePath, size FileSize, mtime ModTime, hash Multihash) error {
|
|
|
|
|
if err := ValidatePath(string(path)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
if path == "" {
|
|
|
|
|
return errors.New("path cannot be empty")
|
|
|
|
|
}
|
|
|
|
|
if size < 0 {
|
|
|
|
|
return errors.New("size cannot be negative")
|
|
|
|
|
|