Skip to main content

lightning_types/
string.rs

1// This file is Copyright its original authors, visible in version control
2// history.
3//
4// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7// You may not use this file except in accordance with one or both of these
8// licenses.
9
10//! Utilities for strings.
11
12use alloc::string::String;
13use core::fmt;
14
15use crate::unicode::*;
16
17/// Struct to `Display` fields in a safe way using `PrintableString`
18#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
19pub struct UntrustedString(pub String);
20
21impl fmt::Display for UntrustedString {
22	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23		PrintableString(&self.0).fmt(f)
24	}
25}
26
27/// A string that displays only printable characters, replacing control characters with
28/// [`core::char::REPLACEMENT_CHARACTER`].
29#[derive(Debug, PartialEq)]
30pub struct PrintableString<'a>(pub &'a str);
31
32impl<'a> fmt::Display for PrintableString<'a> {
33	fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
34		use core::fmt::Write;
35		for c in self.0.chars() {
36			let is_other = is_unicode_general_category_other(c);
37			let is_unassigned = is_unicode_general_category_unassigned(c);
38			let c = if c.is_control() || is_other || is_unassigned {
39				core::char::REPLACEMENT_CHARACTER
40			} else {
41				c
42			};
43			f.write_char(c)?;
44		}
45
46		Ok(())
47	}
48}
49
50#[cfg(test)]
51mod tests {
52	use super::PrintableString;
53
54	#[test]
55	fn displays_printable_string() {
56		assert_eq!(
57			format!("{}", PrintableString("I \u{1F496} LDK!\t\u{26A1}")),
58			"I \u{1F496} LDK!\u{FFFD}\u{26A1}",
59		);
60	}
61
62	#[test]
63	fn sanitizes_unicode_bidi_override_characters() {
64		// U+202E RIGHT-TO-LEFT OVERRIDE and friends are Unicode general category
65		// `Cf` (Format), not `Cc` (Control). They enable "Trojan Source" /
66		// bidi-spoofing attacks where an attacker-supplied string (e.g. a node
67		// alias gossiped from a peer) renders to a human reader as something
68		// other than its byte content. `PrintableString` is the sanitiser used
69		// for exactly these untrusted strings, so it must replace them.
70		let rendered = format!("{}", PrintableString("safe\u{202E}cipsxe.exe"));
71		assert!(
72			!rendered.contains('\u{202E}'),
73			"PrintableString left a U+202E RLO override in its output: {:?}",
74			rendered
75		);
76
77		// U+13440 is in the Egyptian Hieroglyph Format Controls block, but its
78		// general category is `Mn`, not `Cf`, so the `Cf` range ends at U+1343F.
79		assert_eq!(format!("{}", PrintableString("x\u{1343F}y\u{13440}z")), "x\u{FFFD}y\u{13440}z");
80	}
81}