use std::sync::atomic::{AtomicU64, Ordering};
use crate::preprocessing::SecretLineRange;
pub(super) struct SecretCallEntry {
pub(super) sentinel: String,
pub(super) reference: String,
pub(super) value: String,
}
static RENDER_COUNTER: AtomicU64 = AtomicU64::new(1);
pub(super) fn next_render_id() -> u64 {
RENDER_COUNTER.fetch_add(1, Ordering::Relaxed)
}
pub(super) fn make_secret_sentinel(render_id: u64, call_idx: usize) -> String {
let mut s = String::with_capacity(20);
s.push('\u{E000}');
s.push_str("DSEC.");
s.push_str(&render_id.to_string());
s.push('.');
s.push_str(&call_idx.to_string());
s.push('\u{E001}');
s
}
pub(super) fn finalize_secrets(
rendered: String,
tracked: String,
entries: &[SecretCallEntry],
) -> (String, String, Vec<SecretLineRange>) {
let mut ranges = Vec::with_capacity(entries.len());
if !entries.is_empty() {
let line_starts = build_line_starts(&rendered);
for entry in entries {
if let Some(byte_off) = rendered.find(entry.sentinel.as_str()) {
let line = byte_offset_to_line(&line_starts, byte_off);
ranges.push(SecretLineRange {
start: line,
end: line + 1,
reference: entry.reference.clone(),
});
}
}
}
let mut final_rendered = rendered;
let mut final_tracked = tracked;
for entry in entries {
final_rendered = final_rendered.replace(entry.sentinel.as_str(), &entry.value);
final_tracked = final_tracked.replace(entry.sentinel.as_str(), &entry.value);
}
(final_rendered, final_tracked, ranges)
}
fn build_line_starts(s: &str) -> Vec<usize> {
let mut v = Vec::with_capacity(s.len() / 32 + 1);
v.push(0);
for (i, b) in s.bytes().enumerate() {
if b == b'\n' {
v.push(i + 1);
}
}
v
}
fn byte_offset_to_line(line_starts: &[usize], offset: usize) -> usize {
match line_starts.binary_search(&offset) {
Ok(line) => line,
Err(insert_pos) => insert_pos.saturating_sub(1),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(idx: usize, reference: &str, value: &str) -> (SecretCallEntry, String) {
let sentinel = make_secret_sentinel(0, idx);
let entry = SecretCallEntry {
sentinel: sentinel.clone(),
reference: reference.to_string(),
value: value.to_string(),
};
(entry, sentinel)
}
#[test]
fn finalize_secrets_substitutes_sentinels_and_records_line_ranges() {
let (e, sentinel) = entry(0, "pass:k", "hunter2");
let rendered = format!("header\nuser = alice\npassword = {sentinel}\nfooter\n");
let (final_rendered, _, ranges) = finalize_secrets(rendered, String::new(), &[e]);
assert_eq!(ranges.len(), 1);
assert_eq!((ranges[0].start, ranges[0].end), (2, 3));
assert_eq!(ranges[0].reference, "pass:k");
assert_eq!(
final_rendered,
"header\nuser = alice\npassword = hunter2\nfooter\n"
);
assert!(!final_rendered.contains('\u{E000}'));
}
#[test]
fn finalize_secrets_does_not_match_value_substring_outside_sentinel() {
let (e, sentinel) = entry(0, "pass:k", "hunter2");
let rendered = format!("greeting = hunter2 hi\npassword = {sentinel}\n");
let (final_rendered, _, ranges) = finalize_secrets(rendered, String::new(), &[e]);
assert_eq!(ranges.len(), 1);
assert_eq!((ranges[0].start, ranges[0].end), (1, 2));
assert_eq!(
final_rendered,
"greeting = hunter2 hi\npassword = hunter2\n"
);
}
#[test]
fn finalize_secrets_handles_two_calls_resolving_to_same_value() {
let (e1, s1) = entry(0, "pass:a", "shared");
let (e2, s2) = entry(1, "pass:b", "shared");
let rendered = format!("a = {s1}\nb = {s2}\n");
let (final_rendered, _, ranges) = finalize_secrets(rendered, String::new(), &[e1, e2]);
assert_eq!(ranges.len(), 2);
assert_eq!((ranges[0].start, ranges[0].end), (0, 1));
assert_eq!((ranges[1].start, ranges[1].end), (1, 2));
assert_eq!(final_rendered, "a = shared\nb = shared\n");
}
#[test]
fn finalize_secrets_drops_entries_whose_sentinel_was_not_emitted() {
let (e, _sentinel) = entry(0, "pass:hidden", "never-emitted");
let rendered = "clean output\n".to_string();
let (final_rendered, _, ranges) = finalize_secrets(rendered, String::new(), &[e]);
assert!(ranges.is_empty());
assert_eq!(final_rendered, "clean output\n");
}
#[test]
fn finalize_secrets_substitutes_sentinels_in_tracked_render_too() {
let (e, sentinel) = entry(0, "pass:k", "hunter2");
let tracked = format!("preamble {sentinel} epilogue");
let (_, final_tracked, _) = finalize_secrets(String::new(), tracked, &[e]);
assert_eq!(final_tracked, "preamble hunter2 epilogue");
}
}