1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
package keepass
import (
"fmt"
"strings"
gokeepasslib "github.com/tobischo/gokeepasslib/v3"
)
// isAttachmentPath reports whether description refers to a virtual attachment
// entry. It returns true when the parent path (description minus the last
// component) matches an existing text entry and the description itself does
// not match any existing entry.
//
// This implements the "Add to .../filename creates attachment on parent entry"
// contract: if Group/Title exists and Group/Title/file.bin does not, the last
// component is treated as an attachment filename.
func (b *Backend) isAttachmentPath(description string) (parentDesc, attachName string, ok bool) {
// An attachment path must have at least two components (parent + filename).
lastSlash := strings.LastIndex(description, "/")
if lastSlash < 0 {
return "", "", false
}
parentDesc = description[:lastSlash]
attachName = description[lastSlash+1:]
if attachName == "" || parentDesc == "" {
return "", "", false
}
// The path itself must not already be a standalone text entry —
// if it is, treat it as a normal entry update.
if b.isTextEntry(description) {
return "", "", false
}
// The parent must exist as a text (non-attachment) entry.
if !b.isTextEntry(parentDesc) {
return "", "", false
}
return parentDesc, attachName, true
}
// isTextEntry reports whether description refers to an existing non-attachment
// virtual entry in the database.
func (b *Backend) isTextEntry(description string) bool {
for _, ve := range walkEntries(b.root()) {
if ve.description == description && !ve.isBinary {
return true
}
}
return false
}
// addAttachment creates or replaces a binary attachment named attachName on the
// entry identified by parentDesc. The parent entry must already exist; use
// Add for the parent before adding an attachment to it.
func (b *Backend) addAttachment(parentDesc, attachName string, content []byte) error {
groupPath, title, err := SplitDescriptionPath(parentDesc)
if err != nil {
return fmt.Errorf("keepass add attachment: %w", err)
}
g := EnsureGroup(b.root(), groupPath)
entry, found := findEntryByTitle(g, title)
if !found {
return fmt.Errorf("keepass add attachment: parent entry %q not found", parentDesc)
}
upsertAttachment(b.db, entry, attachName, content)
return b.save()
}
// findEntryByTitle locates an entry by title inside g without creating a new
// one. Returns the entry pointer and true when found.
func findEntryByTitle(g *gokeepasslib.Group, title string) (*gokeepasslib.Entry, bool) {
for i := range g.Entries {
if g.Entries[i].GetTitle() == title {
return &g.Entries[i], true
}
}
return nil, false
}
// upsertAttachment replaces an existing attachment named name on entry, or
// appends a new one if no attachment with that name exists.
func upsertAttachment(db *gokeepasslib.Database, entry *gokeepasslib.Entry, name string, content []byte) {
// Remove the existing attachment reference for this name, if any.
// The old binary in the db-level pool is orphaned (gokeepasslib handles
// pool cleanup on encode), so we only need to remove the reference.
newRefs := entry.Binaries[:0]
for _, ref := range entry.Binaries {
if ref.Name != name {
newRefs = append(newRefs, ref)
}
}
entry.Binaries = newRefs
// Add the new binary to the pool and reference it from the entry.
bin := db.AddBinary(content)
entry.Binaries = append(entry.Binaries, bin.CreateReference(name))
}
// removeAttachment removes a binary attachment named attachName from the entry
// identified by parentDesc. Returns an error if the parent entry or attachment
// is not found.
func (b *Backend) removeAttachment(parentDesc, attachName string) error {
groupPath, title, err := SplitDescriptionPath(parentDesc)
if err != nil {
return fmt.Errorf("keepass remove attachment: %w", err)
}
g := EnsureGroup(b.root(), groupPath)
entry, found := findEntryByTitle(g, title)
if !found {
return fmt.Errorf("keepass remove attachment: parent entry %q not found", parentDesc)
}
before := len(entry.Binaries)
newRefs := entry.Binaries[:0]
for _, ref := range entry.Binaries {
if ref.Name != attachName {
newRefs = append(newRefs, ref)
}
}
entry.Binaries = newRefs
if len(entry.Binaries) == before {
return fmt.Errorf("keepass remove attachment: attachment %q not found on %q", attachName, parentDesc)
}
return b.save()
}
|