secret.go RawHistoryFolder
1package neferclient
2
3import "unicode/utf8"
4
5// maxSecretCapacity bounds [NewSecretBuffer].
6const maxSecretCapacity = 4096
7
8// SecretBuffer collects text typed while it is installed with
9// [Seat.SetSecret], for example a password. Its storage is allocated once at
10// full size, so it never reallocates and leaves no stale copies behind; the
11// text is never turned into a string by the library.
12//
13// Only the owner goroutine (the one calling [Conn.Dispatch]) may use it.
14type SecretBuffer struct {
15 buf []byte // capacity*utf8.UTFMax bytes, fixed
16 n int // bytes in use
17 points int // code points in use
18 max int // capacity in code points
19}
20
21// NewSecretBuffer returns a buffer holding up to capacity code points.
22// capacity is clamped to 1..4096.
23func NewSecretBuffer(capacity int) *SecretBuffer {
24 capacity = min(max(capacity, 1), maxSecretCapacity)
25 return &SecretBuffer{buf: make([]byte, capacity*utf8.UTFMax), max: capacity}
26}
27
28// Len returns the number of code points held.
29func (b *SecretBuffer) Len() int {
30 if b == nil {
31 return 0
32 }
33 return b.points
34}
35
36// Bytes returns the UTF-8 text held. The slice aliases the buffer: do not
37// retain it, convert it to a string or log it; it changes on the next edit.
38func (b *SecretBuffer) Bytes() []byte {
39 if b == nil {
40 return nil
41 }
42 return b.buf[:b.n:b.n]
43}
44
45// Wipe zeroes the whole storage and empties the buffer.
46func (b *SecretBuffer) Wipe() {
47 if b == nil {
48 return
49 }
50 clear(b.buf)
51 b.n, b.points = 0, 0
52}
53
54// appendText adds text, which must be valid UTF-8. It reports false and
55// changes nothing when the text does not fit.
56func (b *SecretBuffer) appendText(text []byte) bool {
57 cp := utf8.RuneCount(text)
58 if cp == 0 || b.points+cp > b.max {
59 return false
60 }
61 b.n += copy(b.buf[b.n:], text)
62 b.points += cp
63 return true
64}
65
66// backspace removes the last code point and wipes its bytes.
67func (b *SecretBuffer) backspace() bool {
68 if b.points == 0 {
69 return false
70 }
71 _, size := utf8.DecodeLastRune(b.buf[:b.n])
72 clear(b.buf[b.n-size : b.n])
73 b.n -= size
74 b.points--
75 return true
76}
77
78// classifySecretText reports whether text is printable input for a secret:
79// non-empty, valid UTF-8 and free of C0 controls and DEL.
80func classifySecretText(text []byte) bool {
81 if len(text) == 0 || !utf8.Valid(text) {
82 return false
83 }
84 for rest := text; len(rest) > 0; {
85 r, size := utf8.DecodeRune(rest)
86 if r < 0x20 || r == 0x7f {
87 return false
88 }
89 rest = rest[size:]
90 }
91 return true
92}