forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
edit.go
105 lines (90 loc) · 2.78 KB
/
edit.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
package action
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"github.com/fatih/color"
"github.com/justwatchcom/gopass/fsutil"
"github.com/justwatchcom/gopass/pwgen"
"github.com/justwatchcom/gopass/tpl"
shellquote "github.com/kballard/go-shellquote"
"github.com/urfave/cli"
)
// Edit the content of a password file
func (s *Action) Edit(c *cli.Context) error {
name := c.Args().First()
if name == "" {
return fmt.Errorf("provide a secret name")
}
var content []byte
var changed bool
if s.Store.Exists(name) {
var err error
content, err = s.Store.Get(name)
if err != nil {
return fmt.Errorf("failed to decrypt %s: %v", name, err)
}
} else if tmpl, found := s.Store.LookupTemplate(name); found {
changed = true
// load template if it exists
content = pwgen.GeneratePassword(defaultLength, false)
if nc, err := tpl.Execute(string(tmpl), name, content, s.Store); err == nil {
content = nc
} else {
fmt.Printf("failed to execute template: %s\n", err)
}
}
nContent, err := s.editor(content)
if err != nil {
return err
}
// If content is equal, nothing changed, exiting
if bytes.Equal(content, nContent) && !changed {
return nil
}
return s.Store.SetConfirm(name, nContent, fmt.Sprintf("Edited with %s", os.Getenv("EDITOR")), s.confirmRecipients)
}
func (s *Action) editor(content []byte) ([]byte, error) {
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "editor"
}
tmpfile, err := fsutil.TempFile("gopass-edit")
if err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile %s: %s", editor, err)
}
defer func() {
if err := tmpfile.Remove(); 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: %s", editor, tmpfile.Name(), err)
}
if err := tmpfile.Close(); err != nil {
return []byte{}, fmt.Errorf("failed to close tmpfile to start with %s %v: %s", editor, tmpfile.Name(), err)
}
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())
cmd := exec.Command(editor, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return []byte{}, fmt.Errorf("failed to run %s with %s file", editor, tmpfile.Name())
}
nContent, err := ioutil.ReadFile(tmpfile.Name())
if err != nil {
return []byte{}, fmt.Errorf("failed to read from tmpfile: %v", 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
}