feat: revealed JSON is shown formatted, and PEM certificates as what they say

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-11 14:31:46 +02:00
1 parent 1d8a7d2842
commit ece44daadb
3 files changed
+147 -1

No files matched your search

+8 -1
View File
@@ -313,7 +313,8 @@ func (d Detail) lines(st styles.Styles) []string {
case ValueShown: case ValueShown:
// Where the dots were, continuing under itself; the keys below. // Where the dots were, continuing under itself; the keys below.
first := true first := true
for _, line := range strings.Split(d.value.Reveal(), "\n") { lines, note := valueView(d.value.Reveal())
for _, line := range lines {
for _, w := range strings.Split(ansi.Hardwrap(line, max(1, inner-11), true), "\n") { for _, w := range strings.Split(ansi.Hardwrap(line, max(1, inner-11), true), "\n") {
if first { if first {
out = append(out, " "+st.Label.Render(fmt.Sprintf("%-10s", label))+" "+st.Value.Render(w)) out = append(out, " "+st.Label.Render(fmt.Sprintf("%-10s", label))+" "+st.Value.Render(w))
@@ -324,6 +325,12 @@ func (d Detail) lines(st styles.Styles) []string {
} }
} }
var more []string var more []string
if note != "" && d.CopyKey != "" {
note += ": " + d.CopyKey + " copies it as stored"
}
if note != "" {
more = append(more, note)
}
if d.hidesIn != "" { if d.hidesIn != "" {
more = append(more, "hides in "+d.hidesIn) more = append(more, "hides in "+d.hidesIn)
} }
+79
View File
@@ -0,0 +1,79 @@
package panes
import (
"bytes"
"crypto/sha1"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"strings"
"time"
)
// A revealed value is shown as it reads best: JSON indented, and PEM
// certificates as what they say (subject, issuer, dates, names) rather
// than base64, with any other PEM block, a private key say, named but not
// shown. Anything else is shown as it is. y copies it as stored.
// valueView is how to show v, and what's been done to it ("" when it's
// shown as it is).
func valueView(v string) (lines []string, note string) {
if lines, ok := pemView(v); ok {
return lines, "certificate"
}
if t := strings.TrimSpace(v); (strings.HasPrefix(t, "{") || strings.HasPrefix(t, "[")) && json.Valid([]byte(t)) {
var b bytes.Buffer
if json.Indent(&b, []byte(t), "", " ") == nil {
return strings.Split(b.String(), "\n"), "JSON, formatted"
}
}
return strings.Split(v, "\n"), ""
}
// pemView describes the PEM blocks in v, if it's PEM with a certificate.
func pemView(v string) ([]string, bool) {
rest := []byte(strings.TrimSpace(v))
var out []string
certs := 0
for len(rest) > 0 {
block, after := pem.Decode(rest)
if block == nil {
return nil, false // text that isn't PEM: show it all
}
rest = bytes.TrimSpace(after)
if len(out) > 0 {
out = append(out, "")
}
if block.Type != "CERTIFICATE" {
out = append(out, block.Type+" (not shown)")
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, false
}
certs++
out = append(out, certLines(cert)...)
}
return out, certs > 0
}
func certLines(c *x509.Certificate) []string {
day := func(t time.Time) string { return t.UTC().Format("2006-01-02") }
out := []string{
"subject " + c.Subject.String(),
"issuer " + c.Issuer.String(),
"valid " + day(c.NotBefore) + " to " + day(c.NotAfter),
}
names := append([]string{}, c.DNSNames...)
for _, ip := range c.IPAddresses {
names = append(names, ip.String())
}
names = append(names, c.EmailAddresses...)
if len(names) > 0 {
out = append(out, "names "+strings.Join(names, ", "))
}
sum := sha1.Sum(c.Raw) // what Azure and Windows call the thumbprint
return append(out, "SHA-1 "+fmt.Sprintf("%X", sum[:]))
}
+60
View File
@@ -0,0 +1,60 @@
package panes
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"slices"
"strings"
"testing"
"time"
)
func TestValueViewJSON(t *testing.T) {
lines, note := valueView(`{"user":"app","port":5432,"tags":["a"]}`)
want := []string{"{", ` "user": "app",`, ` "port": 5432,`, ` "tags": [`, ` "a"`, " ]", "}"}
if !slices.Equal(lines, want) || note != "JSON, formatted" {
t.Errorf("lines %q, note %q", lines, note)
}
for _, v := range []string{"plain value", "{not json", "42", "line one\nline two"} {
if lines, note := valueView(v); note != "" || strings.Join(lines, "\n") != v {
t.Errorf("%q shown as %q (%q)", v, lines, note)
}
}
}
func TestValueViewCertificate(t *testing.T) {
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "app.example.com"},
DNSNames: []string{"app.example.com", "www.app.example.com"},
NotBefore: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), NotAfter: time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC),
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
keyDER, _ := x509.MarshalECPrivateKey(key)
bundle := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})) +
string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}))
lines, note := valueView(bundle)
text := strings.Join(lines, "\n")
for _, want := range []string{"subject CN=app.example.com", "valid 2026-01-01 to 2027-01-01",
"names app.example.com, www.app.example.com", "SHA-1 ", "EC PRIVATE KEY (not shown)"} {
if !strings.Contains(text, want) {
t.Errorf("missing %q:\n%s", want, text)
}
}
if note != "certificate" || strings.Contains(text, "BEGIN") {
t.Errorf("note %q, or PEM shown:\n%s", note, text)
}
// A private key alone isn't a certificate: shown as it is.
if _, note := valueView(string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}))); note != "" {
t.Errorf("a key alone: note %q", note)
}
}