1
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![warn(clippy::cognitive_complexity)]
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_duration_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46

            
47
// TODO: Try making it not Deref and having expose+expose_mut instead; how bad is it?
48

            
49
use educe::Educe;
50
#[cfg(feature = "serde")]
51
use serde::{Deserialize, Serialize};
52

            
53
mod err;
54
mod flags;
55
mod impls;
56

            
57
pub use err::Error;
58
pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
59

            
60
use std::ops::Deref;
61

            
62
/// A `Result` returned by the flag-manipulation functions in `safelog`.
63
pub type Result<T> = std::result::Result<T, Error>;
64

            
65
// Re-exported for macros.
66
#[doc(hidden)]
67
pub use flags::unsafe_logging_enabled;
68

            
69
/// A wrapper type for a sensitive value.
70
///
71
/// By default, a `Sensitive<T>` behaves the same as a regular `T`, except that
72
/// attempts to turn it into a string (via `Display`, `Debug`, etc) all produce
73
/// the string `[scrubbed]`.
74
///
75
/// This behavior can be overridden locally by using
76
/// [`with_safe_logging_suppressed`] and globally with [`disable_safe_logging`].
77
144
#[derive(Educe, Clone, Copy)]
78
#[educe(
79
    Default(bound),
80
    Deref,
81
    DerefMut,
82
    Eq(bound),
83
    Hash(bound),
84
    Ord(bound),
85
    PartialEq(bound),
86
    PartialOrd(bound)
87
)]
88
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89
#[cfg_attr(feature = "serde", serde(transparent))]
90
pub struct Sensitive<T>(T);
91

            
92
impl<T> Sensitive<T> {
93
    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
94
68948
    pub fn new(value: T) -> Self {
95
68948
        Sensitive(value)
96
68948
    }
97

            
98
    /// Extract the inner value from this `Sensitive<T>`.
99
1478
    pub fn into_inner(self) -> T {
100
1478
        self.0
101
1478
    }
102

            
103
    /// Extract the inner value from this `Sensitive<T>`.
104
    #[deprecated = "Use the new into_inner method instead"]
105
2
    pub fn unwrap(sensitive: Sensitive<T>) -> T {
106
2
        sensitive.into_inner()
107
2
    }
108

            
109
    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
110
2
    pub fn as_ref(&self) -> Sensitive<&T> {
111
2
        Sensitive(&self.0)
112
2
    }
113

            
114
    /// Return a reference to the inner value
115
    //
116
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
117
    // via what is usually a semantically-neutral interface.
118
18
    pub fn as_inner(&self) -> &T {
119
18
        &self.0
120
18
    }
121
}
122

            
123
/// Wrap a value as `Sensitive`.
124
///
125
/// This function is an alias for [`Sensitive::new`].
126
20
pub fn sensitive<T>(value: T) -> Sensitive<T> {
127
20
    Sensitive(value)
128
20
}
129

            
130
impl<T> From<T> for Sensitive<T> {
131
68942
    fn from(value: T) -> Self {
132
68942
        Sensitive::new(value)
133
68942
    }
134
}
135

            
136
/// Helper: Declare one or more Display-like implementations for a
137
/// Sensitive-like type.  These implementations will delegate to their std::fmt
138
/// types if safe logging is disabled, and write `[scrubbed]` otherwise.
139
macro_rules! impl_display_traits {
140
    { $($trait:ident),* } => {
141
    $(
142
        impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
143
80
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144
80
                if flags::unsafe_logging_enabled() {
145
32
                    std::fmt::$trait::fmt(&self.0, f)
146
                } else {
147
48
                    write!(f, "[scrubbed]")
148
                }
149
80
            }
150
        }
151

            
152
        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
153
            #[inline]
154
8
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155
8
                std::fmt::$trait::fmt(&*self.0, f)
156
8
            }
157
        }
158
   )*
159
   }
160
}
161

            
162
/// A wrapper suitable for logging and including in errors
163
///
164
/// This is a newtype around `Box<Sensitive<T>>`.
165
///
166
/// This is useful particularly in errors,
167
/// where the box can help reduce the size of error variants
168
/// (for example ones containing large values like an `OwnedChanTarget`).
169
///
170
/// `BoxSensitive<T>` dereferences to [`Sensitive<T>`].
171
//
172
// Making it be a newtype rather than a type alias allows us to implement
173
// `into_inner` and `From<T>` and so on.
174
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
175
pub struct BoxSensitive<T>(Box<Sensitive<T>>);
176

            
177
impl<T> From<T> for BoxSensitive<T> {
178
2
    fn from(t: T) -> BoxSensitive<T> {
179
2
        BoxSensitive(Box::new(sensitive(t)))
180
2
    }
181
}
182

            
183
impl<T> BoxSensitive<T> {
184
    /// Return the innermost `T`
185
2
    pub fn into_inner(self) -> T {
186
2
        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
187
2
        let unboxed = *self.0;
188
2
        unboxed.into_inner()
189
2
    }
190
}
191

            
192
impl<T> Deref for BoxSensitive<T> {
193
    type Target = Sensitive<T>;
194

            
195
2
    fn deref(&self) -> &Sensitive<T> {
196
2
        &self.0
197
2
    }
198
}
199

            
200
impl_display_traits! {
201
    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
202
}
203

            
204
/// A `redactable` object is one where we know a way to display _part_ of it
205
/// when we are running with safe logging enabled.
206
///
207
/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
208
/// this trait would allow referring to the user as `S[...]`.
209
///
210
/// # Privacy notes
211
///
212
/// Displaying some information about an object is always less safe than
213
/// displaying no information about it!
214
///
215
/// For example, in an environment with only a small number of users, the first
216
/// letter of a user's name might be plenty of information to identify them
217
/// uniquely.
218
///
219
/// Even if a piece of redacted information is safe on its own, several pieces
220
/// of redacted information, when taken together, can be enough for an adversary
221
/// to infer more than you want.  For example, if you log somebody's first
222
/// initial, month of birth, and last-two-digits of ID number, you have just
223
/// discarded 99.9% of potential individuals from the attacker's consideration.
224
pub trait Redactable: std::fmt::Display + std::fmt::Debug {
225
    /// As `Display::fmt`, but produce a redacted representation.
226
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
227
    /// As `Debug::fmt`, but produce a redacted representation.
228
4
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229
4
        self.display_redacted(f)
230
4
    }
231
    /// Return a smart pointer that will display or debug this object as its
232
    /// redacted form.
233
38
    fn redacted(&self) -> Redacted<&Self> {
234
38
        Redacted(self)
235
38
    }
236
    /// Return a smart pointer that redacts this object if `redact` is true.
237
3832
    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
238
3832
        if redact {
239
6
            MaybeRedacted(either::Either::Right(Redacted(self)))
240
        } else {
241
3826
            MaybeRedacted(either::Either::Left(self))
242
        }
243
3832
    }
244
}
245

            
246
impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
247
109
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248
109
        (*self).display_redacted(f)
249
109
    }
250
}
251

            
252
/// A wrapper around a `Redactable` that displays it in redacted format.
253
#[derive(Educe, Clone, Copy)]
254
#[educe(
255
    Default(bound),
256
    Deref,
257
    DerefMut,
258
    Eq(bound),
259
    Hash(bound),
260
    Ord(bound),
261
    PartialEq(bound),
262
    PartialOrd(bound)
263
)]
264
#[derive(derive_more::From)]
265
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266
#[cfg_attr(feature = "serde", serde(transparent))]
267
pub struct Redacted<T: Redactable>(T);
268

            
269
impl<T: Redactable> Redacted<T> {
270
    /// Create a new `Redacted`.
271
2
    pub fn new(value: T) -> Self {
272
2
        Self(value)
273
2
    }
274

            
275
    /// Consume this wrapper and return its inner value.
276
2
    pub fn unwrap(self) -> T {
277
2
        self.0
278
2
    }
279

            
280
    /// Converts `&Redacted<T>` to `Redacted<&T>`
281
    pub fn as_ref(&self) -> Redacted<&T> {
282
        Redacted(&self.0)
283
    }
284

            
285
    /// Return a reference to the inner value
286
    //
287
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
288
    // via what is usually a semantically-neutral interface.
289
    pub fn as_inner(&self) -> &T {
290
        &self.0
291
    }
292
}
293

            
294
impl<T: Redactable> std::fmt::Display for Redacted<T> {
295
38
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296
38
        if flags::unsafe_logging_enabled() {
297
2
            std::fmt::Display::fmt(&self.0, f)
298
        } else {
299
36
            self.0.display_redacted(f)
300
        }
301
38
    }
302
}
303

            
304
impl<T: Redactable> std::fmt::Debug for Redacted<T> {
305
6
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306
6
        if flags::unsafe_logging_enabled() {
307
2
            std::fmt::Debug::fmt(&self.0, f)
308
        } else {
309
4
            self.0.debug_redacted(f)
310
        }
311
6
    }
312
}
313

            
314
/// An object that may or may not be redacted.
315
///
316
/// Used to implement conditional redaction
317
#[derive(Clone, derive_more::Display)]
318
pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
319

            
320
impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
321
4
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322
        use std::fmt::Debug;
323
4
        match &self.0 {
324
2
            either::Either::Left(v) => Debug::fmt(v, f),
325
2
            either::Either::Right(v) => Debug::fmt(v, f),
326
        }
327
4
    }
328
}
329

            
330
/// A type that can be displayed in a redacted or un-redacted form,
331
/// but which forces the caller to choose.
332
///
333
/// See [`Redactable`] for more discussion on redaction.
334
///
335
/// Unlike [`Redactable`], this type is "inherently sensitive":
336
/// Types implementing `DisplayRedacted` should not typically implement
337
/// [`Display`](std::fmt::Display).
338
///
339
/// For external types that implement `Display`,
340
/// or for types which are usually _not_ sensitive,
341
/// `Redacted` is likely a better choice.
342
pub trait DisplayRedacted {
343
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
344
    /// in its redacted form.
345
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
346
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
347
    /// in its un-redacted form.
348
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
349

            
350
    // TODO: At some point in the future, when default values are supported for GATs,
351
    // it might be good to turn these RPIT functions into associated types.
352

            
353
    /// Return a pointer wrapping this object that can be Displayed in redacted form
354
    /// if safe-logging is enabled.
355
    ///
356
    /// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
357
1660
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
358
1660
        DispRedacted(self)
359
1660
    }
360
    /// Return a pointer wrapping this object that can be Displayed in unredacted form.
361
3078
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
362
3078
        DispUnredacted(self)
363
3078
    }
364
}
365

            
366
impl<'a, T> DisplayRedacted for &'a T
367
where
368
    T: DisplayRedacted + ?Sized,
369
{
370
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
371
        (*self).display_redacted()
372
    }
373
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
374
        (*self).display_unredacted()
375
    }
376
1660
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377
1660
        (*self).fmt_redacted(f)
378
1660
    }
379
3082
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380
3082
        (*self).fmt_unredacted(f)
381
3082
    }
382
}
383

            
384
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
385
/// by displaying the object in its redacted form
386
/// if safe-logging is enabled.
387
///
388
/// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
389
#[allow(clippy::exhaustive_structs)]
390
#[derive(derive_more::AsRef)]
391
pub struct DispRedacted<T: ?Sized>(pub T);
392

            
393
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
394
/// by displaying the object in its un-redacted form.
395
#[allow(clippy::exhaustive_structs)]
396
#[derive(derive_more::AsRef)]
397
pub struct DispUnredacted<T: ?Sized>(pub T);
398

            
399
impl<T> std::fmt::Display for DispRedacted<T>
400
where
401
    T: DisplayRedacted + ?Sized,
402
{
403
1668
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404
1668
        if crate::flags::unsafe_logging_enabled() {
405
2
            self.0.fmt_unredacted(f)
406
        } else {
407
1666
            self.0.fmt_redacted(f)
408
        }
409
1668
    }
410
}
411

            
412
impl<T> std::fmt::Display for DispUnredacted<T>
413
where
414
    T: DisplayRedacted + ?Sized,
415
{
416
3080
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417
3080
        self.0.fmt_unredacted(f)
418
3080
    }
419
}
420

            
421
/// A type that can be debugged in a redacted or un-redacted form,
422
/// but which forces the caller to choose.
423
///
424
/// See [`Redactable`] for more discussion on redaction.
425
///
426
/// Unlike [`Redactable`], this type is "inherently sensitive":
427
/// [`Debug`](std::fmt::Debug) will display it in redacted or un-redacted format
428
/// depending on whether safe logging is enabled.
429
///
430
/// For external types that implement `Debug`,
431
/// or for types which are usually _not_ sensitive,
432
/// `Redacted` is likely a better choice.
433
pub trait DebugRedacted {
434
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
435
    /// in its redacted form.
436
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
437
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
438
    /// in its unredacted form.
439
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
440
}
441

            
442
/// Implement [`std::fmt::Debug`] for a type that implements [`DebugRedacted`].
443
///
444
/// The implementation will use fmt_redacted() when safe-logging is enabled,
445
/// and fmt_unredacted() otherwise.
446
///
447
/// (NOTE we can't just write 'impl<T:DebugRedacted> Debug for T`;
448
/// Rust doesn't like it.)
449
#[macro_export]
450
macro_rules! derive_redacted_debug {
451
    {$t:ty} => {
452
    impl std::fmt::Debug for $t {
453
1540
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454
1540
            if $crate::unsafe_logging_enabled() {
455
4
                $crate::DebugRedacted::fmt_unredacted(self, f)
456
            } else {
457
1536
                $crate::DebugRedacted::fmt_redacted(self, f)
458
            }
459
1540
        }
460
    }
461
}}
462

            
463
#[cfg(test)]
464
mod test {
465
    // @@ begin test lint list maintained by maint/add_warning @@
466
    #![allow(clippy::bool_assert_comparison)]
467
    #![allow(clippy::clone_on_copy)]
468
    #![allow(clippy::dbg_macro)]
469
    #![allow(clippy::mixed_attributes_style)]
470
    #![allow(clippy::print_stderr)]
471
    #![allow(clippy::print_stdout)]
472
    #![allow(clippy::single_char_pattern)]
473
    #![allow(clippy::unwrap_used)]
474
    #![allow(clippy::unchecked_duration_subtraction)]
475
    #![allow(clippy::useless_vec)]
476
    #![allow(clippy::needless_pass_by_value)]
477
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
478

            
479
    use super::*;
480
    use serial_test::serial;
481
    use static_assertions::{assert_impl_all, assert_not_impl_any};
482

            
483
    #[test]
484
    fn clone_bound() {
485
        // Here we'll make sure that educe bounds work about the way we expect.
486
        #[derive(Clone)]
487
        struct A;
488
        struct B;
489

            
490
        let _x = Sensitive(A).clone();
491
        let _y = Sensitive(B);
492

            
493
        assert_impl_all!(Sensitive<A> : Clone);
494
        assert_not_impl_any!(Sensitive<B> : Clone);
495
    }
496

            
497
    #[test]
498
    #[serial]
499
    fn debug_vec() {
500
        type SVec = Sensitive<Vec<u32>>;
501

            
502
        let mut sv = SVec::default();
503
        assert!(sv.is_empty());
504
        sv.push(104);
505
        sv.push(49);
506
        assert_eq!(sv.len(), 2);
507

            
508
        assert!(!flags::unsafe_logging_enabled());
509
        assert_eq!(format!("{:?}", &sv), "[scrubbed]");
510
        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
511
        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
512
        let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
513
        assert_eq!(normal, "[104, 49]");
514

            
515
        let _g = disable_safe_logging().unwrap();
516
        assert_eq!(format!("{:?}", &sv), "[104, 49]");
517

            
518
        assert_eq!(sv, SVec::from(vec![104, 49]));
519
        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
520
        assert_eq!(*sv, vec![104, 49]);
521
    }
522

            
523
    #[test]
524
    #[serial]
525
    #[allow(deprecated)]
526
    fn deprecated() {
527
        type SVec = Sensitive<Vec<u32>>;
528
        let sv = Sensitive(vec![104, 49]);
529

            
530
        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
531
    }
532

            
533
    #[test]
534
    #[serial]
535
    fn display_various() {
536
        let val = Sensitive::<u32>::new(0x0ed19a);
537

            
538
        let closure1 = || {
539
            format!(
540
                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
541
                &val, &val, &val, &val, &val, &val,
542
            )
543
        };
544
        let s1 = closure1();
545
        let s2 = with_safe_logging_suppressed(closure1);
546
        assert_eq!(
547
            s1,
548
            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
549
        );
550
        assert_eq!(
551
            s2,
552
            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
553
        );
554

            
555
        let n = 1.0E32;
556
        let val = Sensitive::<f64>::new(n);
557
        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
558
        let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
559
        let s1 = closure2();
560
        let s2 = with_safe_logging_suppressed(closure2);
561
        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
562
        assert_eq!(s2, expect);
563

            
564
        let ptr: *const u8 = std::ptr::null();
565
        let val = Sensitive::new(ptr);
566
        let expect = format!("{:?}, {:p}", ptr, ptr);
567
        let closure3 = || format!("{:?}, {:p}", val, val);
568
        let s1 = closure3();
569
        let s2 = with_safe_logging_suppressed(closure3);
570
        assert_eq!(s1, "[scrubbed], [scrubbed]");
571
        assert_eq!(s2, expect);
572
    }
573

            
574
    #[test]
575
    #[serial]
576
    fn box_sensitive() {
577
        let b: BoxSensitive<_> = "hello world".into();
578

            
579
        assert_eq!(b.clone().into_inner(), "hello world");
580

            
581
        let closure = || format!("{} {:?}", b, b);
582
        assert_eq!(closure(), "[scrubbed] [scrubbed]");
583
        assert_eq!(
584
            with_safe_logging_suppressed(closure),
585
            r#"hello world "hello world""#
586
        );
587

            
588
        assert_eq!(b.len(), 11);
589
    }
590

            
591
    #[test]
592
    #[serial]
593
    fn test_redacted() {
594
        let localhost = std::net::Ipv4Addr::LOCALHOST;
595
        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
596

            
597
        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
598
        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
599

            
600
        let closure = |b| {
601
            format!(
602
                "{} {:?}",
603
                localhost.maybe_redacted(b),
604
                localhost.maybe_redacted(b)
605
            )
606
        };
607
        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
608
        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
609

            
610
        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
611
    }
612

            
613
    struct RedactionCheck(u32);
614
    impl DisplayRedacted for RedactionCheck {
615
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616
            write!(f, "{}", self.0)
617
        }
618

            
619
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620
            let v = self.0.to_string();
621
            write!(f, "{}xxx", v.chars().next().unwrap())
622
        }
623
    }
624
    impl DebugRedacted for RedactionCheck {
625
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626
            write!(f, "Num({})", self.display_redacted())
627
        }
628

            
629
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630
            write!(f, "Num({})", self.display_unredacted())
631
        }
632
    }
633
    derive_redacted_debug!(RedactionCheck);
634

            
635
    #[test]
636
    #[serial]
637
    fn display_redacted() {
638
        let n = RedactionCheck(999);
639
        assert_eq!(&n.display_unredacted().to_string(), "999");
640
        assert_eq!(&n.display_redacted().to_string(), "9xxx");
641
        with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
642

            
643
        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
644
        assert_eq!(DispUnredacted(&n).to_string(), "999");
645

            
646
        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
647
        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
648
    }
649
}