-
-
Notifications
You must be signed in to change notification settings - Fork 481
/
editor.go
120 lines (105 loc) · 3.2 KB
/
editor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package editor
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/fsutil"
"github.com/gopasspw/gopass/pkg/tempfile"
"github.com/fatih/color"
shellquote "github.com/kballard/go-shellquote"
)
var (
// Stdin is exported for tests
Stdin io.Reader = os.Stdin
// Stdout is exported for tests
Stdout io.Writer = os.Stdout
// Stderr is exported for tests
Stderr io.Writer = os.Stderr
vimOptsRe = regexp.MustCompile(`au\s+BufNewFile,BufRead\s+.*gopass.*setlocal\s+noswapfile\s+nobackup\s+noundofile`)
)
// Check will validate the editor config
func Check(ctx context.Context, editor string) error {
if !strings.Contains(editor, "vi") {
return nil
}
uhd, err := os.UserHomeDir()
if err != nil {
return err
}
vrc := filepath.Join(uhd, ".vimrc")
if runtime.GOOS == "windows" {
vrc = filepath.Join(uhd, "_vimrc")
}
if !fsutil.IsFile(vrc) {
return nil
}
buf, err := os.ReadFile(vrc)
if err != nil {
return err
}
if vimOptsRe.Match(buf) {
debug.Log("Recommended settings found in %s", vrc)
return nil
}
debug.Log("%s did not match %s", string(buf), vimOptsRe)
out.Warningf(ctx, "Vim might leak credentials. Check your setup.\nhttps://github.com/gopasspw/gopass/blob/master/docs/setup.md#securing-your-editor")
return nil
}
// Invoke will start the given editor and return the content
func Invoke(ctx context.Context, editor string, content []byte) ([]byte, error) {
if !ctxutil.IsTerminal(ctx) {
return nil, fmt.Errorf("need terminal")
}
tmpfile, err := tempfile.New(ctx, "gopass-edit")
if err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile %s: %w", editor, err)
}
defer func() {
if err := tmpfile.Remove(ctx); err != nil {
color.Red("Failed to remove tempfile at %s: %s", tmpfile.Name(), err)
}
}()
if _, err := tmpfile.Write(content); err != nil {
return []byte{}, fmt.Errorf("failed to write tmpfile to start with %s %v: %w", editor, tmpfile.Name(), err)
}
if err := tmpfile.Close(); err != nil {
return []byte{}, fmt.Errorf("failed to close tmpfile to start with %s %v: %w", editor, tmpfile.Name(), err)
}
var args []string
if runtime.GOOS != "windows" {
cmdArgs, err := shellquote.Split(editor)
if err != nil {
return []byte{}, fmt.Errorf("failed to parse EDITOR command `%s`", editor)
}
editor = cmdArgs[0]
args = append(cmdArgs[1:], tmpfile.Name())
} else {
args = []string{tmpfile.Name()}
}
cmd := exec.Command(editor, args...)
cmd.Stdin = Stdin
cmd.Stdout = Stdout
cmd.Stderr = Stderr
if err := cmd.Run(); err != nil {
debug.Log("cmd: %s %+v - error: %+v", cmd.Path, cmd.Args, err)
return []byte{}, fmt.Errorf("failed to run %s with %s file: %w", editor, tmpfile.Name(), err)
}
nContent, err := os.ReadFile(tmpfile.Name())
if err != nil {
return []byte{}, fmt.Errorf("failed to read from tmpfile: %w", err)
}
// enforce unix line endings in the password store
nContent = bytes.Replace(nContent, []byte("\r\n"), []byte("\n"), -1)
nContent = bytes.Replace(nContent, []byte("\r"), []byte("\n"), -1)
return nContent, nil
}