commit e5d68c07d1c4f71e5a4f3039b4963c0d538d3606
parent 47c3363f5699ab3573a8efa2e94f91c239b14e68
Author: Sean Enck <sean@ttypty.com>
Date: Thu, 27 Jul 2023 18:59:00 -0400
restructure platform
Diffstat:
13 files changed, 274 insertions(+), 317 deletions(-)
diff --git a/internal/inputs/env.go b/internal/inputs/env.go
@@ -2,8 +2,10 @@
package inputs
import (
+ "errors"
"fmt"
"os"
+ "os/exec"
"strconv"
"strings"
@@ -21,6 +23,7 @@ const (
LinuxXPlatform = "linux-x"
// WindowsLinuxPlatform for WSL subsystems
WindowsLinuxPlatform = "wsl"
+ unknownPlatform = ""
)
type (
@@ -195,3 +198,38 @@ func (e EnvironmentFormatter) values() (string, []string) {
func (e EnvironmentCommand) values() (string, []string) {
return detectedValue, []string{commandArgsExample}
}
+
+// NewPlatform gets a new system platform.
+func NewPlatform() (SystemPlatform, error) {
+ env := EnvPlatform.Get()
+ if env != "" {
+ for _, p := range EnvPlatform.allowed {
+ if p == env {
+ return SystemPlatform(p), nil
+ }
+ }
+ return unknownPlatform, errors.New("unknown platform mode")
+ }
+ b, err := exec.Command("uname", "-a").Output()
+ if err != nil {
+ return unknownPlatform, err
+ }
+ raw := strings.ToLower(strings.TrimSpace(string(b)))
+ parts := strings.Split(raw, " ")
+ switch parts[0] {
+ case "darwin":
+ return MacOSPlatform, nil
+ case "linux":
+ if strings.Contains(raw, "microsoft-standard-wsl") {
+ return WindowsLinuxPlatform, nil
+ }
+ if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
+ if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
+ return unknownPlatform, errors.New("unable to detect linux clipboard mode")
+ }
+ return LinuxXPlatform, nil
+ }
+ return LinuxWaylandPlatform, nil
+ }
+ return unknownPlatform, errors.New("unable to detect clipboard mode")
+}
diff --git a/internal/inputs/env_test.go b/internal/inputs/env_test.go
@@ -28,3 +28,24 @@ func TestKeyValue(t *testing.T) {
t.Errorf("invalid keyvalue")
}
}
+
+func TestNewPlatform(t *testing.T) {
+ for _, item := range inputs.PlatformSet() {
+ os.Setenv("LOCKBOX_PLATFORM", item)
+ s, err := inputs.NewPlatform()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if s != inputs.SystemPlatform(item) {
+ t.Error("mismatch on input and resulting detection")
+ }
+ }
+}
+
+func TestNewPlatformUnknown(t *testing.T) {
+ os.Setenv("LOCKBOX_PLATFORM", "afleaj")
+ _, err := inputs.NewPlatform()
+ if err == nil || err.Error() != "unknown platform mode" {
+ t.Errorf("error expected for platform: %v", err)
+ }
+}
diff --git a/internal/inputs/vars.go b/internal/inputs/vars.go
@@ -53,7 +53,7 @@ var (
// EnvTOTPToken is the leaf token to use to store TOTP tokens
EnvTOTPToken = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "TOTP", desc: "attribute name to store TOTP tokens within the database"}, allowed: []string{"string"}, canDefault: true, defaultValue: "totp"}
// EnvPlatform is the platform that the application is running on
- EnvPlatform = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "PLATFORM", desc: "override the detected platform"}, defaultValue: detectedValue, allowed: PlatformSet(), canDefault: false}
+ EnvPlatform = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "PLATFORM", desc: "override the detected platform"}, defaultValue: detectedValue, allowed: []string{MacOSPlatform, WindowsLinuxPlatform, LinuxXPlatform, LinuxWaylandPlatform}, canDefault: false}
// EnvStore is the location of the keepass file/store
EnvStore = EnvironmentString{environmentBase: environmentBase{key: prefixKey + "STORE", desc: "directory to the database file", requirement: "must be set"}, canDefault: false, allowed: []string{"file"}}
// EnvHookDir is the directory of hooks to execute
diff --git a/internal/platform/clipboard.go b/internal/platform/clipboard.go
@@ -57,7 +57,7 @@ func NewClipboard() (Clipboard, error) {
c := Clipboard{isOSC52: true}
return c, nil
}
- sys, err := NewPlatform()
+ sys, err := inputs.NewPlatform()
if err != nil {
return Clipboard{}, err
}
diff --git a/internal/platform/colors.go b/internal/platform/colors.go
@@ -1,54 +0,0 @@
-// Package platform manages definitions within lockbox for colorization.
-package platform
-
-import (
- "errors"
-
- "github.com/enckse/lockbox/internal/inputs"
-)
-
-const (
- // Red will get red terminal coloring.
- Red = iota
-)
-
-type (
- // Color are terminal colors for dumb terminal coloring.
- Color int
-)
-
-const (
- termBeginRed = "\033[1;31m"
- termEndRed = "\033[0m"
-)
-
-type (
- // Terminal represents terminal coloring information.
- Terminal struct {
- Start string
- End string
- }
-)
-
-// NewTerminal will retrieve start/end terminal coloration indicators.
-func NewTerminal(color Color) (Terminal, error) {
- if color != Red {
- return Terminal{}, errors.New("bad color")
- }
- interactive, err := inputs.EnvInteractive.Get()
- if err != nil {
- return Terminal{}, err
- }
- colors := interactive
- if colors {
- isColored, err := inputs.EnvNoColor.Get()
- if err != nil {
- return Terminal{}, err
- }
- colors = !isColored
- }
- if colors {
- return Terminal{Start: termBeginRed, End: termEndRed}, nil
- }
- return Terminal{}, nil
-}
diff --git a/internal/platform/core.go b/internal/platform/core.go
@@ -1,50 +0,0 @@
-// Package platform handles platform-specific operations.
-package platform
-
-import (
- "errors"
- "os"
- "os/exec"
- "strings"
-
- "github.com/enckse/lockbox/internal/inputs"
-)
-
-const (
- unknownPlatform = ""
-)
-
-// NewPlatform gets a new system platform.
-func NewPlatform() (inputs.SystemPlatform, error) {
- env := inputs.EnvPlatform.Get()
- if env != "" {
- for _, p := range inputs.PlatformSet() {
- if p == env {
- return inputs.SystemPlatform(p), nil
- }
- }
- return unknownPlatform, errors.New("unknown platform mode")
- }
- b, err := exec.Command("uname", "-a").Output()
- if err != nil {
- return unknownPlatform, err
- }
- raw := strings.ToLower(strings.TrimSpace(string(b)))
- parts := strings.Split(raw, " ")
- switch parts[0] {
- case "darwin":
- return inputs.MacOSPlatform, nil
- case "linux":
- if strings.Contains(raw, "microsoft-standard-wsl") {
- return inputs.WindowsLinuxPlatform, nil
- }
- if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
- if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
- return unknownPlatform, errors.New("unable to detect linux clipboard mode")
- }
- return inputs.LinuxXPlatform, nil
- }
- return inputs.LinuxWaylandPlatform, nil
- }
- return unknownPlatform, errors.New("unable to detect clipboard mode")
-}
diff --git a/internal/platform/core_test.go b/internal/platform/core_test.go
@@ -1,30 +0,0 @@
-package platform_test
-
-import (
- "os"
- "testing"
-
- "github.com/enckse/lockbox/internal/inputs"
- "github.com/enckse/lockbox/internal/platform"
-)
-
-func TestNewPlatform(t *testing.T) {
- for _, item := range inputs.PlatformSet() {
- os.Setenv("LOCKBOX_PLATFORM", item)
- s, err := platform.NewPlatform()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if s != inputs.SystemPlatform(item) {
- t.Error("mismatch on input and resulting detection")
- }
- }
-}
-
-func TestNewPlatformUnknown(t *testing.T) {
- os.Setenv("LOCKBOX_PLATFORM", "afleaj")
- _, err := platform.NewPlatform()
- if err == nil || err.Error() != "unknown platform mode" {
- t.Errorf("error expected for platform: %v", err)
- }
-}
diff --git a/internal/platform/os.go b/internal/platform/os.go
@@ -0,0 +1,159 @@
+// Package platform handles stdin processing
+package platform
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "syscall"
+)
+
+type (
+ stdinReaderFunc func(string) (bool, error)
+)
+
+func termEcho(on bool) {
+ // Common settings and variables for both stty calls.
+ attrs := syscall.ProcAttr{
+ Dir: "",
+ Env: []string{},
+ Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
+ Sys: nil,
+ }
+ var ws syscall.WaitStatus
+ cmd := "echo"
+ if !on {
+ cmd = "-echo"
+ }
+
+ // Enable/disable echoing.
+ pid, err := syscall.ForkExec(
+ "/bin/stty",
+ []string{"stty", cmd},
+ &attrs)
+ if err != nil {
+ panic(err)
+ }
+
+ // Wait for the stty process to complete.
+ _, err = syscall.Wait4(pid, &ws, 0, nil)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// GetUserInputPassword will read the user's input from stdin via multiple means.
+func GetUserInputPassword(piping, multiLine bool) ([]byte, error) {
+ var password string
+ if !multiLine && !piping {
+ input, err := confirmInputsMatch()
+ if err != nil {
+ return nil, err
+ }
+ password = input
+ } else {
+ input, err := Stdin(false)
+ if err != nil {
+ return nil, err
+ }
+ password = input
+ }
+ if password == "" {
+ return nil, errors.New("password can NOT be empty")
+ }
+ return []byte(password), nil
+}
+
+func confirmInputsMatch() (string, error) {
+ termEcho(false)
+ defer func() {
+ termEcho(true)
+ }()
+ fmt.Print("please enter password: ")
+ first, err := Stdin(true)
+ if err != nil {
+ return "", err
+ }
+ fmt.Print("\nplease re-enter password: ")
+ second, err := Stdin(true)
+ if err != nil {
+ return "", err
+ }
+ if first != second {
+ return "", errors.New("passwords do NOT match")
+ }
+ return first, nil
+}
+
+// Stdin will get one (or more) lines of stdin as a string.
+func Stdin(one bool) (string, error) {
+ b, err := read(one)
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(b)), nil
+}
+
+// IsInputFromPipe will indicate if connected to stdin pipe.
+func IsInputFromPipe() bool {
+ fileInfo, _ := os.Stdin.Stat()
+ return fileInfo.Mode()&os.ModeCharDevice == 0
+}
+
+// ConfirmYesNoPrompt will ask a yes/no question.
+func ConfirmYesNoPrompt(prompt string) (bool, error) {
+ fmt.Printf("%s? (y/N) ", prompt)
+ resp, err := Stdin(true)
+ if err != nil {
+ return false, err
+ }
+ return resp == "Y" || resp == "y", nil
+}
+
+func readFunc(reader stdinReaderFunc) error {
+ if reader == nil {
+ return errors.New("invalid reader, nil")
+ }
+ scanner := bufio.NewScanner(os.Stdin)
+ for scanner.Scan() {
+ ok, err := reader(scanner.Text())
+ if err != nil {
+ return err
+ }
+ if !ok {
+ break
+ }
+ }
+ return scanner.Err()
+}
+
+func read(one bool) ([]byte, error) {
+ var b bytes.Buffer
+ err := readFunc(func(line string) (bool, error) {
+ if _, err := b.WriteString(line); err != nil {
+ return false, err
+ }
+ if _, err := b.WriteString("\n"); err != nil {
+ return false, err
+ }
+ if one {
+ return false, nil
+ }
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return b.Bytes(), nil
+}
+
+// PathExists indicates whether a path exists (true) or not (false)
+func PathExists(file string) bool {
+ if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
+ return false
+ }
+ return true
+}
diff --git a/internal/platform/paths_test.go b/internal/platform/os_test.go
diff --git a/internal/platform/paths.go b/internal/platform/paths.go
@@ -1,15 +0,0 @@
-// Package platform is responsible for pathing operations/commands
-package platform
-
-import (
- "errors"
- "os"
-)
-
-// PathExists indicates whether a path exists (true) or not (false)
-func PathExists(file string) bool {
- if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
- return false
- }
- return true
-}
diff --git a/internal/platform/stdin.go b/internal/platform/stdin.go
@@ -1,166 +0,0 @@
-// Package platform handles stdin processing
-package platform
-
-import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "os"
- "strings"
- "syscall"
-)
-
-type (
- stdinReaderFunc func(string) (bool, error)
-)
-
-func termEcho(on bool) {
- // Common settings and variables for both stty calls.
- attrs := syscall.ProcAttr{
- Dir: "",
- Env: []string{},
- Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
- Sys: nil,
- }
- var ws syscall.WaitStatus
- cmd := "echo"
- if !on {
- cmd = "-echo"
- }
-
- // Enable/disable echoing.
- pid, err := syscall.ForkExec(
- "/bin/stty",
- []string{"stty", cmd},
- &attrs)
- if err != nil {
- panic(err)
- }
-
- // Wait for the stty process to complete.
- _, err = syscall.Wait4(pid, &ws, 0, nil)
- if err != nil {
- panic(err)
- }
-}
-
-// GetUserInputPassword will read the user's input from stdin via multiple means.
-func GetUserInputPassword(piping, multiLine bool) ([]byte, error) {
- var password string
- if !multiLine && !piping {
- input, err := confirmInputsMatch()
- if err != nil {
- return nil, err
- }
- password = input
- } else {
- input, err := Stdin(false)
- if err != nil {
- return nil, err
- }
- password = input
- }
- if password == "" {
- return nil, errors.New("password can NOT be empty")
- }
- return []byte(password), nil
-}
-
-func confirmInputsMatch() (string, error) {
- termEcho(false)
- defer func() {
- termEcho(true)
- }()
- fmt.Print("please enter password: ")
- first, err := Stdin(true)
- if err != nil {
- return "", err
- }
- fmt.Print("\nplease re-enter password: ")
- second, err := Stdin(true)
- if err != nil {
- return "", err
- }
- if first != second {
- return "", errors.New("passwords do NOT match")
- }
- return first, nil
-}
-
-// Stdin will get one (or more) lines of stdin as string.
-func Stdin(one bool) (string, error) {
- var b []byte
- var err error
- if one {
- b, err = readLine()
- } else {
- b, err = readAll()
- }
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(b)), nil
-}
-
-// IsInputFromPipe will indicate if connected to stdin pipe.
-func IsInputFromPipe() bool {
- fileInfo, _ := os.Stdin.Stat()
- return fileInfo.Mode()&os.ModeCharDevice == 0
-}
-
-// ConfirmYesNoPrompt will ask a yes/no question.
-func ConfirmYesNoPrompt(prompt string) (bool, error) {
- fmt.Printf("%s? (y/N) ", prompt)
- resp, err := Stdin(true)
- if err != nil {
- return false, err
- }
- return resp == "Y" || resp == "y", nil
-}
-
-func readAll() ([]byte, error) {
- return read(false)
-}
-
-func readLine() ([]byte, error) {
- return read(true)
-}
-
-// ReadFunc will read stdin and execute the given function
-func ReadFunc(reader stdinReaderFunc) error {
- if reader == nil {
- return errors.New("invalid reader, nil")
- }
- scanner := bufio.NewScanner(os.Stdin)
- for scanner.Scan() {
- ok, err := reader(scanner.Text())
- if err != nil {
- return err
- }
- if !ok {
- break
- }
- }
- return scanner.Err()
-}
-
-func read(one bool) ([]byte, error) {
- var b bytes.Buffer
- err := ReadFunc(func(line string) (bool, error) {
- if _, err := b.WriteString(line); err != nil {
- return false, err
- }
- if _, err := b.WriteString("\n"); err != nil {
- return false, err
- }
- if one {
- return false, nil
- }
- return true, nil
- })
- if err != nil {
- return nil, err
- }
- return b.Bytes(), nil
-}
diff --git a/internal/platform/terminal.go b/internal/platform/terminal.go
@@ -0,0 +1,54 @@
+// Package platform handles platform-specific operations.
+package platform
+
+import (
+ "errors"
+
+ "github.com/enckse/lockbox/internal/inputs"
+)
+
+const (
+ termBeginRed = "\033[1;31m"
+ termEndRed = "\033[0m"
+)
+
+const (
+ // Red will get red terminal coloring.
+ Red = iota
+)
+
+type (
+ // Color are terminal colors for dumb terminal coloring.
+ Color int
+)
+
+type (
+ // Terminal represents terminal coloring information.
+ Terminal struct {
+ Start string
+ End string
+ }
+)
+
+// NewTerminal will retrieve start/end terminal coloration indicators.
+func NewTerminal(color Color) (Terminal, error) {
+ if color != Red {
+ return Terminal{}, errors.New("bad color")
+ }
+ interactive, err := inputs.EnvInteractive.Get()
+ if err != nil {
+ return Terminal{}, err
+ }
+ colors := interactive
+ if colors {
+ isColored, err := inputs.EnvNoColor.Get()
+ if err != nil {
+ return Terminal{}, err
+ }
+ colors = !isColored
+ }
+ if colors {
+ return Terminal{Start: termBeginRed, End: termEndRed}, nil
+ }
+ return Terminal{}, nil
+}
diff --git a/internal/platform/colors_test.go b/internal/platform/terminal_test.go