diff --git a/main.go b/main.go index 97576fd..6040394 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "image" "image/color" "io" + "net/url" "os" "os/exec" "path/filepath" @@ -2319,6 +2320,34 @@ func normalizeRemoteCredentialURL(raw string) string { return raw } +func remoteCredentialURLMatches(candidate, target string) bool { + candidate = normalizeRemoteCredentialURL(candidate) + target = normalizeRemoteCredentialURL(target) + if candidate == "" || target == "" { + return false + } + if candidate == target { + return true + } + candidateURL, err := url.Parse(candidate) + if err != nil { + return false + } + targetURL, err := url.Parse(target) + if err != nil { + return false + } + if !strings.EqualFold(candidateURL.Hostname(), targetURL.Hostname()) { + return false + } + candidatePath := strings.TrimRight(candidateURL.EscapedPath(), "/") + targetPath := strings.TrimRight(targetURL.EscapedPath(), "/") + if candidatePath == "" || candidatePath == "/" || targetPath == "" || targetPath == "/" { + return true + } + return strings.HasPrefix(targetPath, candidatePath) || strings.HasPrefix(candidatePath, targetPath) +} + func (u *ui) matchingAdvancedSyncRemoteCredentialEntries() []vault.Entry { if sanitizeSyncSourceMode(u.syncSourceMode) != syncSourceRemote { return nil @@ -2346,7 +2375,7 @@ func (u *ui) matchingAdvancedSyncRemoteCredentialEntries() []vault.Entry { matches = append(matches, entry) } for _, entry := range entries { - if normalizeRemoteCredentialURL(entry.URL) != baseURL { + if !remoteCredentialURLMatches(entry.URL, baseURL) { continue } appendMatch(entry) @@ -2364,7 +2393,7 @@ func (u *ui) matchingAdvancedSyncRemoteCredentialEntries() []vault.Entry { if !ok { continue } - if normalizeRemoteCredentialURL(profile.BaseURL) != baseURL { + if !remoteCredentialURLMatches(profile.BaseURL, baseURL) { continue } if remotePath != "" && strings.TrimSpace(profile.Path) != remotePath && strings.TrimSpace(record.Path) != remotePath { diff --git a/main_test.go b/main_test.go index 117a648..b81e564 100644 --- a/main_test.go +++ b/main_test.go @@ -6732,6 +6732,55 @@ func TestUIAdvancedSyncMatchingRemoteCredentialEntriesUsesBaseURL(t *testing.T) } } +func TestUIAdvancedSyncMatchingRemoteCredentialEntriesUsesMatchingHost(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{ + Entries: []vault.Entry{ + {ID: "entry-1", Title: "Mint WebDAV", Username: "charliecroker", URL: "https://dav.example.invalid", Path: []string{"Crew", "Signals"}}, + {ID: "entry-2", Title: "Bellagio WebDAV Sign-In", Username: "linuscaldwell", URL: "https://dav.example.invalid/remote.php/dav", Path: []string{"Crew", "Signals"}}, + {ID: "entry-3", Title: "Bank Console", Username: "stevefrezelli", URL: "https://insidejob.example.invalid", Path: []string{"Crew", "Signals"}}, + }, + }) + u.syncSourceMode = syncSourceRemote + u.syncRemoteBaseURL.SetText("https://dav.example.invalid/remote.php/dav") + + got := u.matchingAdvancedSyncRemoteCredentialEntries() + if len(got) != 2 { + t.Fatalf("len(matchingAdvancedSyncRemoteCredentialEntries()) = %d, want 2", len(got)) + } + gotIDs := []string{got[0].ID, got[1].ID} + slices.Sort(gotIDs) + if !slices.Equal(gotIDs, []string{"entry-1", "entry-2"}) { + t.Fatalf("matchingAdvancedSyncRemoteCredentialEntries() ids = %v, want [entry-1 entry-2]", gotIDs) + } +} + +func TestUIRemoteSyncSetupMatchingCredentialsUsesMatchingHost(t *testing.T) { + t.Parallel() + + u := newUIWithModel("desktop", vault.Model{ + Entries: []vault.Entry{ + {ID: "entry-1", Title: "Mint WebDAV", Username: "charliecroker", URL: "https://dav.example.invalid", Path: []string{"Crew", "Signals"}}, + {ID: "entry-2", Title: "Bellagio WebDAV Sign-In", Username: "linuscaldwell", URL: "https://dav.example.invalid/remote.php/dav", Path: []string{"Crew", "Signals"}}, + }, + }) + u.syncDialogPurpose = syncDialogPurposeRemoteSetup + u.syncSourceMode = syncSourceRemote + u.syncDirection = syncDirectionPush + u.syncRemoteBaseURL.SetText("https://dav.example.invalid/remote.php/dav") + + got := u.matchingAdvancedSyncRemoteCredentialEntries() + if len(got) != 2 { + t.Fatalf("len(matchingAdvancedSyncRemoteCredentialEntries()) = %d, want 2 in remote setup flow", len(got)) + } + gotIDs := []string{got[0].ID, got[1].ID} + slices.Sort(gotIDs) + if !slices.Equal(gotIDs, []string{"entry-1", "entry-2"}) { + t.Fatalf("matchingAdvancedSyncRemoteCredentialEntries() ids = %v, want [entry-1 entry-2] in remote setup flow", gotIDs) + } +} + func TestUIAdvancedSyncMatchingRemoteCredentialEntriesUsesSavedBindingForCurrentVault(t *testing.T) { t.Parallel()