Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! The purpose of this macro is to allow for easy, configurable and efficient redaction of sensitive data in structs and enum variants.
//! This can be used to hide sensitive data in logs or anywhere where personal data should not be exposed or stored.
//!
//! Redaction is unicode-aware. Only alphanumeric characters are redacted. Whitespace, symbols and other characters are left as-is.
//! Redaction is unicode-aware by default. Unless opted out of, only alphanumeric characters are redacted. Whitespace, symbols and other characters are left as-is.
//!
//! # Controlling Redaction
//!
Expand All @@ -19,6 +19,7 @@
//! | **Modifier** | | **Effects** | | **Default** |
//! |--------------------------------|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|-----------------------------------------------|
//! | `#[redact(partial)]` | | If the string is long enough, a small part of the<br>beginning and end will be exposed. If the string is too short to securely expose a portion of it, it will be redacted entirely. | | Disabled. The entire string will be redacted. |
//! | `#[redact(all_utf8)]` | | Overrides the redaction behavior to redact all characters instead of just alphanumeric characters. | | Disabled. |
//! | `#[redact(with = 'X')]` | | Specifies the `char` the string will be redacted with. | | `'*'` |
//! | `#[redact(fixed = <integer>)]` | | If this modifier is present, the length and contents of<br>the string are completely ignored and the string will always<br>be redacted as a fixed number of redaction characters. | | Disabled. |
//! | `#[redact(display)]` | | Overrides the redaction behavior to use the type's [`Display`](std::fmt::Display) implementation instead of [`Debug`]. | | Disabled. |
Expand All @@ -40,7 +41,7 @@
//! ```rust
//! # use veil_macros::Redact;
//! #[derive(Redact)]
//! #[redact(all, partial, with = 'X')]
//! #[redact(all, partial, with = 'X', all_utf8)]
//! struct Foo {
//! redact_me: String,
//! also_redact_me: String,
Expand All @@ -56,10 +57,10 @@
//! # use veil_macros::Redact;
//! #[derive(Redact)]
//! struct Foo {
//! #[redact(partial, with = 'X')]
//! #[redact(partial, with = 'X', all_utf8)]
//! redact_me: String,
//!
//! #[redact(partial, with = 'X')]
//! #[redact(partial, with = 'X', all_utf8)]
//! also_redact_me: String,
//!
//! do_not_redact_me: String,
Expand Down
7 changes: 5 additions & 2 deletions src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub enum RedactionLength {

#[derive(Clone, Copy)]
pub struct RedactFlags {
/// If we should skip non-alphanumeric characters when redacting
pub skip_non_alphanumeric: bool,

/// How much of the data to redact.
pub redact_length: RedactionLength,

Expand All @@ -54,7 +57,7 @@ impl RedactFlags {
let count = to_redact.chars().filter(|char| char.is_alphanumeric()).count();
if count < Self::MIN_PARTIAL_CHARS {
for char in to_redact.chars() {
if char.is_alphanumeric() {
if char.is_alphanumeric() && self.skip_non_alphanumeric {
fmt.write_char(self.redact_char)?;
} else {
fmt.write_char(char)?;
Expand Down Expand Up @@ -87,7 +90,7 @@ impl RedactFlags {

pub(crate) fn redact_full(&self, fmt: &mut std::fmt::Formatter, to_redact: &str) -> std::fmt::Result {
for char in to_redact.chars() {
if char.is_whitespace() || !char.is_alphanumeric() {
if (char.is_whitespace() || !char.is_alphanumeric()) && self.skip_non_alphanumeric {
fmt.write_char(char)?;
} else {
fmt.write_char(self.redact_char)?;
Expand Down
21 changes: 14 additions & 7 deletions src/redactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ impl Redactor {
pub struct RedactorBuilder {
redact_char: Option<char>,
partial: bool,
skip_non_alphanumeric: bool,
}
impl RedactorBuilder {
/// Initialize a new redaction flag builder.
Expand All @@ -170,6 +171,7 @@ impl RedactorBuilder {
Self {
redact_char: None,
partial: false,
skip_non_alphanumeric: true,
}
}

Expand All @@ -191,26 +193,31 @@ impl RedactorBuilder {
self
}

/// Redact all utf8 characters, not just the alphanumeric ones.
///
/// Equivalent to `#[redact(all_utf8)]` when deriving.
#[inline(always)]
pub const fn all_utf8(mut self) -> Self {
self.skip_non_alphanumeric = false;
self
}

/// Build the redaction flags.
///
/// Returns an error if the state of the builder is invalid.
/// The error will be optimised away by the compiler if the builder is valid at compile time, so it's safe and zero-cost to use `unwrap` on the result if you are constructing this at compile time.
#[inline(always)]
pub const fn build(self) -> Result<Redactor, &'static str> {
let mut flags = RedactFlags {
let flags = RedactFlags {
redact_length: if self.partial {
RedactionLength::Partial
} else {
RedactionLength::Full
},

redact_char: '*',
skip_non_alphanumeric: self.skip_non_alphanumeric,
redact_char: if let Some(ch) = self.redact_char { ch } else { '*' },
};

if let Some(char) = self.redact_char {
flags.redact_char = char;
}

Ok(Redactor(flags))
}
}
Expand Down
9 changes: 8 additions & 1 deletion veil-macros/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,15 @@ pub struct RedactFlags {

/// The character to use for redacting. Defaults to `*`.
pub redact_char: char,

pub all_utf8: bool,
}
impl Default for RedactFlags {
fn default() -> Self {
Self {
redact_length: RedactionLength::Full,
redact_char: '*',
all_utf8: false,
}
}
}
Expand Down Expand Up @@ -138,6 +141,8 @@ impl ExtractFlags for RedactFlags {
NonZeroU8::new(int)
.ok_or_else(|| syn::Error::new_spanned(int, "fixed redacting width must be greater than zero"))
})?)
} else if meta.path.is_ident("all_utf8") {
self.all_utf8 = true;
} else {
return Ok(ParseMeta::Unrecognised);
}
Expand All @@ -149,12 +154,14 @@ impl quote::ToTokens for RedactFlags {
let Self {
redact_length,
redact_char,
all_utf8,
..
} = self;

tokens.extend(quote! {
redact_length: #redact_length,
redact_char: #redact_char
redact_char: #redact_char,
skip_non_alphanumeric: !#all_utf8,
});
}
}
Expand Down
36 changes: 36 additions & 0 deletions veil-tests/src/redaction_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub const SENSITIVE_DATA: &[&str] = &[
"039845734895",
"10 Downing Street",
"SensitiveVariant",
"password123!@#",
];

const DEBUGGY_PHRASE: &str = "Hello \"William\"!\nAnd here's the newline...";
Expand Down Expand Up @@ -280,6 +281,40 @@ fn test_derive_redactable_modifiers() {
assert_eq!(buffer, "---");
}

#[test]
fn test_derive_redactable_all_utf8() {
#[derive(Redactable)]
#[redact(all_utf8)]
struct SensitiveString(String);
impl std::fmt::Display for SensitiveString {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(fmt)
}
}

#[derive(Redactable)]
struct SensitiveStringLeaky(String);
impl std::fmt::Display for SensitiveStringLeaky {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(fmt)
}
}

let sensitive = SensitiveString(SENSITIVE_DATA[5].to_string());
let sensitive_leaky = SensitiveStringLeaky(SENSITIVE_DATA[5].to_string());

assert_eq!(sensitive.redact(), "**************");
assert_eq!(sensitive_leaky.redact(), "***********!@#");

let mut buffer = String::new();
sensitive.redact_into(&mut buffer).unwrap();
assert_eq!(buffer, "**************");

buffer.clear();
sensitive_leaky.redact_into(&mut buffer).unwrap();
assert_eq!(buffer, "***********!@#");
}

#[test]
fn test_derive_redactable_dyn() {
#[derive(Redactable)]
Expand Down Expand Up @@ -316,6 +351,7 @@ fn test_derive_redactable_dyn() {
fn test_enum_variant_names() {
#[derive(Debug)]
enum Control {
#[allow(dead_code)]
Foo(String),
Bar,
}
Expand Down