fix: render actual newline in terminal-sanitized error messages
sanitize_for_terminal() escaped every control char it saw, including plain LF, so any multi-line diagnostic built from a hardcoded template (e.g. the path-separator error in main.rs) came out on a TTY as the literal 4-character sequence "\x0A" instead of a line break. LF cannot draw, move the cursor, or exfiltrate data the way ESC- and CSI/OSC-initiated sequences can, so it does not need the same defense as those. CR is still escaped since it can overwrite the current line and forge fake output. Closes #2104
This commit is contained in:
1 parent
b6fe117b17
commit
8157be1243
2 files changed
+29
-6
No files matched your search
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
-
|
-
|
||||||
|
|
||||||
## Bugfixes
|
## Bugfixes
|
||||||
-
|
- Render actual newlines instead of the literal `\x0A` escape in multi-line error
|
||||||
|
messages printed to a terminal, see #2104
|
||||||
|
|
||||||
# 10.5.0
|
# 10.5.0
|
||||||
|
|
||||||
|
|||||||
+27
-5
@@ -3,11 +3,19 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
/// True for any char that is neither printable nor permitted whitespace (only HT).
|
/// True for any char that is neither printable nor permitted whitespace (only HT
|
||||||
/// Covers C0/C1/DEL, bidi overrides, zero-width and format chars, and tag chars.
|
/// and LF). Covers C0/C1/DEL, bidi overrides, zero-width and format chars, and tag
|
||||||
|
/// chars.
|
||||||
|
///
|
||||||
|
/// LF is deliberately let through even though it is a C0 control: it only ever
|
||||||
|
/// starts a new line and cannot draw, move the cursor or exfiltrate data the way
|
||||||
|
/// ESC- and CSI/OSC-initiated sequences can, so escaping it just mangles the
|
||||||
|
/// developer-authored multi-line diagnostics fd prints (see sharkdp/fd#2104).
|
||||||
|
/// CR is still escaped: unlike LF it can overwrite the current line and forge
|
||||||
|
/// fake output.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn needs_escape(c: char) -> bool {
|
fn needs_escape(c: char) -> bool {
|
||||||
if c == '\t' {
|
if c == '\t' || c == '\n' {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
c.is_control()
|
c.is_control()
|
||||||
@@ -107,8 +115,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn strips_newline() {
|
fn preserves_newline() {
|
||||||
assert_eq!(sanitize_for_terminal("a\nb"), "a\\x0Ab");
|
assert!(matches!(sanitize_for_terminal("a\nb"), Cow::Borrowed(_)));
|
||||||
|
assert_eq!(sanitize_for_terminal("a\nb"), "a\nb");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preserves_real_newlines_in_diagnostics() {
|
||||||
|
// Multi-line diagnostics such as the path-separator error in main.rs use
|
||||||
|
// real newlines for readability; they carry no attacker-controlled bytes,
|
||||||
|
// so a bare '\n' must reach the terminal as an actual line break, not as
|
||||||
|
// the literal 4-character sequence "\x0A". See sharkdp/fd#2104.
|
||||||
|
let message = "The search pattern 'a/b' contains a path-separation character \
|
||||||
|
and will not lead to any search results.\n\n\
|
||||||
|
If you want to search for all files inside the 'a/b' directory, use a match-all pattern:\n\n \
|
||||||
|
fd . 'a/b'";
|
||||||
|
assert_eq!(sanitize_for_terminal(message), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user