Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (cmd *installCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&cmd.dbOnly, "db_only", false, "only make changes to DB, don't perform install system actions")
f.StringVar(&cmd.sources, "sources", "", "comma separated list of sources, setting this overrides local .repo files")
f.BoolVar(&cmd.dryRun, "dry_run", false, "show what would be installed but do not install")
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files")
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files (only required if StrictConflicts is enabled in config)")
}

func (cmd *installCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...any) subcommands.ExitStatus {
Expand Down
2 changes: 1 addition & 1 deletion cli/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (cmd *updateCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&cmd.dbOnly, "db_only", false, "only make changes to DB, don't perform install system actions")
f.StringVar(&cmd.sources, "sources", "", "comma separated list of sources, setting this overrides local .repo files")
f.BoolVar(&cmd.dryRun, "dry_run", false, "check for updates and print them, but do not prompt to install")
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files")
f.BoolVar(&cmd.force, "force", false, "force overwrite of conflicting files (only required if StrictConflicts is enabled in config)")
}

func (cmd *updateCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
Expand Down
3 changes: 2 additions & 1 deletion googet.goospec
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{$version := "3.3.1@0" -}}
{{$version := "3.3.2@0" -}}
{
"name": "googet",
"version": "{{$version}}",
Expand All @@ -15,6 +15,7 @@
"path": "install.ps1"
},
"releaseNotes": [
"3.3.2 - Feat: introduce StrictConflicts setting to optionally enforce file ownership conflict errors.",
"3.3.1 - Fix: Prevent path traversal vulnerabilities in file unpacking and add workflow permissions.",
"3.3.0 - Refactor: Update FindRepoLatest to prioritize repo priority, version, and architecture with lock support.",
"3.3.0 - Refactor: Add file ownership conflict checks to prevent overwrites.",
Expand Down
9 changes: 7 additions & 2 deletions install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/google/googet/v2/goolib"
"github.com/google/googet/v2/oswrap"
"github.com/google/googet/v2/remove"
"github.com/google/googet/v2/settings"
"github.com/google/googet/v2/system"
"github.com/google/logger"
)
Expand Down Expand Up @@ -410,10 +411,14 @@ func makeInstallFunction(src, dst string, insFiles map[string]string, dbOnly, fo
outPath := filepath.Join(dst, strings.TrimPrefix(path, src))

if owner, ok := conflictMap[outPath]; ok && !fi.IsDir() {
if !force {
if settings.StrictConflicts && !force {
return fmt.Errorf("file conflict: %s is already owned by package %s", outPath, owner)
}
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting due to force flag", outPath, owner)
if force {
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting due to force flag", outPath, owner)
} else {
logger.Infof("Warning: file conflict: %s is already owned by package %s, overwriting because `StrictConflicts` is not set", outPath, owner)
}
fmt.Printf("Warning: file conflict: %s is already owned by package %s, overwriting...\n", outPath, owner)
}

Expand Down
22 changes: 19 additions & 3 deletions install/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,11 @@ func TestMakeInstallFunction(t *testing.T) {
fi, _ := f.Stat()
f.Close()

// Test 1: Conflict without force -> Error
// Test 1: Conflict without force -> Success by default
fnBlock := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, false, cm)
errBlock := fnBlock(filepath.Join(srcDir, "conflicting_file"), fi, nil)
if errBlock == nil {
t.Errorf("expected conflict error, got nil")
if errBlock != nil {
t.Errorf("expected no conflict error by default, got %v", errBlock)
}

// Test 2: Conflict with force -> Success
Expand All @@ -585,4 +585,20 @@ func TestMakeInstallFunction(t *testing.T) {
if errForce != nil {
t.Errorf("expected no error with force, got %v", errForce)
}

// Test 3: Conflict without force in strict mode -> Error
settings.StrictConflicts = true
defer func() { settings.StrictConflicts = false }()
fnStrict := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, false, cm)
errStrict := fnStrict(filepath.Join(srcDir, "conflicting_file"), fi, nil)
if errStrict == nil {
t.Errorf("expected conflict error in strict mode, got nil")
}

// Test 4: Conflict with force in strict mode -> Success
fnStrictForce := makeInstallFunction(srcDir, dstDir, make(map[string]string), false, true, cm)
errStrictForce := fnStrictForce(filepath.Join(srcDir, "conflicting_file"), fi, nil)
if errStrictForce != nil {
t.Errorf("expected no error with force in strict mode, got %v", errStrictForce)
}
}
14 changes: 9 additions & 5 deletions settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ var (
ProxyServer string
// AllowUnsafeURL allows HTTP repos; set from googet.conf.
AllowUnsafeURL bool
// StrictConflicts enables strict enforcement of file ownership conflicts.
StrictConflicts bool
)

// Initialize reads the initial settings.
Expand Down Expand Up @@ -76,11 +78,12 @@ func RepoDir() string {

// conf represents a googet configuration file.
type conf struct {
Archs []string
CacheLife string
LockFileMaxAge string
ProxyServer string
AllowUnsafeURL bool
Archs []string
CacheLife string
LockFileMaxAge string
ProxyServer string
AllowUnsafeURL bool
StrictConflicts bool
}

// unmarshalConfFile returns a conf from a YAML configuration file.
Expand Down Expand Up @@ -138,4 +141,5 @@ func readConf(filename string) {
}

AllowUnsafeURL = gc.AllowUnsafeURL
StrictConflicts = gc.StrictConflicts
}
Loading