fix(store): expand a leading ~ in EMCLI_DB
A literal "~/..." in EMCLI_DB has no shell to expand it, so SQLite opened it relative to the cwd and silently created a stray "~" directory tree. Expand a leading "~" or "~/" to the user's home dir; "~user", mid-path tildes, and absolute/relative paths are left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
@@ -77,10 +78,24 @@ func (s *Store) migrate() error {
|
|||||||
|
|
||||||
func (s *Store) Close() error { return s.db.Close() }
|
func (s *Store) Close() error { return s.db.Close() }
|
||||||
|
|
||||||
|
// expandUserHome replaces a leading "~" or "~/" in p with the user's home
|
||||||
|
// directory. Only a leading tilde is expanded (the usual shell convention) —
|
||||||
|
// "~user" and a tilde elsewhere in the path are left untouched. This guards
|
||||||
|
// against an EMCLI_DB set to a literal "~/..." (no shell to expand it), which
|
||||||
|
// would otherwise be opened relative to the cwd and create a stray "~" dir.
|
||||||
|
func expandUserHome(p string) string {
|
||||||
|
if p == "~" || strings.HasPrefix(p, "~/") {
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return filepath.Join(home, strings.TrimPrefix(p[1:], "/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultDBPath resolves EMCLI_DB or the per-OS default location.
|
// DefaultDBPath resolves EMCLI_DB or the per-OS default location.
|
||||||
func DefaultDBPath() (string, error) {
|
func DefaultDBPath() (string, error) {
|
||||||
if p := os.Getenv("EMCLI_DB"); p != "" {
|
if p := os.Getenv("EMCLI_DB"); p != "" {
|
||||||
return p, nil
|
return expandUserHome(p), nil
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if dir := os.Getenv("AppData"); dir != "" {
|
if dir := os.Getenv("AppData"); dir != "" {
|
||||||
|
|||||||
@@ -2,10 +2,53 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A leading "~" in EMCLI_DB must be expanded to the home dir, so a literal
|
||||||
|
// tilde (no shell to expand it) can't be opened relative to the cwd and
|
||||||
|
// silently create a stray "~" directory.
|
||||||
|
func TestDefaultDBPathExpandsLeadingTilde(t *testing.T) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("no home dir: %v", err)
|
||||||
|
}
|
||||||
|
cases := map[string]string{
|
||||||
|
"~/.config/emcli/emcli.db": filepath.Join(home, ".config", "emcli", "emcli.db"),
|
||||||
|
"~": home,
|
||||||
|
}
|
||||||
|
for in, want := range cases {
|
||||||
|
t.Setenv("EMCLI_DB", in)
|
||||||
|
got, err := DefaultDBPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DefaultDBPath(%q): %v", in, err)
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("EMCLI_DB=%q -> %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "~") {
|
||||||
|
t.Fatalf("EMCLI_DB=%q left a literal tilde: %q", in, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A non-leading tilde or "~user" is NOT a path we should rewrite — leave it be.
|
||||||
|
func TestDefaultDBPathLeavesOtherPathsUntouched(t *testing.T) {
|
||||||
|
for _, p := range []string{"/var/lib/emcli.db", "./rel/emcli.db", "~user/db"} {
|
||||||
|
t.Setenv("EMCLI_DB", p)
|
||||||
|
got, err := DefaultDBPath()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DefaultDBPath(%q): %v", p, err)
|
||||||
|
}
|
||||||
|
if got != p {
|
||||||
|
t.Fatalf("EMCLI_DB=%q was rewritten to %q", p, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// openTemp opens a fresh store in a temp dir and initialises keys so that
|
// openTemp opens a fresh store in a temp dir and initialises keys so that
|
||||||
// account tests (which do crypto) work without needing their own setup.
|
// account tests (which do crypto) work without needing their own setup.
|
||||||
func openTemp(t *testing.T) *Store {
|
func openTemp(t *testing.T) *Store {
|
||||||
|
|||||||
Reference in New Issue
Block a user