lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 8f09028fc5a53273584a2b7589f63ed33005a6a2
parent 8aae004bca6eb90abda3bfad32c5b7159da59d0b
Author: Sean Enck <sean@ttypty.com>
Date:   Tue, 10 Jun 2025 10:27:19 -0400

globbing and listing filters are better for ls/conv

Diffstat:
Mcmd/lb/main_test.go | 4++--
Mcmd/lb/tests/expected.log | 2++
Minternal/app/json_test.go | 16++++++++++++++++
Minternal/app/list.go | 10+++++++++-
Minternal/app/list_test.go | 20++++++++++++++++++--
Minternal/kdbx/core.go | 14+++++++++-----
Minternal/kdbx/core_test.go | 11+++++++++++
7 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go @@ -222,13 +222,13 @@ func test(profile string) error { r.run("", "ls */multiline/*") r.run("", "ls url") r.run("", "json") - r.run("", "json '*/multiline'") + r.run("", "json 'multiline'") r.section("totp") r.logAppend("echo") r.run("echo 5ae472abqdekjqykoyxk7hvc2leklq5n |", "insert test6/multiline/otp") r.run(`printf "otpauth://totp/lbissuer:lbaccount?algorithm=SHA1&digits=6&issuer=lbissuer&period=30&secret=5ae472abqdekjqykoyxk7hvc2leklq5n" |`, "insert test10/key1/otp") r.run("", "totp ls") - r.run("", "totp ls '**/**/rooted/*") + r.run("", "totp ls deeper/rooted") const grepTOTP = "| sed 's/^[[:space:]]*//g' | grep '^[0-9][0-9][0-9][0-9][0-9][0-9]$'" r.run("", fmt.Sprintf("totp show test6/multiline/otp %s", grepTOTP)) r.run("", fmt.Sprintf("totp show test10/key1/otp %s", grepTOTP)) diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log @@ -61,6 +61,7 @@ test4/multiline/notes test5/multiline/notes test6/multiline/notes test6/multiline/password +test7/deeper/root/url { "test1/key1": { "modtime": "XXXX-XX-XX", @@ -126,6 +127,7 @@ totp test10/key1/otp test6/multiline/otp test7/deeper/rooted/otp +test7/deeper/rooted/otp XXXXXX XXXXXX XXXXXX diff --git a/internal/app/json_test.go b/internal/app/json_test.go @@ -24,6 +24,22 @@ func TestJSON(t *testing.T) { t.Error("no data") } m.buf = bytes.Buffer{} + m.args = []string{"test"} + 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{"test/test2"} + 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{"test/test2/*"} 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 @@ -88,5 +88,13 @@ func createFilter(filter string) (bool, func(string, string) (bool, error)) { if filter == "" { return false, nil } - return true, kdbx.Glob + parts := kdbx.SplitPath(filter) + for _, p := range parts { + if strings.ContainsAny(p, "*?[^]\\-") { + return true, kdbx.Glob + } + } + return true, func(criteria, path string) (bool, error) { + return strings.Contains(path, criteria), nil + } } diff --git a/internal/app/list_test.go b/internal/app/list_test.go @@ -56,7 +56,7 @@ func TestList(t *testing.T) { func TestFind(t *testing.T) { m := newMockCommand(t) - m.args = []string{"["} + m.args = []string{"a/["} if err := app.List(m, false); err == nil || !strings.Contains(err.Error(), "syntax error in pattern") { t.Errorf("invalid error: %v", err) } @@ -72,7 +72,23 @@ func TestFind(t *testing.T) { t.Error("something listed") } m.buf.Reset() - m.args = []string{"test/test2/**/*"} + m.args = []string{"test"} + if err := app.List(m, false); err != nil { + t.Errorf("invalid error: %v", err) + } + 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{"test/**/**/*"} if err := app.List(m, false); err != nil { t.Errorf("invalid error: %v", err) } diff --git a/internal/kdbx/core.go b/internal/kdbx/core.go @@ -23,7 +23,6 @@ var ( const ( titleKey = "Title" pathSep = "/" - isGlob = "*" modTimeKey = "ModTime" // OTPField is the totp storage attribute OTPField = "otp" @@ -83,7 +82,7 @@ func NewTransaction() (*Transaction, error) { } func splitComponents(path string) ([]string, string, error) { - if len(strings.Split(path, pathSep)) < 2 { + if len(SplitPath(path)) < 2 { return nil, "", errPath } if strings.HasPrefix(path, pathSep) { @@ -96,7 +95,7 @@ func splitComponents(path string) ([]string, string, error) { return nil, "", errors.New("unwilling to operate on path with empty segment") } title := Base(path) - parts := strings.Split(Directory(path), pathSep) + parts := SplitPath(Directory(path)) return parts, title, nil } @@ -182,7 +181,7 @@ func (e Entity) Value(key string) (string, bool) { // Base will get the base name of input path func Base(s string) string { - parts := strings.Split(s, pathSep) + parts := SplitPath(s) if len(parts) == 0 { return s } @@ -191,7 +190,7 @@ func Base(s string) string { // Directory will get the directory/group for the given path func Directory(s string) string { - parts := strings.Split(s, pathSep) + parts := SplitPath(s) return NewPath(parts[0 : len(parts)-1]...) } @@ -213,6 +212,11 @@ func IsLeafAttribute(path, attr string) bool { return strings.HasSuffix(path, pathSep+attr) } +// SplitPath will split a path based on the separator +func SplitPath(path string) []string { + return strings.Split(path, pathSep) +} + // Collect will create a slice from an iterable set of query sequence results func (s QuerySeq2) Collect() ([]Entity, error) { var entities []Entity diff --git a/internal/kdbx/core_test.go b/internal/kdbx/core_test.go @@ -167,3 +167,14 @@ func TestEntityValue(t *testing.T) { t.Error("values are not set") } } + +func TestSplitPath(t *testing.T) { + parts := kdbx.SplitPath("a") + if len(parts) != 1 || parts[0] != "a" { + t.Errorf("invalid split: %v", parts) + } + parts = kdbx.SplitPath("a/b") + if len(parts) != 2 || parts[0] != "a" || parts[1] != "b" { + t.Errorf("invalid split: %v", parts) + } +}