#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
#![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_duration_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] use educe::Educe;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod err;
mod flags;
mod impls;
pub use err::Error;
pub use flags::{disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed, Guard};
use std::ops::Deref;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Educe)]
#[educe(
Clone(bound),
Default(bound),
Deref,
DerefMut,
Eq(bound),
Hash(bound),
Ord(bound),
PartialEq(bound),
PartialOrd(bound)
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Sensitive<T>(T);
impl<T> Sensitive<T> {
pub fn new(value: T) -> Self {
Sensitive(value)
}
pub fn into_inner(self) -> T {
self.0
}
#[deprecated = "Use the new into_inner method instead"]
pub fn unwrap(sensitive: Sensitive<T>) -> T {
sensitive.into_inner()
}
pub fn as_ref(&self) -> Sensitive<&T> {
Sensitive(&self.0)
}
pub fn as_inner(&self) -> &T {
&self.0
}
}
pub fn sensitive<T>(value: T) -> Sensitive<T> {
Sensitive(value)
}
impl<T> From<T> for Sensitive<T> {
fn from(value: T) -> Self {
Sensitive::new(value)
}
}
macro_rules! impl_display_traits {
{ $($trait:ident),* } => {
$(
impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if flags::unsafe_logging_enabled() {
std::fmt::$trait::fmt(&self.0, f)
} else {
write!(f, "[scrubbed]")
}
}
}
impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::$trait::fmt(&*self.0, f)
}
}
)*
}
}
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct BoxSensitive<T>(Box<Sensitive<T>>);
impl<T> From<T> for BoxSensitive<T> {
fn from(t: T) -> BoxSensitive<T> {
BoxSensitive(Box::new(sensitive(t)))
}
}
impl<T> BoxSensitive<T> {
pub fn into_inner(self) -> T {
let unboxed = *self.0;
unboxed.into_inner()
}
}
impl<T> Deref for BoxSensitive<T> {
type Target = Sensitive<T>;
fn deref(&self) -> &Sensitive<T> {
&self.0
}
}
impl_display_traits! {
Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
}
pub trait Redactable: std::fmt::Display + std::fmt::Debug {
fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.display_redacted(f)
}
fn redacted(&self) -> Redacted<&Self> {
Redacted(self)
}
fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
if redact {
MaybeRedacted(either::Either::Right(Redacted(self)))
} else {
MaybeRedacted(either::Either::Left(self))
}
}
}
impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(*self).display_redacted(f)
}
}
#[derive(Educe)]
#[educe(
Clone(bound),
Default(bound),
Deref,
DerefMut,
Eq(bound),
Hash(bound),
Ord(bound),
PartialEq(bound),
PartialOrd(bound)
)]
#[derive(derive_more::From)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Redacted<T: Redactable>(T);
impl<T: Redactable> Redacted<T> {
pub fn new(value: T) -> Self {
Self(value)
}
pub fn unwrap(self) -> T {
self.0
}
pub fn as_ref(&self) -> Redacted<&T> {
Redacted(&self.0)
}
pub fn as_inner(&self) -> &T {
&self.0
}
}
impl<T: Redactable> std::fmt::Display for Redacted<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if flags::unsafe_logging_enabled() {
std::fmt::Display::fmt(&self.0, f)
} else {
self.0.display_redacted(f)
}
}
}
impl<T: Redactable> std::fmt::Debug for Redacted<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if flags::unsafe_logging_enabled() {
std::fmt::Debug::fmt(&self.0, f)
} else {
self.0.debug_redacted(f)
}
}
}
#[derive(Clone, derive_more::Display)]
pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Debug;
match &self.0 {
either::Either::Left(v) => Debug::fmt(v, f),
either::Either::Right(v) => Debug::fmt(v, f),
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use serial_test::serial;
use static_assertions::{assert_impl_all, assert_not_impl_any};
#[test]
fn clone_bound() {
#[derive(Clone)]
struct A;
struct B;
let _x = Sensitive(A).clone();
let _y = Sensitive(B);
assert_impl_all!(Sensitive<A> : Clone);
assert_not_impl_any!(Sensitive<B> : Clone);
}
#[test]
#[serial]
fn debug_vec() {
type SVec = Sensitive<Vec<u32>>;
let mut sv = SVec::default();
assert!(sv.is_empty());
sv.push(104);
sv.push(49);
assert_eq!(sv.len(), 2);
assert!(!flags::unsafe_logging_enabled());
assert_eq!(format!("{:?}", &sv), "[scrubbed]");
assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
assert_eq!(normal, "[104, 49]");
let _g = disable_safe_logging().unwrap();
assert_eq!(format!("{:?}", &sv), "[104, 49]");
assert_eq!(sv, SVec::from(vec![104, 49]));
assert_eq!(sv.clone().into_inner(), vec![104, 49]);
assert_eq!(*sv, vec![104, 49]);
}
#[test]
#[serial]
#[allow(deprecated)]
fn deprecated() {
type SVec = Sensitive<Vec<u32>>;
let sv = Sensitive(vec![104, 49]);
assert_eq!(SVec::unwrap(sv), vec![104, 49]);
}
#[test]
#[serial]
fn display_various() {
let val = Sensitive::<u32>::new(0x0ed19a);
let closure1 = || {
format!(
"{:?}, {}, {:o}, {:x}, {:X}, {:b}",
&val, &val, &val, &val, &val, &val,
)
};
let s1 = closure1();
let s2 = with_safe_logging_suppressed(closure1);
assert_eq!(
s1,
"[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
);
assert_eq!(
s2,
"971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
);
let n = 1.0E32;
let val = Sensitive::<f64>::new(n);
let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
let s1 = closure2();
let s2 = with_safe_logging_suppressed(closure2);
assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
assert_eq!(s2, expect);
let ptr: *const u8 = std::ptr::null();
let val = Sensitive::new(ptr);
let expect = format!("{:?}, {:p}", ptr, ptr);
let closure3 = || format!("{:?}, {:p}", val, val);
let s1 = closure3();
let s2 = with_safe_logging_suppressed(closure3);
assert_eq!(s1, "[scrubbed], [scrubbed]");
assert_eq!(s2, expect);
}
#[test]
#[serial]
fn box_sensitive() {
let b: BoxSensitive<_> = "hello world".into();
assert_eq!(b.clone().into_inner(), "hello world");
let closure = || format!("{} {:?}", b, b);
assert_eq!(closure(), "[scrubbed] [scrubbed]");
assert_eq!(
with_safe_logging_suppressed(closure),
r#"hello world "hello world""#
);
assert_eq!(b.len(), 11);
}
#[test]
#[serial]
fn test_redacted() {
let localhost = std::net::Ipv4Addr::LOCALHOST;
let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
assert_eq!(closure(), "127.x.x.x 127.x.x.x");
assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
let closure = |b| {
format!(
"{} {:?}",
localhost.maybe_redacted(b),
localhost.maybe_redacted(b)
)
};
assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
assert_eq!(Redacted::new(localhost).unwrap(), localhost);
}
}