Use data.redacted() to visualize redacted ranges

This commit is contained in:
Hendrik Eeckhaut
2023-11-22 09:37:00 +01:00
parent 5ca4161502
commit ad26565df0
3 changed files with 83 additions and 103 deletions

View File

@@ -18,6 +18,7 @@ elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
webpki-roots = "0.25.2" webpki-roots = "0.25.2"
wasm-logger = "0.2.0" wasm-logger = "0.2.0"
web-time = "0.2" web-time = "0.2"
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
# tlsn-core = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" } # tlsn-core = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
[dependencies.tlsn-core] [dependencies.tlsn-core]

View File

@@ -1,10 +1,8 @@
use std::fmt; use std::{fmt, ops::Range};
use gloo::console::log;
use yew::prelude::*; use yew::prelude::*;
pub const REDACTED_CHAR: char = '█';
// pub const REDACTED_CHAR: char = '🙈';
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum Direction { pub enum Direction {
Send, Send,
@@ -23,116 +21,91 @@ impl fmt::Display for Direction {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub direction: Direction, pub direction: Direction,
pub bytes: String, pub redacted_char: char,
pub bytes: Vec<u8>,
pub redacted_ranges: Vec<Range<usize>>,
} }
//split the text in regular text parts and redacted parts fn get_redacted_string(redacted_char: &char, size: usize) -> String {
fn split_text(text: String) -> Vec<String> { if true || size < 10 {
let (mut parts, last_part) = text.chars().fold( redacted_char.to_string().repeat(size)
(Vec::new(), String::new()), } else {
|(mut acc, mut current_part), c| { format! {"{}...{}", redacted_char.to_string().repeat(3), redacted_char.to_string().repeat(3)}
let previous_c = current_part.chars().last().unwrap_or(REDACTED_CHAR); }
if (c == REDACTED_CHAR) == (previous_c == REDACTED_CHAR) { }
current_part.push(c);
} else { fn redactions_in_red(
if !current_part.is_empty() { bytes: &Vec<u8>,
acc.push(current_part.clone()); redacted_ranges: &Vec<Range<usize>>,
} redacted_char: &char,
current_part.clear(); ) -> Html {
current_part.push(c); if redacted_ranges.is_empty() {
} return Html::from(String::from_utf8(bytes.to_vec()).unwrap());
(acc, current_part) }
// create ranges for non redacted parts and store last redacted position
let (non_redacted_ranges, last_redacted_position) = redacted_ranges.iter().fold(
(Vec::new(), 0), // (Accumulator vector, last redacted position)
|(mut acc, last_end), range| {
acc.push(Range {
start: last_end,
end: range.start,
});
(acc, range.end)
}, },
); );
if !last_part.is_empty() {
parts.push(last_part);
}
parts
}
fn redactions_in_red(text: String) -> Html { // interweave the redacted and non-redacted ranges
let parts = split_text(text); let all_ranges = non_redacted_ranges
.into_iter()
//color redacted parts in red .zip(redacted_ranges.iter())
let html: Html = parts .flat_map(|(non_redacted, redacted)| {
.iter() vec![
.map(|part| match part { (non_redacted.start, non_redacted.end, false),
x if x.starts_with(REDACTED_CHAR) => Html::from_html_unchecked(AttrValue::from( (redacted.start, redacted.end, true),
format!("<span style=\"color:red;\">{}</span>", x), ]
)),
_ => Html::from(part),
}) })
.collect(); .chain(std::iter::once((
last_redacted_position,
bytes.len(),
false,
))); // Handle remaining non-redacted part
html let html_nodes = all_ranges
.map(|(start, end, is_redacted)| {
if is_redacted {
Html::from_html_unchecked(AttrValue::from(format!(
"<span style=\"color:red;\">{}</span>",
get_redacted_string(redacted_char, end - start)
)))
} else {
Html::from(String::from_utf8_lossy(&bytes[start..end]))
}
})
.collect::<Vec<_>>();
html! {
<>
{ for html_nodes }
</>
}
} }
#[function_component] #[function_component]
pub fn RedactedBytesComponent(props: &Props) -> Html { pub fn RedactedBytesComponent(props: &Props) -> Html {
let Props { direction, bytes } = props; let Props {
direction,
let redacted_transcript = bytes.replace('\0', REDACTED_CHAR.to_string().as_str()); redacted_char,
bytes,
redacted_ranges,
} = props;
html! { html! {
<details class="p-4 w-5/6" open={true}> <details class="p-4 w-5/6" open={true}>
<summary><b>{"Bytes "}{direction}{": " }</b></summary> <summary><b>{"Bytes "}{direction}{": " }</b></summary>
<div class="bg-black text-white p-4 rounded-md overflow-x-auto"> <div class="bg-black text-white p-4 rounded-md overflow-x-auto">
<pre>{redactions_in_red(redacted_transcript)}</pre> <pre>{redactions_in_red(bytes, redacted_ranges, redacted_char)}</pre>
</div> </div>
</details> </details>
} }
} }
#[cfg(test)]
mod tests {
use super::*;
fn redacted(length: usize) -> String {
'█'.to_string().repeat(length)
}
#[test]
fn test_split_text_starts_with_redacted() {
let foobar = String::from("foobar");
let text = format!("{}{}", redacted(8), &foobar);
let result1 = split_text(text.to_owned());
assert_eq!(result1, vec![redacted(8), foobar]);
}
#[test]
fn test_split_text_no_redactions() {
let text = "NoRedactedParts";
let result = split_text(text.to_owned());
assert_eq!(result, vec![text]);
}
#[test]
fn test_split_text_ends_with_redaction() {
let foobar = String::from("foobar");
let text = format!("{}{}", &foobar, redacted(8));
let result = split_text(text);
assert_eq!(result, vec![foobar, redacted(8)]);
}
#[test]
fn test_split_text_multiple() {
let text = format!("foobar {} test {} end", redacted(8), redacted(1));
let result = split_text(text);
assert_eq!(
result,
vec![
"foobar ",
redacted(8).as_str(),
" test ",
redacted(1).as_str(),
" end"
]
);
}
#[test]
fn test_split_text_empty() {
let result5 = split_text(String::from(""));
assert_eq!(result5, Vec::<String>::new());
}
}

View File

@@ -1,4 +1,5 @@
extern crate base64; extern crate base64;
use std::ops::Range;
use std::str; use std::str;
use web_time::Duration; use web_time::Duration;
@@ -10,6 +11,8 @@ use crate::components::content_iframe::ContentIFrame;
use crate::components::redacted_bytes_component::Direction; use crate::components::redacted_bytes_component::Direction;
use crate::components::redacted_bytes_component::RedactedBytesComponent; use crate::components::redacted_bytes_component::RedactedBytesComponent;
const REDACTED_CHAR: char = '█'; // 'X'. '🙈';
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct Props { pub struct Props {
pub name: String, pub name: String,
@@ -85,9 +88,11 @@ pub fn ViewFile(props: &Props) -> Html {
sent.set_redacted(b'\0'); sent.set_redacted(b'\0');
recv.set_redacted(b'\0'); recv.set_redacted(b'\0');
let bytes_send = String::from_utf8(sent.data().to_vec()).unwrap(); let redacted_ranges_send: Vec<Range<usize>> =
sent.redacted().clone().iter_ranges().collect();
let bytes_received = String::from_utf8(recv.data().to_vec()).unwrap(); let bytes_recv = String::from_utf8(recv.data().to_vec()).unwrap();
let redacted_ranges_recv: Vec<Range<usize>> =
recv.redacted().clone().iter_ranges().collect();
html! { html! {
<div class="p-4 flex flex-col justify-center items-center w-full"> <div class="p-4 flex flex-col justify-center items-center w-full">
@@ -106,11 +111,12 @@ pub fn ViewFile(props: &Props) -> Html {
</div> </div>
</div> </div>
<RedactedBytesComponent direction={Direction::Send} bytes={bytes_send} /> <RedactedBytesComponent direction={Direction::Send} redacted_char={REDACTED_CHAR} bytes={sent.data().to_vec()} redacted_ranges={redacted_ranges_send} />
<ContentIFrame bytes={bytes_received.clone()} /> <ContentIFrame bytes={bytes_recv.clone()} />
<RedactedBytesComponent direction={Direction::Received} redacted_char={REDACTED_CHAR} bytes={recv.data().to_vec()} redacted_ranges={redacted_ranges_recv} />
<RedactedBytesComponent direction={Direction::Received} bytes={bytes_received} />
</div> </div>
} }
} }