Skip to content

Commit

Permalink
Use terminal to read password on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Jul 31, 2021
1 parent 69dc0f3 commit 47de173
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 135 deletions.
78 changes: 6 additions & 72 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bufio"
"bytes"
"encoding/json"
"errors"
Expand Down Expand Up @@ -1343,27 +1344,15 @@ func (c *Config) readConfig() error {
}

func (c *Config) readLine(prompt string) (string, error) {
var line string
if err := c.withTerminal(prompt, func(t terminal) error {
var err error
line, err = t.ReadLine()
return err
}); err != nil {
_, err := c.stdout.Write([]byte(prompt))
if err != nil {
return "", err
}
return line, nil
}

func (c *Config) readPassword(prompt string) (string, error) {
var password string
if err := c.withTerminal("", func(t terminal) error {
var err error
password, err = t.ReadPassword(prompt)
return err
}); err != nil {
line, err := bufio.NewReader(c.stdin).ReadString('\n')
if err != nil {
return "", err
}
return password, nil
return strings.TrimSuffix(line, "\n"), nil
}

func (c *Config) run(dir chezmoi.AbsPath, name string, args []string) error {
Expand Down Expand Up @@ -1518,61 +1507,6 @@ func (c *Config) validateData() error {
return validateKeys(c.Data, identifierRx)
}

func (c *Config) withTerminal(prompt string, f func(terminal) error) error {
if c.noTTY || runtime.GOOS == "windows" {
return f(newDumbTerminal(c.stdin, c.stdout, prompt))
}

if stdinFile, ok := c.stdin.(*os.File); ok && term.IsTerminal(int(stdinFile.Fd())) {
fd := int(stdinFile.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
return err
}
oldState, err := term.MakeRaw(fd)
if err != nil {
return err
}
defer func() {
_ = term.Restore(fd, oldState)
}()
t := term.NewTerminal(struct {
io.Reader
io.Writer
}{
Reader: c.stdin,
Writer: c.stdout,
}, prompt)
if err := t.SetSize(width, height); err != nil {
return err
}
return f(t)
}

devTTY, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return err
}
defer devTTY.Close()
fd := int(devTTY.Fd())
width, height, err := term.GetSize(fd)
if err != nil {
return err
}
oldState, err := term.MakeRaw(fd)
if err != nil {
return err
}
defer func() {
_ = term.Restore(fd, oldState)
}()
t := term.NewTerminal(devTTY, prompt)
if err := t.SetSize(width, height); err != nil {
return err
}
return f(t)
}

func (c *Config) writeOutput(data []byte) error {
if c.outputAbsPath == "" || c.outputAbsPath == "-" {
_, err := c.stdout.Write(data)
Expand Down
35 changes: 35 additions & 0 deletions internal/cmd/config_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//+build !windows

package cmd

import (
"errors"
"os"

"golang.org/x/term"
)

func (c *Config) readPassword(prompt string) (string, error) {
if c.noTTY {
return c.readLine(prompt)
}

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return "", err
}
defer func() {
_ = tty.Close()
}()
if _, err := tty.Write([]byte(prompt)); err != nil {
return "", err
}
password, err := term.ReadPassword(int(tty.Fd()))
if err != nil && !errors.Is(err, term.ErrPasteIndicator) {
return "", err
}
if _, err := tty.Write([]byte{'\n'}); err != nil {
return "", err
}
return string(password), nil
}
35 changes: 35 additions & 0 deletions internal/cmd/config_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"fmt"

"golang.org/x/sys/windows"
"golang.org/x/term"
)

func (c *Config) readPassword(prompt string) (string, error) {
if c.noTTY {
return c.readLine(prompt)
}

name, err := windows.UTF16PtrFromString("CONIN$")
if err != nil {
return "", err
}
handle, err := windows.CreateFile(name, windows.GENERIC_READ|windows.GENERIC_WRITE, windows.FILE_SHARE_READ, nil, windows.OPEN_EXISTING, 0, 0)
if err != nil {
return "", err
}
defer func() {
_ = windows.CloseHandle(handle)
}()
//nolint:forbidigo
fmt.Print(prompt)
password, err := term.ReadPassword(int(handle))
if err != nil {
return "", err
}
//nolint:forbidigo
fmt.Println("")
return string(password), nil
}
2 changes: 1 addition & 1 deletion internal/cmd/internaltestcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ func (c *Config) runInternalTestReadPasswordCmd(cmd *cobra.Command, args []strin
if err != nil {
return err
}
return c.writeOutputString(password)
return c.writeOutputString(password + "\n")
}
62 changes: 0 additions & 62 deletions internal/cmd/terminal.go

This file was deleted.

0 comments on commit 47de173

Please sign in to comment.