Documentation
¶
Overview ¶
Package termsafe filters text someone else wrote before it reaches a terminal, and checks text an application is about to publish against the same rules.
A terminal obeys the escape sequences and control characters it is sent. Text read from a record, a log line or a store was written by whoever published it, so printing it raw lets that publisher drive the reader's terminal: set the window title, move the cursor, clear the screen, or make one string look like another with bidirectional overrides. Sanitize passes printable text, spaces and newlines, turns a tab into a space, drops every control character, every escape sequence and the zero-width and bidirectional characters, and bounds the result at MaxLines lines of MaxCols columns, so one value cannot scroll a terminal indefinitely. Validate and ValidateBounded are the publishing side of the same rules: they name the first thing in a value that Sanitize would have to strip.
Unlike the rest of this module, this package is about terminals. It still reads no environment of its own accord: an application that wants to know whether the locale draws UTF-8 passes its own lookup to UTF8Locale.
Index ¶
Examples ¶
Constants ¶
const ( MaxLines = 200 MaxCols = 512 )
MaxLines and MaxCols bound what Sanitize returns: at most MaxLines lines, each of at most MaxCols printed runes. ValidateBounded holds a value to the same two bounds.
Variables ¶
var ErrUnsafe = errors.New("termsafe: text a terminal would have to be protected from")
ErrUnsafe matches, through errors.Is, every error Validate and ValidateBounded return.
Functions ¶
func Abbrev ¶
Abbrev is the first twelve characters of s, filtered by Text: the form a message names a key by, from its hex. A key read from a file someone else wrote may be shorter than that, or not hex at all, so it is cut by rune, which never panics, and filtered like any other text from outside.
func Sanitize ¶
Sanitize returns s with only printable runes, spaces, tabs (as spaces) and newlines, bounded at MaxLines lines of MaxCols columns. A value cut at the line bound ends with "[truncated]". It never returns a byte sequence a terminal interprets, except an SGR sequence when o.ANSI is set, which is re-validated here rather than copied and is always followed by a reset: at the end, or before "[truncated]" when the value is cut.
Invalid UTF-8 becomes U+FFFD. Without o.ANSI, a half block drawn on a set background becomes a full block: half-block art inks a cell's second half with the background colour, and dropping the colour would otherwise break the picture into stripes, where a full block keeps its shape.
Example ¶
A value from a record somebody else published is filtered before it is printed. The window-title sequence and the screen clear are dropped, the bidirectional override that would reverse the rest of the line is dropped, and the colour survives only when the reader asked for it.
package main
import (
"fmt"
"github.com/lightwebinc/bcommon/termsafe"
)
func main() {
hostile := "status \x1b]0;you have been pwned\x07\x1b[2J\x1b[32mgreen\x1b[0m evil"
fmt.Printf("%q\n", termsafe.Text(hostile))
fmt.Printf("%q\n", termsafe.Sanitize(hostile, termsafe.Options{ANSI: true}))
fmt.Printf("%q\n", termsafe.Sanitize("café ☕", termsafe.Options{ASCII: true}))
}
Output: "status green evil" "status \x1b[32mgreen\x1b[0m evil\x1b[0m" "caf? ?"
func UTF8Locale ¶
UTF8Locale reports whether the locale says the terminal draws UTF-8, from the first of LC_ALL, LC_CTYPE and LANG that getenv answers with a value. A locale that says nothing is taken as UTF-8. The application passes its own lookup, os.Getenv for the process's environment, so this package reads no environment of its own.
Example ¶
Whether to degrade to ASCII is the locale's answer, read through the lookup the application passes: os.Getenv in a command, a map here.
package main
import (
"fmt"
"strings"
"github.com/lightwebinc/bcommon/termsafe"
)
func main() {
env := map[string]string{"LC_ALL": "C", "LANG": "en_US.UTF-8"}
getenv := func(k string) string { return env[k] }
fmt.Println(termsafe.UTF8Locale(getenv))
fmt.Println(termsafe.Abbrev("02" + strings.Repeat("ab", 32)))
}
Output: false 02ababababab
func Validate ¶
Validate reports the first thing in s that Sanitize would have to strip: invalid UTF-8, a control character, an escape sequence other than a colour (SGR) sequence, a zero-width or bidirectional character, or another non-printable character. Printable text, spaces, tabs and newlines pass, at any length. field names s in the error, which begins with it: "plan line 3 has a control character (U+0007)".
func ValidateBounded ¶
ValidateBounded is Validate that also holds s to MaxLines lines of MaxCols columns. The two bounds are display bounds, not content rules: they suit a value meant to be read on a screen, and Sanitize applies them to whatever it prints anyway, so content that is meant to be longer than a screen belongs with Validate.
Example ¶
The publishing side of the same rules: a value that a reader would have to strip is refused before it is published, naming the first offence. An application puts its own words in front and keeps both matches.
package main
import (
"errors"
"fmt"
"github.com/lightwebinc/bcommon/termsafe"
)
func main() {
errBody := errors.New("body text")
for _, v := range []string{"back monday\n\x1b[1mbold\x1b[0m is fine", "ring\x07 the bell", "\x1b]0;title\x07"} {
if err := termsafe.ValidateBounded("plan", v); err != nil {
err = fmt.Errorf("%w: %w", errBody, err)
fmt.Println(err, errors.Is(err, errBody), errors.Is(err, termsafe.ErrUnsafe))
continue
}
fmt.Println("ok")
}
}
Output: ok body text: plan line 1 has a control character (U+0007) true true body text: plan line 1 has an escape sequence that is not a colour (SGR) true true
Types ¶
type Options ¶
type Options struct {
// ANSI keeps colour and weight (SGR) sequences, and nothing else. Set it
// only when the reader asked for colour: the sequences are re-validated
// rather than copied, and a reset is always written at the end.
ANSI bool
// ASCII degrades every rune above 0x7E to "?", for a terminal that
// cannot draw it.
ASCII bool
}
Options is how a value may reach the terminal.