Fix underlay provenance and resource path handling
This commit is contained in:
@@ -92,14 +92,35 @@ func composeDocuments(inputs ...*document.Node) (*composedDocument, []Diagnostic
|
||||
for _, input := range inputs {
|
||||
applications = append(applications, historyApplication{layer: LayerBase, value: input})
|
||||
}
|
||||
origins := compositionOrigins(collected.ordered)
|
||||
completeCompositionOrigins(root, Path{}, origins)
|
||||
return &composedDocument{
|
||||
root: root,
|
||||
claims: cloneCompositionClaims(collected.ordered),
|
||||
origins: compositionOrigins(collected.ordered),
|
||||
origins: origins,
|
||||
applications: applications,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Conflict claims treat sequences atomically, but inspection needs origins for
|
||||
// every descendant. Preserve composite mapping claims while filling those gaps.
|
||||
func completeCompositionOrigins(node *document.Node, path Path, origins map[string]Origin) {
|
||||
pointer := path.String()
|
||||
if _, present := origins[pointer]; !present {
|
||||
origins[pointer] = Origin{Layer: LayerBase, Locations: []Location{publicLocation(node.Location)}}
|
||||
}
|
||||
switch node.Kind {
|
||||
case document.Mapping:
|
||||
for _, entry := range node.Mapping {
|
||||
completeCompositionOrigins(entry.Value, path.Key(entry.Key), origins)
|
||||
}
|
||||
case document.Sequence:
|
||||
for index, child := range node.Sequence {
|
||||
completeCompositionOrigins(child, path.Index(index), origins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compositionClaims) append(node *document.Node, path Path) {
|
||||
claim := compositionClaim{
|
||||
path: path.clone(),
|
||||
|
||||
@@ -125,9 +125,6 @@ func applyOverlayMetadata(metadata *Metadata, records []overlayRecord) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if history := events[pointer]; len(history) != 0 && history[len(history)-1].Origin.Layer == info.Origin.Layer && history[len(history)-1].Origin.Operation == info.Origin.Operation {
|
||||
continue
|
||||
}
|
||||
appendInputOriginEvent(events, baseline, pointer, path, info.Origin)
|
||||
}
|
||||
metadata.history = make(map[string][]Change, len(baseline)+len(events))
|
||||
@@ -144,12 +141,37 @@ func appendInputOriginEvent(events map[string][]Change, baseline map[string][]Ch
|
||||
return
|
||||
}
|
||||
history := events[pointer]
|
||||
if len(history) != 0 && originContainedBy(history[len(history)-1].Origin, origin) {
|
||||
if len(history) != 0 && originRepresentedByChange(path, origin, history[len(history)-1]) {
|
||||
return
|
||||
}
|
||||
appendInputChangeEvent(events, baseline, pointer, Change{Path: path.clone(), Origin: cloneOrigin(origin)})
|
||||
}
|
||||
|
||||
func originRepresentedByChange(path Path, origin Origin, change Change) bool {
|
||||
if change.Path.String() == path.String() {
|
||||
return originContainedBy(origin, change.Origin)
|
||||
}
|
||||
if !pathContains(change.Path, path) || origin.Layer != change.Origin.Layer || origin.Operation != change.Origin.Operation {
|
||||
return false
|
||||
}
|
||||
// A subtree replacement already records its descendants' contributions.
|
||||
// Their coordinates differ from the container's, but their sources must be
|
||||
// covered by the operation; matching only the precedence role is insufficient.
|
||||
for _, location := range origin.Locations {
|
||||
found := false
|
||||
for _, recorded := range change.Origin.Locations {
|
||||
if location.SourceID == recorded.SourceID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func appendInputChangeEvent(events map[string][]Change, baseline map[string][]Change, pointer string, value Change) {
|
||||
change := cloneChange(value)
|
||||
history := events[pointer]
|
||||
|
||||
@@ -36,7 +36,7 @@ type rootedNativeFS struct {
|
||||
}
|
||||
|
||||
func (r rootedNativeFS) name(name string) (string, error) {
|
||||
if !fs.ValidPath(name) || strings.Contains(name, "\\") {
|
||||
if !fs.ValidPath(name) {
|
||||
return "", &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
joined := path.Join(r.root, name)
|
||||
@@ -75,7 +75,8 @@ type rootedHostFS struct {
|
||||
}
|
||||
|
||||
func (r rootedHostFS) Open(name string) (fs.File, error) {
|
||||
if !fs.ValidPath(name) || strings.Contains(name, "\\") {
|
||||
localName, err := filepath.Localize(name)
|
||||
if err != nil {
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
opener := r.openRoot
|
||||
@@ -86,7 +87,7 @@ func (r rootedHostFS) Open(name string) (fs.File, error) {
|
||||
if err != nil {
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
||||
}
|
||||
canonical, err := canonicalExistingPath(filepath.Join(r.root, filepath.FromSlash(name)))
|
||||
canonical, err := canonicalExistingPath(filepath.Join(r.root, localName))
|
||||
if err != nil {
|
||||
_ = root.Close()
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
||||
|
||||
@@ -5,10 +5,46 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
func TestResourceContextsPreserveLiteralBackslashes(t *testing.T) {
|
||||
const directory = `assets\literal`
|
||||
const name = directory + `/text\file`
|
||||
check := func(t *testing.T, filesystem fs.FS) {
|
||||
t.Helper()
|
||||
data, err := fs.ReadFile(filesystem, name)
|
||||
if err != nil || string(data) != "content" {
|
||||
t.Fatalf("read = %q, %v", data, err)
|
||||
}
|
||||
entries, err := fs.ReadDir(filesystem, directory)
|
||||
if err != nil || len(entries) != 1 || entries[0].Name() != `text\file` {
|
||||
t.Fatalf("entries = %v, %v", entries, err)
|
||||
}
|
||||
if _, err := filesystem.Open("../outside"); !errors.Is(err, fs.ErrInvalid) {
|
||||
t.Fatalf("escape = %v", err)
|
||||
}
|
||||
}
|
||||
t.Run("native", func(t *testing.T) {
|
||||
check(t, rootedNativeFS{filesystem: fstest.MapFS{"bundle/" + name: &fstest.MapFile{Data: []byte("content")}}, root: "bundle"})
|
||||
})
|
||||
t.Run("host", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("backslash is a host separator on Windows; native case remains covered")
|
||||
}
|
||||
root := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(root, directory), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, filepath.FromSlash(name)), []byte("content"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(t, rootedHostFS{root: root})
|
||||
})
|
||||
}
|
||||
|
||||
type trackedHostRoot struct {
|
||||
root *os.Root
|
||||
closed *int
|
||||
|
||||
Reference in New Issue
Block a user