Honor alternate autofill targets from entry fields

This commit is contained in:
Joe Julian
2026-04-01 10:36:08 -07:00
parent 4eda211666
commit 1b1f11c2d0
4 changed files with 250 additions and 49 deletions
+111 -25
View File
@@ -19,6 +19,7 @@ type Entry struct {
Password string `json:"password"`
URL string `json:"url"`
Host string `json:"host"`
Targets []string `json:"targets,omitempty"`
Path []string `json:"path,omitempty"`
}
@@ -36,11 +37,11 @@ func Match(cache File, webURL string) (Entry, bool) {
exactHost := make([]Entry, 0)
parentHost := make([]Entry, 0)
for _, entry := range cache.Entries {
if entry.Host == target.host {
if entryMatchesHost(entry, target.host) {
exactHost = append(exactHost, entry)
continue
}
if entry.Host != "" && strings.HasSuffix(target.host, "."+entry.Host) {
if entryMatchesParentHost(entry, target.host) {
parentHost = append(parentHost, entry)
}
}
@@ -54,7 +55,16 @@ func Match(cache File, webURL string) (Entry, bool) {
func Build(model vault.Model, now time.Time) File {
entries := make([]Entry, 0, len(model.Entries))
for _, item := range model.Entries {
targets := collectTargets(item)
host := normalizeHost(item.URL)
if host == "" {
for _, target := range targets {
host = normalizeHost(target)
if host != "" {
break
}
}
}
if host == "" {
continue
}
@@ -68,6 +78,7 @@ func Build(model vault.Model, now time.Time) File {
Password: item.Password,
URL: item.URL,
Host: host,
Targets: targets,
Path: append([]string(nil), item.Path...),
})
}
@@ -150,18 +161,23 @@ func chooseEntry(target normalizedTarget, entries []Entry) (Entry, bool) {
}
exact := make([]Entry, 0)
prefix := make([]Entry, 0)
bestPrefixLen := -1
bestPrefix := make([]Entry, 0)
for _, entry := range entries {
entryTarget := normalizeURL(entry.URL)
if entryTarget.host == "" {
continue
}
if entryTarget.url == target.url {
exactMatch, prefixLen := bestTargetMatch(entry, target)
if exactMatch {
exact = append(exact, entry)
continue
}
if entryTarget.path != "/" && strings.HasPrefix(target.path, entryTarget.path) {
prefix = append(prefix, entry)
if prefixLen <= 0 {
continue
}
switch {
case prefixLen > bestPrefixLen:
bestPrefixLen = prefixLen
bestPrefix = []Entry{entry}
case prefixLen == bestPrefixLen:
bestPrefix = append(bestPrefix, entry)
}
}
if len(exact) == 1 {
@@ -170,22 +186,92 @@ func chooseEntry(target normalizedTarget, entries []Entry) (Entry, bool) {
if len(exact) > 1 {
return Entry{}, false
}
if len(prefix) == 0 {
if len(bestPrefix) == 1 {
return bestPrefix[0], true
}
if len(bestPrefix) == 0 {
return Entry{}, false
}
sort.Slice(prefix, func(i, j int) bool {
return len(normalizeURL(prefix[i].URL).path) > len(normalizeURL(prefix[j].URL).path)
})
bestPath := normalizeURL(prefix[0].URL).path
best := make([]Entry, 0, len(prefix))
for _, entry := range prefix {
if normalizeURL(entry.URL).path == bestPath {
best = append(best, entry)
}
}
if len(best) == 1 {
return best[0], true
}
return Entry{}, false
}
func collectTargets(item vault.Entry) []string {
seen := make(map[string]struct{})
targets := make([]string, 0, 1+len(item.Fields))
appendTarget := func(raw string) {
value := strings.TrimSpace(raw)
if value == "" {
return
}
if _, ok := seen[value]; ok {
return
}
seen[value] = struct{}{}
targets = append(targets, value)
}
appendTarget(item.URL)
keys := make([]string, 0, len(item.Fields))
for key := range item.Fields {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
upper := strings.ToUpper(strings.TrimSpace(key))
if strings.HasPrefix(upper, "ANDROIDAPP") || strings.HasPrefix(upper, "KP2A_URL") {
appendTarget(item.Fields[key])
}
}
return targets
}
func entryTargets(entry Entry) []normalizedTarget {
values := entry.Targets
if len(values) == 0 {
values = []string{entry.URL}
}
targets := make([]normalizedTarget, 0, len(values))
for _, value := range values {
target := normalizeURL(value)
if target.host == "" {
continue
}
targets = append(targets, target)
}
return targets
}
func entryMatchesHost(entry Entry, host string) bool {
for _, target := range entryTargets(entry) {
if target.host == host {
return true
}
}
return false
}
func entryMatchesParentHost(entry Entry, host string) bool {
for _, target := range entryTargets(entry) {
if target.host != "" && strings.HasSuffix(host, "."+target.host) {
return true
}
}
return false
}
func bestTargetMatch(entry Entry, target normalizedTarget) (bool, int) {
bestPrefixLen := -1
for _, candidate := range entryTargets(entry) {
if candidate.url == target.url {
return true, 0
}
if candidate.path != "/" && strings.HasPrefix(target.path, candidate.path) {
if pathLen := len(candidate.path); pathLen > bestPrefixLen {
bestPrefixLen = pathLen
}
}
}
return false, bestPrefixLen
}