111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
|
package gopass
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
)
|
||
|
|
||
|
type FdReader interface {
|
||
|
io.Reader
|
||
|
Fd() uintptr
|
||
|
}
|
||
|
|
||
|
var defaultGetCh = func(r io.Reader) (byte, error) {
|
||
|
buf := make([]byte, 1)
|
||
|
if n, err := r.Read(buf); n == 0 || err != nil {
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
return buf[0], nil
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
maxLength = 512
|
||
|
ErrInterrupted = errors.New("interrupted")
|
||
|
ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength)
|
||
|
|
||
|
// Provide variable so that tests can provide a mock implementation.
|
||
|
getch = defaultGetCh
|
||
|
)
|
||
|
|
||
|
// getPasswd returns the input read from terminal.
|
||
|
// If prompt is not empty, it will be output as a prompt to the user
|
||
|
// If masked is true, typing will be matched by asterisks on the screen.
|
||
|
// Otherwise, typing will echo nothing.
|
||
|
func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) {
|
||
|
var err error
|
||
|
var pass, bs, mask []byte
|
||
|
if masked {
|
||
|
bs = []byte("\b \b")
|
||
|
mask = []byte("*")
|
||
|
}
|
||
|
|
||
|
if isTerminal(r.Fd()) {
|
||
|
if oldState, err := makeRaw(r.Fd()); err != nil {
|
||
|
return pass, err
|
||
|
} else {
|
||
|
defer func() {
|
||
|
restore(r.Fd(), oldState)
|
||
|
fmt.Fprintln(w)
|
||
|
}()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if prompt != "" {
|
||
|
fmt.Fprint(w, prompt)
|
||
|
}
|
||
|
|
||
|
// Track total bytes read, not just bytes in the password. This ensures any
|
||
|
// errors that might flood the console with nil or -1 bytes infinitely are
|
||
|
// capped.
|
||
|
var counter int
|
||
|
for counter = 0; counter <= maxLength; counter++ {
|
||
|
if v, e := getch(r); e != nil {
|
||
|
err = e
|
||
|
break
|
||
|
} else if v == 127 || v == 8 {
|
||
|
if l := len(pass); l > 0 {
|
||
|
pass = pass[:l-1]
|
||
|
fmt.Fprint(w, string(bs))
|
||
|
}
|
||
|
} else if v == 13 || v == 10 {
|
||
|
break
|
||
|
} else if v == 3 {
|
||
|
err = ErrInterrupted
|
||
|
break
|
||
|
} else if v != 0 {
|
||
|
pass = append(pass, v)
|
||
|
fmt.Fprint(w, string(mask))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if counter > maxLength {
|
||
|
err = ErrMaxLengthExceeded
|
||
|
}
|
||
|
|
||
|
return pass, err
|
||
|
}
|
||
|
|
||
|
// GetPasswd returns the password read from the terminal without echoing input.
|
||
|
// The returned byte array does not include end-of-line characters.
|
||
|
func GetPasswd() ([]byte, error) {
|
||
|
return getPasswd("", false, os.Stdin, os.Stdout)
|
||
|
}
|
||
|
|
||
|
// GetPasswdMasked returns the password read from the terminal, echoing asterisks.
|
||
|
// The returned byte array does not include end-of-line characters.
|
||
|
func GetPasswdMasked() ([]byte, error) {
|
||
|
return getPasswd("", true, os.Stdin, os.Stdout)
|
||
|
}
|
||
|
|
||
|
// GetPasswdPrompt prompts the user and returns the password read from the terminal.
|
||
|
// If mask is true, then asterisks are echoed.
|
||
|
// The returned byte array does not include end-of-line characters.
|
||
|
func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) {
|
||
|
return getPasswd(prompt, mask, r, w)
|
||
|
}
|