lockbox

password manager
Log | Files | Refs | README | LICENSE

commit a9ab6b7ac44a0531d297f72fb66388983e83ae8e
parent 886cdc69b720a4273dcb9f27d67a8a193d6a726f
Author: Sean Enck <sean@ttypty.com>
Date:   Thu, 30 Mar 2023 18:55:50 -0400

use json instead of text parsing

Diffstat:
Mcmd/vers.txt | 4++--
Minternal/app/rekey.go | 21+++++++++------------
Minternal/app/rekey_test.go | 28+++++++++++++++-------------
Minternal/backend/query.go | 14++++++++++----
Minternal/backend/query_test.go | 12++++++++++--
Minternal/backend/types.go | 7+++++--
Mtests/expected.log | 5++++-
Mtests/run.sh | 1+
8 files changed, 56 insertions(+), 36 deletions(-)

diff --git a/cmd/vers.txt b/cmd/vers.txt @@ -1 +1 @@ -6bf2506-1 -\ No newline at end of file +76d0142 +\ No newline at end of file diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -1,6 +1,7 @@ package app import ( + "encoding/json" "errors" "fmt" "io" @@ -17,7 +18,7 @@ type ( // Keyer defines how rekeying happens Keyer interface { List() ([]string, error) - Stats(string) ([]string, error) + Stats(string) ([]byte, error) Show(string) ([]byte, error) Insert(ReKeyEntry) error } @@ -48,8 +49,8 @@ func (r DefaultKeyer) List() ([]string, error) { } // Stats will get stats for an entry -func (r DefaultKeyer) Stats(entry string) ([]string, error) { - return r.getCommandLines(cli.StatsCommand, entry) +func (r DefaultKeyer) Stats(entry string) ([]byte, error) { + return exec.Command(r.exe, cli.StatsCommand, entry).Output() } func (r DefaultKeyer) getCommandLines(args ...string) ([]string, error) { @@ -98,18 +99,14 @@ func ReKey(writer io.Writer, r Keyer) error { } stats, err := r.Stats(entry) if err != nil { - return fmt.Errorf("failed to get modtime, command failed: %w", err) + return fmt.Errorf("failed to get modtime: %w", err) } modTime := "" - for _, stat := range stats { - if strings.HasPrefix(stat, backend.ModTimeField) { - if modTime != "" { - return errors.New("unable to read modtime, too many values") - } - modTime = strings.TrimPrefix(stat, backend.ModTimeField) - } + j := backend.Stats{} + if err := json.Unmarshal(stats, &j); err != nil { + return fmt.Errorf("invalid stats json: %w", err) } - modTime = strings.TrimSpace(modTime) + modTime = strings.TrimSpace(j.ModTime) if modTime == "" { return errors.New("did not read modtime") } diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "strings" "testing" "github.com/enckse/lockbox/internal/app" @@ -14,7 +15,7 @@ type ( mockKeyer struct { entries []string data map[string][]byte - stats map[string][]string + stats map[string][]byte err error rekeys []app.ReKeyEntry } @@ -35,7 +36,7 @@ func (m *mockKeyer) Show(entry string) ([]byte, error) { return val, nil } -func (m *mockKeyer) Stats(entry string) ([]string, error) { +func (m *mockKeyer) Stats(entry string) ([]byte, error) { val, ok := m.stats[entry] if !ok { return nil, errors.New("no stats") @@ -66,20 +67,21 @@ func TestErrors(t *testing.T) { } m.err = nil m.entries = []string{"test1", "error"} - if err := app.ReKey(&buf, m); err == nil || err.Error() != "failed to get modtime, command failed: no stats" { + if err := app.ReKey(&buf, m); err == nil || err.Error() != "failed to get modtime: no stats" { t.Errorf("invalid error: %v", err) } - m.stats = make(map[string][]string) - m.stats["test1"] = []string{"modtime"} - m.stats["error"] = []string{"modtime: 3"} - if err := app.ReKey(&buf, m); err == nil || err.Error() != "did not read modtime" { + m.stats = make(map[string][]byte) + m.stats["test1"] = []byte("modtime") + if err := app.ReKey(&buf, m); err == nil || !strings.HasPrefix(err.Error(), "invalid stats json:") { t.Errorf("invalid error: %v", err) } - m.stats["test1"] = []string{"modtime: 1", "modtime: 2"} - if err := app.ReKey(&buf, m); err == nil || err.Error() != "unable to read modtime, too many values" { + m.stats = make(map[string][]byte) + m.stats["test1"] = []byte("{\"modtime\": \"\"}") + if err := app.ReKey(&buf, m); err == nil || err.Error() != "did not read modtime" { t.Errorf("invalid error: %v", err) } - m.stats["test1"] = []string{"modtime: 1"} + m.stats["test1"] = []byte("{\"modtime\": \"1\"}") + m.stats["error"] = []byte("{\"modtime\": \"1\"}") if err := app.ReKey(&buf, m); err == nil || err.Error() != "no data" { t.Errorf("invalid error: %v", err) } @@ -105,9 +107,9 @@ func TestReKey(t *testing.T) { m.data = make(map[string][]byte) m.data["test1"] = []byte{1} m.data["test2"] = []byte{2} - m.stats = make(map[string][]string) - m.stats["test1"] = []string{"modtime: 1", "modtime2"} - m.stats["test2"] = []string{"moime: 1", "modtime: 2"} + m.stats = make(map[string][]byte) + m.stats["test1"] = []byte("{\"modtime\": \"1\"}") + m.stats["test2"] = []byte("{\"modtime\": \"2\"}") if err := app.ReKey(&buf, m); err != nil { t.Errorf("invalid error: %v", err) } diff --git a/internal/backend/query.go b/internal/backend/query.go @@ -3,6 +3,7 @@ package backend import ( "crypto/sha512" + "encoding/json" "errors" "fmt" "sort" @@ -131,12 +132,17 @@ func (t *Transaction) QueryCallback(args QueryOptions) ([]QueryEntity, error) { switch args.Values { case SecretValue: entity.Value = val - case HashedValue, StatsValue: + case StatsValue: t := getValue(e.backing, modTimeKey) - res := fmt.Sprintf("%s %s", ModTimeField, t) - if args.Values == HashedValue { - res = fmt.Sprintf("%s\nhash: %x", res, sha512.Sum512([]byte(val))) + s := Stats{Path: k, ModTime: t} + m, err := json.MarshalIndent(s, "", " ") + if err != nil { + return nil, err } + entity.Value = string(m) + case HashedValue: + t := getValue(e.backing, modTimeKey) + res := fmt.Sprintf("modtime: %s\nhash: %x", t, sha512.Sum512([]byte(val))) entity.Value = res } } diff --git a/internal/backend/query_test.go b/internal/backend/query_test.go @@ -1,6 +1,7 @@ package backend_test import ( + "encoding/json" "fmt" "os" "strings" @@ -106,8 +107,15 @@ func TestValueModes(t *testing.T) { t.Errorf("invalid result value: %s", q.Value) } q, err = fullSetup(t, true).Get("test/test/abc", backend.StatsValue) - if err != nil || !strings.HasPrefix(q.Value, "modtime: ") || len(strings.Split(q.Value, "\n")) != 1 { - t.Errorf("invalid stats: %s", q.Value) + if err != nil { + t.Errorf("no error: %v", err) + } + r := backend.Stats{} + if err := json.Unmarshal([]byte(q.Value), &r); err != nil { + t.Errorf("json error: %v", err) + } + if len(r.ModTime) != 25 || r.Path != "test/test/abc" { + t.Errorf("invalid stats: %v", r) } } diff --git a/internal/backend/types.go b/internal/backend/types.go @@ -48,6 +48,11 @@ type ( enabled bool scripts []string } + // Stats shows information about the entry + Stats struct { + ModTime string `json:"modtime"` + Path string `json:"path"` + } removal struct { parts []string title string @@ -100,8 +105,6 @@ const ( pathSep = "/" isGlob = pathSep + "*" modTimeKey = "ModTime" - // ModTimeField is the stats field for modification time - ModTimeField = "modtime:" ) var errPath = errors.New("input paths must contain at LEAST 2 components") diff --git a/tests/expected.log b/tests/expected.log @@ -15,7 +15,10 @@ keys2/k/three test2 test3 test4 -modtime: XXXX-XX-XX +{ + "modtime": "XXXX-XX-XX", + "path": "keys2/k/three" +} test/k XXXXXX XXXXXX diff --git a/tests/run.sh b/tests/run.sh @@ -122,6 +122,7 @@ _clipboard() { _logtest() { _execute 2>&1 | \ sed 's/modtime: [0-9].*$/modtime: XXXX-XX-XX/g' | \ + sed 's/\"modtime\": \"[0-9].*$/"modtime": "XXXX-XX-XX",/g' | \ sed 's/^[0-9][0-9][0-9][0-9][0-9][0-9]$/XXXXXX/g' }