lockbox

password manager
Log | Files | Refs | README | LICENSE

commit c2cb8ecc5a75752a5c0662492fa3335dc48f189e
parent 46934afcb75a339f3a8681128ad2b74e6dd9923b
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  7 Jun 2025 20:40:10 -0400

filtering should happen at the list/group or conv level

Diffstat:
Mcmd/lb/main_test.go | 1+
Mcmd/lb/tests/expected.log | 1+
Minternal/app/conv.go | 17++++++++++++++++-
Minternal/app/help/core_test.go | 2+-
Minternal/app/json_test.go | 8++++++++
Minternal/app/list.go | 26+++++++++++++++++++++++---
Minternal/app/list_test.go | 16++++++++++++++++
Minternal/kdbx/query.go | 22+++-------------------
Minternal/kdbx/query_test.go | 18------------------
9 files changed, 69 insertions(+), 42 deletions(-)

diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go @@ -217,6 +217,7 @@ func test(profile string) error { r.logAppend("echo") r.run("", "ls") r.run("", "ls multiline") + r.run("", "ls url") r.run("", "json") r.run("", "json 'multiline'") r.logAppend("echo") diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log @@ -58,6 +58,7 @@ test4/multiline/notes test5/multiline/notes test6/multiline/notes test6/multiline/password +test7/deeper/root/url { "test1/key1": { "modtime": "XXXX-XX-XX", diff --git a/internal/app/conv.go b/internal/app/conv.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "regexp" "strings" "git.sr.ht/~enckse/lockbox/internal/kdbx" @@ -31,7 +32,16 @@ func Conv(cmd CommandOptions) error { } func serialize(w io.Writer, tx *kdbx.Transaction, isJSON bool, filter string) error { - e, err := tx.QueryCallback(kdbx.QueryOptions{Mode: kdbx.ListMode, Values: kdbx.JSONValue, PathFilter: filter}) + var re *regexp.Regexp + hasFilter := filter != "" + if hasFilter { + var err error + re, err = regexp.Compile(filter) + if err != nil { + return err + } + } + e, err := tx.QueryCallback(kdbx.QueryOptions{Mode: kdbx.ListMode, Values: kdbx.JSONValue}) if err != nil { return err } @@ -43,6 +53,11 @@ func serialize(w io.Writer, tx *kdbx.Transaction, isJSON bool, filter string) er if err != nil { return err } + if hasFilter { + if !re.MatchString(item.Path) { + continue + } + } if printed { if isJSON { fmt.Fprint(w, ",") diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go @@ -13,7 +13,7 @@ func TestUsage(t *testing.T) { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = help.Usage(true, "lb") - if len(u) != 108 { + if len(u) != 112 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { diff --git a/internal/app/json_test.go b/internal/app/json_test.go @@ -16,6 +16,14 @@ func TestJSON(t *testing.T) { if err := app.JSON(m); err.Error() != "invalid arguments" { t.Errorf("invalid error: %v", err) } + m.args = []string{} + if err := app.JSON(m); err != nil { + t.Errorf("invalid error: %v", err) + } + if m.buf.String() == "" || m.buf.String() == "{}\n" { + t.Error("no data") + } + m.buf = bytes.Buffer{} m.args = []string{"test2/test1"} if err := app.JSON(m); err != nil { t.Errorf("invalid error: %v", err) diff --git a/internal/app/list.go b/internal/app/list.go @@ -4,6 +4,7 @@ package app import ( "errors" "fmt" + "regexp" "sort" "strings" @@ -27,13 +28,27 @@ func List(cmd CommandOptions, groups bool) error { } func doList(attr, filter string, cmd CommandOptions, groups bool) error { + var re *regexp.Regexp + hasFilter := filter != "" + if hasFilter { + var err error + re, err = regexp.Compile(filter) + if err != nil { + return err + } + } opts := kdbx.QueryOptions{} opts.Mode = kdbx.ListMode - opts.PathFilter = filter e, err := cmd.Transaction().QueryCallback(opts) if err != nil { return err } + allowed := func(p string) bool { + if hasFilter { + return re.MatchString(p) + } + return true + } w := cmd.Writer() attrFilter := attr != "" for f, err := range e { @@ -41,7 +56,9 @@ func doList(attr, filter string, cmd CommandOptions, groups bool) error { return err } if groups { - fmt.Fprintf(w, "%s\n", f.Path) + if allowed(f.Path) { + fmt.Fprintf(w, "%s\n", f.Path) + } continue } if f.Values == nil { @@ -54,7 +71,10 @@ func doList(attr, filter string, cmd CommandOptions, groups bool) error { continue } } - results = append(results, kdbx.NewPath(f.Path, k)) + path := kdbx.NewPath(f.Path, k) + if allowed(path) { + results = append(results, path) + } } if len(results) == 0 { continue diff --git a/internal/app/list_test.go b/internal/app/list_test.go @@ -80,6 +80,22 @@ func TestFind(t *testing.T) { if m.buf.String() == "" { t.Error("nothing listed") } + m.buf.Reset() + m.args = []string{"test"} + if err := app.List(m, true); err != nil { + t.Errorf("invalid error: %v", err) + } + if m.buf.String() == "" { + t.Error("nothing listed") + } + m.buf.Reset() + m.args = []string{"[zzzz]"} + if err := app.List(m, true); err != nil { + t.Errorf("invalid error: %v", err) + } + if m.buf.String() != "" { + t.Error("something listed") + } } func TestGroups(t *testing.T) { diff --git a/internal/kdbx/query.go b/internal/kdbx/query.go @@ -5,7 +5,6 @@ import ( "crypto/sha512" "errors" "fmt" - "regexp" "slices" "strings" @@ -17,10 +16,9 @@ import ( type ( // QueryOptions indicates how to find entities QueryOptions struct { - PathFilter string - Criteria string - Mode QueryMode - Values ValueMode + Criteria string + Mode QueryMode + Values ValueMode } // QueryMode indicates HOW an entity will be found QueryMode int @@ -123,15 +121,6 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) { var entities []entity isSort := args.Mode != ExactMode decrypt := args.Values != BlankValue - hasPathFilter := args.PathFilter != "" - var pathFilter *regexp.Regexp - if hasPathFilter { - var err error - pathFilter, err = regexp.Compile(args.PathFilter) - if err != nil { - return nil, err - } - } err := t.act(func(ctx Context) error { forEach("", ctx.db.Content.Root.Groups[0].Groups, ctx.db.Content.Root.Groups[0].Entries, func(offset string, entry gokeepasslib.Entry) { path := getPathName(entry) @@ -156,11 +145,6 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) { } } } - if hasPathFilter { - if !pathFilter.MatchString(path) { - return - } - } obj := entity{backing: entry, path: path} if isSort && len(entities) > 0 { i, _ := slices.BinarySearchFunc(entities, obj, func(i, j entity) int { diff --git a/internal/kdbx/query_test.go b/internal/kdbx/query_test.go @@ -246,24 +246,6 @@ func TestQueryCallback(t *testing.T) { if res[0].Path != "test/test/abc" { t.Errorf("invalid results: %v", res) } - seq, err = fullSetup(t, true).QueryCallback(kdbx.QueryOptions{Mode: kdbx.ExactMode, Criteria: "test/test/abc", PathFilter: "abc"}) - if err != nil { - t.Errorf("no error: %v", err) - } - res = testCollect(t, 1, seq) - if res[0].Path != "test/test/abc" { - t.Errorf("invalid results: %v", res) - } - seq, err = fullSetup(t, true).QueryCallback(kdbx.QueryOptions{Mode: kdbx.ExactMode, Criteria: "test/test/abc", PathFilter: "abz"}) - if err != nil { - t.Errorf("no error: %v", err) - } - testCollect(t, 0, seq) - seq, err = fullSetup(t, true).QueryCallback(kdbx.QueryOptions{Mode: kdbx.ExactMode, Criteria: "abczzz"}) - if err != nil { - t.Errorf("no error: %v", err) - } - testCollect(t, 0, seq) } func TestSetModTime(t *testing.T) {