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"
wasm-logger = "0.2.0"
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" }
[dependencies.tlsn-core]

View File

@@ -1,10 +1,8 @@
use std::fmt;
use std::{fmt, ops::Range};
use gloo::console::log;
use yew::prelude::*;
pub const REDACTED_CHAR: char = '█';
// pub const REDACTED_CHAR: char = '🙈';
#[derive(Clone, PartialEq)]
pub enum Direction {
Send,
@@ -23,116 +21,91 @@ impl fmt::Display for Direction {
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
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 split_text(text: String) -> Vec<String> {
let (mut parts, last_part) = text.chars().fold(
(Vec::new(), String::new()),
|(mut acc, mut current_part), c| {
let previous_c = current_part.chars().last().unwrap_or(REDACTED_CHAR);
if (c == REDACTED_CHAR) == (previous_c == REDACTED_CHAR) {
current_part.push(c);
} else {
if !current_part.is_empty() {
acc.push(current_part.clone());
}
current_part.clear();
current_part.push(c);
}
(acc, current_part)
fn get_redacted_string(redacted_char: &char, size: usize) -> String {
if true || size < 10 {
redacted_char.to_string().repeat(size)
} else {
format! {"{}...{}", redacted_char.to_string().repeat(3), redacted_char.to_string().repeat(3)}
}
}
fn redactions_in_red(
bytes: &Vec<u8>,
redacted_ranges: &Vec<Range<usize>>,
redacted_char: &char,
) -> Html {
if redacted_ranges.is_empty() {
return Html::from(String::from_utf8(bytes.to_vec()).unwrap());
}
// 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 {
let parts = split_text(text);
//color redacted parts in red
let html: Html = parts
.iter()
.map(|part| match part {
x if x.starts_with(REDACTED_CHAR) => Html::from_html_unchecked(AttrValue::from(
format!("<span style=\"color:red;\">{}</span>", x),
)),
_ => Html::from(part),
// interweave the redacted and non-redacted ranges
let all_ranges = non_redacted_ranges
.into_iter()
.zip(redacted_ranges.iter())
.flat_map(|(non_redacted, redacted)| {
vec![
(non_redacted.start, non_redacted.end, false),
(redacted.start, redacted.end, true),
]
})
.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]
pub fn RedactedBytesComponent(props: &Props) -> Html {
let Props { direction, bytes } = props;
let redacted_transcript = bytes.replace('\0', REDACTED_CHAR.to_string().as_str());
let Props {
direction,
redacted_char,
bytes,
redacted_ranges,
} = props;
html! {
<details class="p-4 w-5/6" open={true}>
<summary><b>{"Bytes "}{direction}{": " }</b></summary>
<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>
</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;
use std::ops::Range;
use std::str;
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::RedactedBytesComponent;
const REDACTED_CHAR: char = '█'; // 'X'. '🙈';
#[derive(Properties, PartialEq)]
pub struct Props {
pub name: String,
@@ -85,9 +88,11 @@ pub fn ViewFile(props: &Props) -> Html {
sent.set_redacted(b'\0');
recv.set_redacted(b'\0');
let bytes_send = String::from_utf8(sent.data().to_vec()).unwrap();
let bytes_received = String::from_utf8(recv.data().to_vec()).unwrap();
let redacted_ranges_send: Vec<Range<usize>> =
sent.redacted().clone().iter_ranges().collect();
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! {
<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>
<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>
}
}