Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: move tui code to tui package #591

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 43 additions & 42 deletions cmd/age/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package main
import (
"bufio"
"bytes"
"filippo.io/age/tui"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -104,7 +105,7 @@ func main() {

if len(os.Args) == 1 {
flag.Usage()
exit(1)
tui.Exit(1)
}

var (
Expand Down Expand Up @@ -180,47 +181,47 @@ func main() {
hints = append(hints, "only a single input file may be specified at a time")
}

errorWithHint("too many INPUT arguments: "+quotedArgs, hints...)
tui.ErrorWithHint("too many INPUT arguments: "+quotedArgs, hints...)
}

switch {
case decryptFlag:
if encryptFlag {
errorf("-e/--encrypt can't be used with -d/--decrypt")
tui.Errorf("-e/--encrypt can't be used with -d/--decrypt")
}
if armorFlag {
errorWithHint("-a/--armor can't be used with -d/--decrypt",
tui.ErrorWithHint("-a/--armor can't be used with -d/--decrypt",
"note that armored files are detected automatically")
}
if passFlag {
errorWithHint("-p/--passphrase can't be used with -d/--decrypt",
tui.ErrorWithHint("-p/--passphrase can't be used with -d/--decrypt",
"note that password protected files are detected automatically")
}
if len(recipientFlags) > 0 {
errorWithHint("-r/--recipient can't be used with -d/--decrypt",
tui.ErrorWithHint("-r/--recipient can't be used with -d/--decrypt",
"did you mean to use -i/--identity to specify a private key?")
}
if len(recipientsFileFlags) > 0 {
errorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
tui.ErrorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
"did you mean to use -i/--identity to specify a private key?")
}
default: // encrypt
if len(identityFlags) > 0 && !encryptFlag {
errorWithHint("-i/--identity and -j can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
tui.ErrorWithHint("-i/--identity and -j can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
"did you forget to specify -d/--decrypt?")
}
if len(recipientFlags)+len(recipientsFileFlags)+len(identityFlags) == 0 && !passFlag {
errorWithHint("missing recipients",
tui.ErrorWithHint("missing recipients",
"did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
}
if len(recipientFlags) > 0 && passFlag {
errorf("-p/--passphrase can't be combined with -r/--recipient")
tui.Errorf("-p/--passphrase can't be combined with -r/--recipient")
}
if len(recipientsFileFlags) > 0 && passFlag {
errorf("-p/--passphrase can't be combined with -R/--recipients-file")
tui.Errorf("-p/--passphrase can't be combined with -R/--recipients-file")
}
if len(identityFlags) > 0 && passFlag {
errorf("-p/--passphrase can't be combined with -i/--identity and -j")
tui.Errorf("-p/--passphrase can't be combined with -i/--identity and -j")
}
}

Expand All @@ -241,7 +242,7 @@ func main() {
inUseFiles = append(inUseFiles, absPath(name))
f, err := os.Open(name)
if err != nil {
errorf("failed to open input file %q: %v", name, err)
tui.Errorf("failed to open input file %q: %v", name, err)
}
defer f.Close()
in = f
Expand All @@ -251,23 +252,23 @@ func main() {
// If the input comes from a TTY, assume it's armored, and buffer up
// to the END line (or EOF/EOT) so that a password prompt or the
// output don't get in the way of typing the input. See Issue 364.
buf, err := bufferTerminalInput(in)
buf, err := tui.BufferTerminalInput(in)
if err != nil {
errorf("failed to buffer terminal input: %v", err)
tui.Errorf("failed to buffer terminal input: %v", err)
}
in = buf
}
}
if name := outFlag; name != "" && name != "-" {
for _, f := range inUseFiles {
if f == absPath(name) {
errorf("input and output file are the same: %q", name)
tui.Errorf("input and output file are the same: %q", name)
}
}
f := newLazyOpener(name)
defer func() {
if err := f.Close(); err != nil {
errorf("failed to close output file %q: %v", name, err)
tui.Errorf("failed to close output file %q: %v", name, err)
}
}()
out = f
Expand All @@ -278,7 +279,7 @@ func main() {
} else if !armorFlag {
// If the output wouldn't be armored, refuse to send binary to
// the terminal unless explicitly requested with "-o -".
errorWithHint("refusing to output binary to the terminal",
tui.ErrorWithHint("refusing to output binary to the terminal",
"did you mean to use -a/--armor?",
`force anyway with "-o -"`)
}
Expand All @@ -305,7 +306,7 @@ func main() {
}

func passphrasePromptForEncryption() (string, error) {
pass, err := readSecret("Enter passphrase (leave empty to autogenerate a secure one):")
pass, err := tui.ReadSecret("Enter passphrase (leave empty to autogenerate a secure one):")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
Expand All @@ -316,12 +317,12 @@ func passphrasePromptForEncryption() (string, error) {
words = append(words, randomWord())
}
p = strings.Join(words, "-")
err := printfToTerminal("using autogenerated passphrase %q", p)
err := tui.PrintfToTerminal("using autogenerated passphrase %q", p)
if err != nil {
return "", fmt.Errorf("could not print passphrase: %v", err)
}
} else {
confirm, err := readSecret("Confirm passphrase:")
confirm, err := tui.ReadSecret("Confirm passphrase:")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
Expand All @@ -337,19 +338,19 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
for _, arg := range recs {
r, err := parseRecipient(arg)
if err, ok := err.(gitHubRecipientError); ok {
errorWithHint(err.Error(), "instead, use recipient files like",
tui.ErrorWithHint(err.Error(), "instead, use recipient files like",
" curl -O https://github.com/"+err.username+".keys",
" age -R "+err.username+".keys")
}
if err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
recipients = append(recipients, r)
}
for _, name := range files {
recs, err := parseRecipientsFile(name)
if err != nil {
errorf("failed to parse recipient file %q: %v", name, err)
tui.Errorf("failed to parse recipient file %q: %v", name, err)
}
recipients = append(recipients, recs...)
}
Expand All @@ -358,17 +359,17 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
case "i":
ids, err := parseIdentitiesFile(f.Value)
if err != nil {
errorf("reading %q: %v", f.Value, err)
tui.Errorf("reading %q: %v", f.Value, err)
}
r, err := identitiesToRecipients(ids)
if err != nil {
errorf("internal error processing %q: %v", f.Value, err)
tui.Errorf("internal error processing %q: %v", f.Value, err)
}
recipients = append(recipients, r...)
case "j":
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
id, err := plugin.NewIdentityWithoutData(f.Value, tui.PluginTerminalUI)
if err != nil {
errorf("initializing %q: %v", f.Value, err)
tui.Errorf("initializing %q: %v", f.Value, err)
}
recipients = append(recipients, id.Recipient())
}
Expand All @@ -379,12 +380,12 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader
func encryptPass(in io.Reader, out io.Writer, armor bool) {
pass, err := passphrasePromptForEncryption()
if err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}

r, err := age.NewScryptRecipient(pass)
if err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
testOnlyConfigureScryptIdentity(r)
encrypt([]age.Recipient{r}, in, out, armor)
Expand All @@ -397,20 +398,20 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor
a := armor.NewWriter(out)
defer func() {
if err := a.Close(); err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
}()
out = a
}
w, err := age.Encrypt(out, recipients...)
if err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
if _, err := io.Copy(w, in); err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
if err := w.Close(); err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
}

Expand All @@ -426,7 +427,7 @@ func (rejectScryptIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
if len(stanzas) != 1 || stanzas[0].Type != "scrypt" {
return nil, age.ErrIncorrectIdentity
}
errorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j",
tui.ErrorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j",
"remove all -i/--identity/-j flags to decrypt passphrase-encrypted files")
panic("unreachable")
}
Expand All @@ -439,13 +440,13 @@ func decryptNotPass(flags identityFlags, in io.Reader, out io.Writer) {
case "i":
ids, err := parseIdentitiesFile(f.Value)
if err != nil {
errorf("reading %q: %v", f.Value, err)
tui.Errorf("reading %q: %v", f.Value, err)
}
identities = append(identities, ids...)
case "j":
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
id, err := plugin.NewIdentityWithoutData(f.Value, tui.PluginTerminalUI)
if err != nil {
errorf("initializing %q: %v", f.Value, err)
tui.Errorf("initializing %q: %v", f.Value, err)
}
identities = append(identities, id)
}
Expand All @@ -468,7 +469,7 @@ func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {
rr := bufio.NewReader(in)
if intro, _ := rr.Peek(len(crlfMangledIntro)); string(intro) == crlfMangledIntro ||
string(intro) == utf16MangledIntro {
errorWithHint("invalid header intro",
tui.ErrorWithHint("invalid header intro",
"it looks like this file was corrupted by PowerShell redirection",
"consider using -o or -a to encrypt files in PowerShell")
}
Expand All @@ -481,16 +482,16 @@ func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {

r, err := age.Decrypt(in, identities...)
if err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
out.Write(nil) // trigger the lazyOpener even if r is empty
if _, err := io.Copy(out, r); err != nil {
errorf("%v", err)
tui.Errorf("%v", err)
}
}

func passphrasePromptForDecryption() (string, error) {
pass, err := readSecret("Enter passphrase:")
pass, err := tui.ReadSecret("Enter passphrase:")
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/age/age_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main

import (
"bufio"
"filippo.io/age/tui"
"os"
"testing"

Expand All @@ -16,9 +17,9 @@ import (
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"age": func() (exitCode int) {
testOnlyPanicInsteadOfExit = true
tui.TestOnlyPanicInsteadOfExit = true
defer func() {
if testOnlyDidExit {
if tui.TestOnlyDidExit {
exitCode = recover().(int)
}
}()
Expand Down
13 changes: 7 additions & 6 deletions cmd/age/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package main
import (
"bufio"
"encoding/base64"
"filippo.io/age/tui"
"fmt"
"io"
"os"
Expand All @@ -31,7 +32,7 @@ func (gitHubRecipientError) Error() string {
func parseRecipient(arg string) (age.Recipient, error) {
switch {
case strings.HasPrefix(arg, "age1") && strings.Count(arg, "1") > 1:
return plugin.NewRecipient(arg, pluginTerminalUI)
return plugin.NewRecipient(arg, tui.PluginTerminalUI)
case strings.HasPrefix(arg, "age1"):
return age.ParseX25519Recipient(arg)
case strings.HasPrefix(arg, "ssh-"):
Expand Down Expand Up @@ -79,7 +80,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
if err != nil {
if t, ok := sshKeyType(line); ok {
// Skip unsupported but valid SSH public keys with a warning.
warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
tui.Warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
continue
}
// Hide the error since it might unintentionally leak the contents
Expand Down Expand Up @@ -162,14 +163,14 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
return []age.Identity{&EncryptedIdentity{
Contents: contents,
Passphrase: func() (string, error) {
pass, err := readSecret(fmt.Sprintf("Enter passphrase for identity file %q:", name))
pass, err := tui.ReadSecret(fmt.Sprintf("Enter passphrase for identity file %q:", name))
if err != nil {
return "", fmt.Errorf("could not read passphrase: %v", err)
}
return string(pass), nil
},
NoMatchWarning: func() {
warningf("encrypted identity file %q didn't match file's recipients", name)
tui.Warningf("encrypted identity file %q didn't match file's recipients", name)
},
}}, nil

Expand Down Expand Up @@ -198,7 +199,7 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
func parseIdentity(s string) (age.Identity, error) {
switch {
case strings.HasPrefix(s, "AGE-PLUGIN-"):
return plugin.NewIdentity(s, pluginTerminalUI)
return plugin.NewIdentity(s, tui.PluginTerminalUI)
case strings.HasPrefix(s, "AGE-SECRET-KEY-1"):
return age.ParseX25519Identity(s)
default:
Expand Down Expand Up @@ -246,7 +247,7 @@ func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
}
}
passphrasePrompt := func() ([]byte, error) {
pass, err := readSecret(fmt.Sprintf("Enter passphrase for %q:", name))
pass, err := tui.ReadSecret(fmt.Sprintf("Enter passphrase for %q:", name))
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", name, err)
}
Expand Down
Loading
Loading