fix(bonfire): replace invalid UTF-8 before the control scan in Sanitize (IN-08, quick-261028-rqd)
This commit is contained in:
@@ -140,7 +140,7 @@ if catalog.Has("blog:import") {
|
||||
| `bonfire.WithHint` | Wraps an error in a `bonfire.HintError`; `{bin}` in the Try text becomes `bin/<name>` or `summer`. |
|
||||
| `bonfire.ErrReported` | Marks an error whose details the command already printed. |
|
||||
| `bonfire.Reported` | Wraps an error so it matches `bonfire.ErrReported`: exit code 1, or the code of `bonfire.WithExitCode`, and no error block. |
|
||||
| `bonfire.Sanitize` | Removes control characters other than tab (line breaks become spaces), so untrusted text cannot move the cursor or retitle the terminal; error blocks, process views, failure lines, tables and diary's console use it. |
|
||||
| `bonfire.Sanitize` | Replaces invalid UTF-8 with U+FFFD and removes control characters other than tab (line breaks become spaces), so untrusted text, including a raw 8-bit C1 byte, cannot move the cursor or retitle the terminal; error blocks, process views, failure lines, tables and diary's console use it. |
|
||||
| `bonfire.WithExitCode` | Makes an error carry the exit code `bonfire.Execute` returns for it (1 to 255; other codes give 1). |
|
||||
| `bonfire.Listing` | The JSON catalog of `list --format=json`: `Application` (`bonfire.ApplicationInfo`), `GlobalOptions` and `Commands`. |
|
||||
| `bonfire.ApplicationInfo` | Name, version and framework version of a binary in a `bonfire.Listing`. |
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.golem15.com/golem15/summercms/internal/argmask"
|
||||
"git.golem15.com/golem15/summercms/modules/sunscreen"
|
||||
@@ -102,18 +103,23 @@ func Reported(err error) error {
|
||||
// text, names an administrator typed) then cannot move the cursor, clear
|
||||
// the screen or set the window title. Error blocks, process views, failure
|
||||
// lines and tables pass their text through it, and so does diary's console
|
||||
// channel. A clean string is returned unchanged.
|
||||
// channel. Invalid UTF-8 becomes U+FFFD first, so a raw 8-bit C1 byte (such
|
||||
// as 0x9b, CSI) cannot reach a terminal that honours it, and the result is
|
||||
// always valid UTF-8. A clean, valid string is returned unchanged.
|
||||
func Sanitize(s string) string {
|
||||
clean := true
|
||||
for _, r := range s {
|
||||
if r != '\t' && unicode.IsControl(r) {
|
||||
clean = false
|
||||
break
|
||||
clean := utf8.ValidString(s)
|
||||
if clean {
|
||||
for _, r := range s {
|
||||
if r != '\t' && unicode.IsControl(r) {
|
||||
clean = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if clean {
|
||||
return s
|
||||
}
|
||||
s = strings.ToValidUTF8(s, "\uFFFD")
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
switch {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// TestExitCodes: Execute returns 0 on success, 1 on a failure and 2 on an
|
||||
@@ -323,9 +324,21 @@ func TestSanitize(t *testing.T) {
|
||||
"a\x1b]0;title\x07b": "a]0;titleb",
|
||||
"nul\x00 and del\x7f and \x01": "nul and del and ",
|
||||
"boom\x1b[2Jclear": "boom[2Jclear",
|
||||
// IN-08: invalid UTF-8 becomes U+FFFD, so a raw 8-bit C1 byte
|
||||
// such as 0x9b (CSI) never reaches the terminal.
|
||||
"a\x9b31mb": "a\uFFFD31mb",
|
||||
"\x9b": "\uFFFD",
|
||||
"\xffok": "\uFFFDok",
|
||||
} {
|
||||
if got := Sanitize(in); got != want {
|
||||
t.Errorf("Sanitize(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
if got := Sanitize(in); !utf8.ValidString(got) {
|
||||
t.Errorf("Sanitize(%q) = %q is not valid UTF-8", in, got)
|
||||
}
|
||||
}
|
||||
clean := "plain ok"
|
||||
if got := Sanitize(clean); got != clean {
|
||||
t.Errorf("Sanitize(%q) = %q, want it unchanged", clean, got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user