Fix paths asset regression
This commit is contained in:
@@ -156,10 +156,15 @@ func extractArchiveResources(p *project.Project, archive erf.Archive, desired ma
|
|||||||
|
|
||||||
func extractedFile(p *project.Project, resource erf.Resource, extension string) (string, []byte, error) {
|
func extractedFile(p *project.Project, resource erf.Resource, extension string) (string, []byte, error) {
|
||||||
resref := strings.ToLower(resource.Name)
|
resref := strings.ToLower(resource.Name)
|
||||||
|
effective := p.EffectiveConfig()
|
||||||
|
|
||||||
switch extension {
|
switch extension {
|
||||||
case "nss":
|
case "nss":
|
||||||
return filepath.Join(p.ScriptSourceDir(), resref+".nss"), resource.Data, nil
|
target, err := extractionTarget(p, "paths.source", effective.Paths.Source, p.SourceDir(), filepath.FromSlash(effective.Scripts.SourceDir), resref+".nss")
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return target, resource.Data, nil
|
||||||
case "utc", "utd", "ute", "uti", "utm", "utp", "uts", "utt", "utw",
|
case "utc", "utd", "ute", "uti", "utm", "utp", "uts", "utt", "utw",
|
||||||
"are", "dlg", "fac", "gic", "git", "ifo", "itp", "jrl":
|
"are", "dlg", "fac", "gic", "git", "ifo", "itp", "jrl":
|
||||||
document, err := gff.Read(bytes.NewReader(resource.Data))
|
document, err := gff.Read(bytes.NewReader(resource.Data))
|
||||||
@@ -171,10 +176,39 @@ func extractedFile(p *project.Project, resource erf.Resource, extension string)
|
|||||||
return "", nil, fmt.Errorf("marshal json %s.%s: %w", resource.Name, extension, err)
|
return "", nil, fmt.Errorf("marshal json %s.%s: %w", resource.Name, extension, err)
|
||||||
}
|
}
|
||||||
formatted = append(formatted, '\n')
|
formatted = append(formatted, '\n')
|
||||||
return filepath.Join(p.SourceDir(), sourceSubdir(extension), resref+"."+extension+".json"), formatted, nil
|
target, err := extractionTarget(p, "paths.source", effective.Paths.Source, p.SourceDir(), sourceSubdir(extension), resref+"."+extension+".json")
|
||||||
default:
|
if err != nil {
|
||||||
return filepath.Join(p.AssetsDir(), extension, resref+"."+extension), resource.Data, nil
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
return target, formatted, nil
|
||||||
|
default:
|
||||||
|
target, err := extractionTarget(p, "paths.assets", effective.Paths.Assets, p.AssetsDir(), extension, resref+"."+extension)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return target, resource.Data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractionTarget(p *project.Project, field, configured, root string, parts ...string) (string, error) {
|
||||||
|
if strings.TrimSpace(configured) == "" {
|
||||||
|
return "", fmt.Errorf("cannot extract resource: %s is not configured", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanRoot := filepath.Clean(root)
|
||||||
|
if cleanRoot == "." || cleanRoot == string(filepath.Separator) || cleanRoot == filepath.Clean(p.Root) {
|
||||||
|
return "", fmt.Errorf("cannot extract resource: %s resolves to unsafe extraction root %s", field, cleanRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
target := filepath.Join(append([]string{cleanRoot}, parts...)...)
|
||||||
|
rel, err := filepath.Rel(cleanRoot, target)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve extraction target %s: %w", target, err)
|
||||||
|
}
|
||||||
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
|
||||||
|
return "", fmt.Errorf("refusing to extract resource outside %s: %s", cleanRoot, target)
|
||||||
|
}
|
||||||
|
return target, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractHAKPaths(p *project.Project) ([]string, error) {
|
func extractHAKPaths(p *project.Project) ([]string, error) {
|
||||||
@@ -236,15 +270,19 @@ func writeManagedFile(path string, data []byte) (writeState, error) {
|
|||||||
|
|
||||||
func cleanupStaleFiles(p *project.Project, desired map[string]struct{}) (int, []error) {
|
func cleanupStaleFiles(p *project.Project, desired map[string]struct{}) (int, []error) {
|
||||||
candidates := make([]string, 0, len(p.Inventory.SourceFiles)+len(p.Inventory.ScriptFiles)+len(p.Inventory.AssetFiles))
|
candidates := make([]string, 0, len(p.Inventory.SourceFiles)+len(p.Inventory.ScriptFiles)+len(p.Inventory.AssetFiles))
|
||||||
|
if safeCleanupRoot(p.SourceDir(), p.Root) {
|
||||||
for _, rel := range p.Inventory.SourceFiles {
|
for _, rel := range p.Inventory.SourceFiles {
|
||||||
candidates = append(candidates, filepath.Join(p.SourceDir(), filepath.FromSlash(rel)))
|
candidates = append(candidates, filepath.Join(p.SourceDir(), filepath.FromSlash(rel)))
|
||||||
}
|
}
|
||||||
for _, rel := range p.Inventory.ScriptFiles {
|
for _, rel := range p.Inventory.ScriptFiles {
|
||||||
candidates = append(candidates, filepath.Join(p.SourceDir(), filepath.FromSlash(rel)))
|
candidates = append(candidates, filepath.Join(p.SourceDir(), filepath.FromSlash(rel)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if safeCleanupRoot(p.AssetsDir(), p.Root) {
|
||||||
for _, rel := range p.Inventory.AssetFiles {
|
for _, rel := range p.Inventory.AssetFiles {
|
||||||
candidates = append(candidates, filepath.Join(p.AssetsDir(), filepath.FromSlash(rel)))
|
candidates = append(candidates, filepath.Join(p.AssetsDir(), filepath.FromSlash(rel)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removed := 0
|
removed := 0
|
||||||
var failures []error
|
var failures []error
|
||||||
@@ -266,6 +304,11 @@ func cleanupStaleFiles(p *project.Project, desired map[string]struct{}) (int, []
|
|||||||
return removed, failures
|
return removed, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func safeCleanupRoot(root, projectRoot string) bool {
|
||||||
|
cleanRoot := filepath.Clean(root)
|
||||||
|
return cleanRoot != "." && cleanRoot != string(filepath.Separator) && cleanRoot != filepath.Clean(projectRoot)
|
||||||
|
}
|
||||||
|
|
||||||
func cleanupEmptyParents(dir string, roots ...string) {
|
func cleanupEmptyParents(dir string, roots ...string) {
|
||||||
for {
|
for {
|
||||||
if dir == "." || dir == string(filepath.Separator) {
|
if dir == "." || dir == string(filepath.Separator) {
|
||||||
|
|||||||
@@ -175,6 +175,173 @@ func TestExtractReadsHAKAssets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractRefusesAssetsWhenAssetsPathIsUnset(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
t.Chdir(root)
|
||||||
|
mustMkdir(t, filepath.Join(root, "src"))
|
||||||
|
mustMkdir(t, filepath.Join(root, "build"))
|
||||||
|
mustWriteFile(t, filepath.Join(root, "root_level_asset.mdl"), "must stay")
|
||||||
|
|
||||||
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||||
|
module:
|
||||||
|
name: Test Module
|
||||||
|
resref: testmod
|
||||||
|
paths:
|
||||||
|
source: src
|
||||||
|
build: build
|
||||||
|
`)
|
||||||
|
|
||||||
|
p, err := project.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load project: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.Scan(); err != nil {
|
||||||
|
t.Fatalf("scan: %v", err)
|
||||||
|
}
|
||||||
|
if len(p.Inventory.AssetFiles) != 0 {
|
||||||
|
t.Fatalf("expected unset assets path not to inventory repository-root assets, got %#v", p.Inventory.AssetFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
modFile, err := os.Create(p.ModuleArchivePath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := erf.Write(modFile, erf.New("MOD ", nil)); err != nil {
|
||||||
|
t.Fatalf("write mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := modFile.Close(); err != nil {
|
||||||
|
t.Fatalf("close mod: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hakFile, err := os.Create(filepath.Join(root, "build", "vfx.hak"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create hak: %v", err)
|
||||||
|
}
|
||||||
|
if err := erf.Write(hakFile, erf.New("HAK ", []erf.Resource{
|
||||||
|
{Name: "test_vfx", Type: 0x07D2, Data: []byte("mdl-data")},
|
||||||
|
})); err != nil {
|
||||||
|
t.Fatalf("write hak: %v", err)
|
||||||
|
}
|
||||||
|
if err := hakFile.Close(); err != nil {
|
||||||
|
t.Fatalf("close hak: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Extract(p)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected extract to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "paths.assets is not configured") {
|
||||||
|
t.Fatalf("expected missing assets path error, got %v", err)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(filepath.Join(root, "mdl", "test_vfx.mdl")); !os.IsNotExist(statErr) {
|
||||||
|
t.Fatalf("expected no root-level extracted asset, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(filepath.Join(root, "root_level_asset.mdl")); statErr != nil {
|
||||||
|
t.Fatalf("expected pre-existing root-level asset to remain, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractRefusesAssetsWhenAssetsPathIsRepositoryRoot(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
mustMkdir(t, filepath.Join(root, "src"))
|
||||||
|
mustMkdir(t, filepath.Join(root, "build"))
|
||||||
|
|
||||||
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||||
|
module:
|
||||||
|
name: Test Module
|
||||||
|
resref: testmod
|
||||||
|
paths:
|
||||||
|
source: src
|
||||||
|
assets: .
|
||||||
|
build: build
|
||||||
|
`)
|
||||||
|
|
||||||
|
p, err := project.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modFile, err := os.Create(p.ModuleArchivePath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := erf.Write(modFile, erf.New("MOD ", nil)); err != nil {
|
||||||
|
t.Fatalf("write mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := modFile.Close(); err != nil {
|
||||||
|
t.Fatalf("close mod: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hakFile, err := os.Create(filepath.Join(root, "build", "vfx.hak"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create hak: %v", err)
|
||||||
|
}
|
||||||
|
if err := erf.Write(hakFile, erf.New("HAK ", []erf.Resource{
|
||||||
|
{Name: "test_vfx", Type: 0x07D2, Data: []byte("mdl-data")},
|
||||||
|
})); err != nil {
|
||||||
|
t.Fatalf("write hak: %v", err)
|
||||||
|
}
|
||||||
|
if err := hakFile.Close(); err != nil {
|
||||||
|
t.Fatalf("close hak: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Extract(p)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected extract to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "paths.assets resolves to unsafe extraction root") {
|
||||||
|
t.Fatalf("expected unsafe assets root error, got %v", err)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(filepath.Join(root, "mdl", "test_vfx.mdl")); !os.IsNotExist(statErr) {
|
||||||
|
t.Fatalf("expected no root-level extracted asset, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractRefusesSourceWhenSourcePathIsUnset(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
t.Chdir(root)
|
||||||
|
mustMkdir(t, filepath.Join(root, "assets"))
|
||||||
|
mustMkdir(t, filepath.Join(root, "build"))
|
||||||
|
|
||||||
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||||
|
module:
|
||||||
|
name: Test Module
|
||||||
|
resref: testmod
|
||||||
|
paths:
|
||||||
|
assets: assets
|
||||||
|
build: build
|
||||||
|
`)
|
||||||
|
|
||||||
|
p, err := project.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modFile, err := os.Create(p.ModuleArchivePath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := erf.Write(modFile, erf.New("MOD ", []erf.Resource{
|
||||||
|
{Name: "test_script", Type: 0x07D9, Data: []byte("void main() {}\n")},
|
||||||
|
})); err != nil {
|
||||||
|
t.Fatalf("write mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := modFile.Close(); err != nil {
|
||||||
|
t.Fatalf("close mod: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = Extract(p)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected extract to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "paths.source is not configured") {
|
||||||
|
t.Fatalf("expected missing source path error, got %v", err)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(filepath.Join(root, "scripts", "test_script.nss")); !os.IsNotExist(statErr) {
|
||||||
|
t.Fatalf("expected no root-level extracted script, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtractConfiguredHAKDiscoveryIgnoresUnconfiguredArchives(t *testing.T) {
|
func TestExtractConfiguredHAKDiscoveryIgnoresUnconfiguredArchives(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
mustMkdir(t, filepath.Join(root, "src"))
|
mustMkdir(t, filepath.Join(root, "src"))
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ func (p *Project) Scan() error {
|
|||||||
effective := p.EffectiveConfig()
|
effective := p.EffectiveConfig()
|
||||||
|
|
||||||
sourceDir := p.SourceDir()
|
sourceDir := p.SourceDir()
|
||||||
if sourceDir != "" && filepath.Clean(sourceDir) != p.Root {
|
if sourceDir != "" && filepath.Clean(sourceDir) != filepath.Clean(p.Root) {
|
||||||
var err error
|
var err error
|
||||||
sourceFiles, sourceExts, err = scanDir(sourceDir, func(path string) bool {
|
sourceFiles, sourceExts, err = scanDir(sourceDir, func(path string) bool {
|
||||||
ext := strings.ToLower(filepath.Ext(path))
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
@@ -594,7 +594,11 @@ func (p *Project) Scan() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assetFiles, _, err := scanDir(p.AssetsDir(), func(path string) bool {
|
assetDir := p.AssetsDir()
|
||||||
|
var assetFiles []string
|
||||||
|
if assetDir != "" && filepath.Clean(assetDir) != filepath.Clean(p.Root) {
|
||||||
|
var err error
|
||||||
|
assetFiles, _, err = scanDir(assetDir, func(path string) bool {
|
||||||
ext := strings.ToLower(filepath.Ext(path))
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
return slices.Contains(effective.Inventory.AssetExtensions, ext)
|
return slices.Contains(effective.Inventory.AssetExtensions, ext)
|
||||||
})
|
})
|
||||||
@@ -605,6 +609,7 @@ func (p *Project) Scan() error {
|
|||||||
return fmt.Errorf("scan assets tree: %w", err)
|
return fmt.Errorf("scan assets tree: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var scripts []string
|
var scripts []string
|
||||||
var sources []string
|
var sources []string
|
||||||
|
|||||||
Reference in New Issue
Block a user