1
#![cfg_attr(docsrs, feature(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
#![allow(clippy::cognitive_complexity)] // See arti#2556
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
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

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

            
54
use educe::Educe;
55
#[cfg(feature = "serde")]
56
use serde::{Deserialize, Serialize};
57

            
58
mod err;
59
mod flags;
60
mod impls;
61
pub mod util;
62

            
63
pub use err::Error;
64
pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
65

            
66
use std::ops::Deref;
67

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

            
71
// Re-exported for macros.
72
#[doc(hidden)]
73
pub use flags::unsafe_logging_enabled;
74

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

            
98
impl<T> Sensitive<T> {
99
    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
100
70701
    pub fn new(value: T) -> Self {
101
70701
        Sensitive(value)
102
70701
    }
103

            
104
    /// Extract the inner value from this `Sensitive<T>`.
105
765
    pub fn into_inner(self) -> T {
106
765
        self.0
107
765
    }
108

            
109
    /// Extract the inner value from this `Sensitive<T>`.
110
    #[deprecated = "Use the new into_inner method instead"]
111
1
    pub fn unwrap(sensitive: Sensitive<T>) -> T {
112
1
        sensitive.into_inner()
113
1
    }
114

            
115
    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
116
1
    pub fn as_ref(&self) -> Sensitive<&T> {
117
1
        Sensitive(&self.0)
118
1
    }
119

            
120
    /// Return a reference to the inner value
121
    //
122
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
123
    // via what is usually a semantically-neutral interface.
124
160
    pub fn as_inner(&self) -> &T {
125
160
        &self.0
126
160
    }
127
}
128

            
129
/// Wrap a value as `Sensitive`.
130
///
131
/// This function is an alias for [`Sensitive::new`].
132
14
pub fn sensitive<T>(value: T) -> Sensitive<T> {
133
14
    Sensitive(value)
134
14
}
135

            
136
impl<T> From<T> for Sensitive<T> {
137
70693
    fn from(value: T) -> Self {
138
70693
        Sensitive::new(value)
139
70693
    }
140
}
141

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

            
158
        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
159
            #[inline]
160
4
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161
4
                std::fmt::$trait::fmt(&*self.0, f)
162
4
            }
163
        }
164
   )*
165
   }
166
}
167

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

            
183
impl<T> From<T> for BoxSensitive<T> {
184
1
    fn from(t: T) -> BoxSensitive<T> {
185
1
        BoxSensitive(Box::new(sensitive(t)))
186
1
    }
187
}
188

            
189
impl<T> BoxSensitive<T> {
190
    /// Return the innermost `T`
191
1
    pub fn into_inner(self) -> T {
192
        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
193
1
        let unboxed = *self.0;
194
1
        unboxed.into_inner()
195
1
    }
196
}
197

            
198
impl<T> Deref for BoxSensitive<T> {
199
    type Target = Sensitive<T>;
200

            
201
1
    fn deref(&self) -> &Sensitive<T> {
202
1
        &self.0
203
1
    }
204
}
205

            
206
impl_display_traits! {
207
    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
208
}
209

            
210
/// An object that may or may not be sensitive.
211
///
212
/// See [`Sensitive`] for the guarantees it provides for the sensitive case.
213
#[derive(Clone, derive_more::Display)]
214
pub struct MaybeSensitive<T>(either::Either<T, Sensitive<T>>);
215

            
216
impl<T> MaybeSensitive<T> {
217
    /// Build a sensitive container.
218
2
    pub fn sensitive(t: T) -> Self {
219
2
        Self(either::Either::Right(Sensitive::new(t)))
220
2
    }
221

            
222
    /// Build a non sensitive container.
223
317
    pub fn not_sensitive(t: T) -> Self {
224
317
        Self(either::Either::Left(t))
225
317
    }
226

            
227
    /// Return the innermost `T`
228
1
    pub fn inner(self) -> T {
229
1
        match self.0 {
230
            either::Either::Left(t) => t,
231
1
            either::Either::Right(s) => s.into_inner(),
232
        }
233
1
    }
234

            
235
    /// Map a `MaybeSensitive<T>` to a `MaybeSensitive<U>`
236
    /// by applying the supplied function `f` to the inner `T`
237
284
    pub fn map<U, F>(self, f: F) -> MaybeSensitive<U>
238
284
    where
239
284
        F: FnOnce(T) -> U,
240
    {
241
284
        match self.0 {
242
283
            either::Either::Left(t) => MaybeSensitive(either::Either::Left(f(t))),
243
1
            either::Either::Right(s) => {
244
1
                let new_inner = f(s.into_inner());
245
1
                MaybeSensitive(either::Either::Right(Sensitive::new(new_inner)))
246
            }
247
        }
248
284
    }
249
}
250

            
251
impl<T: std::fmt::Debug> std::fmt::Debug for MaybeSensitive<T> {
252
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253
        use std::fmt::Debug;
254
        match &self.0 {
255
            either::Either::Left(v) => Debug::fmt(v, f),
256
            either::Either::Right(v) => Debug::fmt(v, f),
257
        }
258
    }
259
}
260

            
261
impl<T> Deref for MaybeSensitive<T> {
262
    type Target = T;
263

            
264
186
    fn deref(&self) -> &T {
265
186
        match &self.0 {
266
35
            either::Either::Left(t) => t,
267
151
            either::Either::Right(s) => s.as_inner(),
268
        }
269
186
    }
270
}
271

            
272
/// A `redactable` object is one where we know a way to display _part_ of it
273
/// when we are running with safe logging enabled.
274
///
275
/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
276
/// this trait would allow referring to the user as `S[...]`.
277
///
278
/// # Privacy notes
279
///
280
/// Displaying some information about an object is always less safe than
281
/// displaying no information about it!
282
///
283
/// For example, in an environment with only a small number of users, the first
284
/// letter of a user's name might be plenty of information to identify them
285
/// uniquely.
286
///
287
/// Even if a piece of redacted information is safe on its own, several pieces
288
/// of redacted information, when taken together, can be enough for an adversary
289
/// to infer more than you want.  For example, if you log somebody's first
290
/// initial, month of birth, and last-two-digits of ID number, you have just
291
/// discarded 99.9% of potential individuals from the attacker's consideration.
292
pub trait Redactable: std::fmt::Display + std::fmt::Debug {
293
    /// As `Display::fmt`, but produce a redacted representation.
294
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
295
    /// As `Debug::fmt`, but produce a redacted representation.
296
2
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297
2
        self.display_redacted(f)
298
2
    }
299
    /// Return a smart pointer that will display or debug this object as its
300
    /// redacted form.
301
19
    fn redacted(&self) -> Redacted<&Self> {
302
19
        Redacted(self)
303
19
    }
304
    /// Return a smart pointer that redacts this object if `redact` is true.
305
60044
    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
306
60044
        if redact {
307
3
            MaybeRedacted(either::Either::Right(Redacted(self)))
308
        } else {
309
60041
            MaybeRedacted(either::Either::Left(self))
310
        }
311
60044
    }
312
}
313

            
314
impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
315
109
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316
109
        (*self).display_redacted(f)
317
109
    }
318

            
319
2
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320
2
        (*self).debug_redacted(f)
321
2
    }
322

            
323
    // The default implementations for redacted() and maybe_redacted() are okay unconditionally.
324
}
325

            
326
/// A wrapper around a `Redactable` that displays it in redacted format.
327
#[derive(Educe, Clone, Copy)]
328
#[educe(
329
    Default(bound),
330
    Deref,
331
    DerefMut,
332
    Eq(bound),
333
    Hash(bound),
334
    Ord(bound),
335
    PartialEq(bound),
336
    PartialOrd(bound)
337
)]
338
#[derive(derive_more::From)]
339
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
340
#[cfg_attr(feature = "serde", serde(transparent))]
341
pub struct Redacted<T: Redactable>(T);
342

            
343
impl<T: Redactable> Redacted<T> {
344
    /// Create a new `Redacted`.
345
1
    pub fn new(value: T) -> Self {
346
1
        Self(value)
347
1
    }
348

            
349
    /// Consume this wrapper and return its inner value.
350
1
    pub fn unwrap(self) -> T {
351
1
        self.0
352
1
    }
353

            
354
    /// Converts `&Redacted<T>` to `Redacted<&T>`
355
    pub fn as_ref(&self) -> Redacted<&T> {
356
        Redacted(&self.0)
357
    }
358

            
359
    /// Return a reference to the inner value
360
    //
361
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
362
    // via what is usually a semantically-neutral interface.
363
435
    pub fn as_inner(&self) -> &T {
364
435
        &self.0
365
435
    }
366
}
367

            
368
impl<T: Redactable> std::fmt::Display for Redacted<T> {
369
19
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370
19
        if flags::unsafe_logging_enabled() {
371
1
            std::fmt::Display::fmt(&self.0, f)
372
        } else {
373
18
            self.0.display_redacted(f)
374
        }
375
19
    }
376
}
377

            
378
impl<T: Redactable> std::fmt::Debug for Redacted<T> {
379
3
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380
3
        if flags::unsafe_logging_enabled() {
381
1
            std::fmt::Debug::fmt(&self.0, f)
382
        } else {
383
2
            self.0.debug_redacted(f)
384
        }
385
3
    }
386
}
387

            
388
/// An object that may or may not be redacted.
389
///
390
/// Used to implement conditional redaction
391
#[derive(Clone, derive_more::Display)]
392
pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
393

            
394
impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
395
2
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396
        use std::fmt::Debug;
397
2
        match &self.0 {
398
1
            either::Either::Left(v) => Debug::fmt(v, f),
399
1
            either::Either::Right(v) => Debug::fmt(v, f),
400
        }
401
2
    }
402
}
403

            
404
/// A type that can be displayed in a redacted or un-redacted form,
405
/// but which forces the caller to choose.
406
///
407
/// See [`Redactable`] for more discussion on redaction.
408
///
409
/// Unlike [`Redactable`], this type is "inherently sensitive":
410
/// Types implementing `DisplayRedacted` should not typically implement
411
/// [`Display`](std::fmt::Display).
412
///
413
/// For external types that implement `Display`,
414
/// or for types which are usually _not_ sensitive,
415
/// `Redacted` is likely a better choice.
416
pub trait DisplayRedacted {
417
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
418
    /// in its redacted form.
419
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
420
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
421
    /// in its un-redacted form.
422
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
423

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

            
427
    /// Return a pointer wrapping this object that can be Displayed in redacted form
428
    /// if safe-logging is enabled.
429
    ///
430
    /// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
431
2116
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
432
2116
        DispRedacted(self)
433
2116
    }
434
    /// Return a pointer wrapping this object that can be Displayed in unredacted form.
435
54125
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
436
54125
        DispUnredacted(self)
437
54125
    }
438
}
439

            
440
impl<'a, T> DisplayRedacted for &'a T
441
where
442
    T: DisplayRedacted + ?Sized,
443
{
444
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
445
        (*self).display_redacted()
446
    }
447
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
448
        (*self).display_unredacted()
449
    }
450
2116
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451
2116
        (*self).fmt_redacted(f)
452
2116
    }
453
54127
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454
54127
        (*self).fmt_unredacted(f)
455
54127
    }
456
}
457

            
458
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
459
/// by displaying the object in its redacted form
460
/// if safe-logging is enabled.
461
///
462
/// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
463
#[allow(clippy::exhaustive_structs)]
464
#[derive(derive_more::AsRef)]
465
pub struct DispRedacted<T: ?Sized>(pub T);
466

            
467
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
468
/// by displaying the object in its un-redacted form.
469
#[allow(clippy::exhaustive_structs)]
470
#[derive(derive_more::AsRef)]
471
pub struct DispUnredacted<T: ?Sized>(pub T);
472

            
473
impl<T> std::fmt::Display for DispRedacted<T>
474
where
475
    T: DisplayRedacted + ?Sized,
476
{
477
2841
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478
2841
        if crate::flags::unsafe_logging_enabled() {
479
1
            self.0.fmt_unredacted(f)
480
        } else {
481
2840
            self.0.fmt_redacted(f)
482
        }
483
2841
    }
484
}
485

            
486
impl<T> std::fmt::Display for DispUnredacted<T>
487
where
488
    T: DisplayRedacted + ?Sized,
489
{
490
54126
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491
54126
        self.0.fmt_unredacted(f)
492
54126
    }
493
}
494

            
495
/// A type that can be debugged in a redacted or un-redacted form,
496
/// but which forces the caller to choose.
497
///
498
/// See [`Redactable`] for more discussion on redaction.
499
///
500
/// Unlike [`Redactable`], this type is "inherently sensitive":
501
/// [`Debug`](std::fmt::Debug) will display it in redacted or un-redacted format
502
/// depending on whether safe logging is enabled.
503
///
504
/// For external types that implement `Debug`,
505
/// or for types which are usually _not_ sensitive,
506
/// `Redacted` is likely a better choice.
507
pub trait DebugRedacted {
508
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
509
    /// in its redacted form.
510
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
511
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
512
    /// in its unredacted form.
513
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
514
}
515

            
516
/// Implement [`std::fmt::Debug`] for a type that implements [`DebugRedacted`].
517
///
518
/// The implementation will use fmt_redacted() when safe-logging is enabled,
519
/// and fmt_unredacted() otherwise.
520
///
521
/// (NOTE we can't just write 'impl<T:DebugRedacted> Debug for T`;
522
/// Rust doesn't like it.)
523
#[macro_export]
524
macro_rules! derive_redacted_debug {
525
    {$t:ty} => {
526
    impl std::fmt::Debug for $t {
527
1719
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528
1719
            if $crate::unsafe_logging_enabled() {
529
2
                $crate::DebugRedacted::fmt_unredacted(self, f)
530
            } else {
531
1717
                $crate::DebugRedacted::fmt_redacted(self, f)
532
            }
533
1719
        }
534
    }
535
}}
536

            
537
#[cfg(test)]
538
mod test {
539
    // @@ begin test lint list maintained by maint/add_warning @@
540
    #![allow(clippy::bool_assert_comparison)]
541
    #![allow(clippy::clone_on_copy)]
542
    #![allow(clippy::dbg_macro)]
543
    #![allow(clippy::mixed_attributes_style)]
544
    #![allow(clippy::print_stderr)]
545
    #![allow(clippy::print_stdout)]
546
    #![allow(clippy::single_char_pattern)]
547
    #![allow(clippy::unwrap_used)]
548
    #![allow(clippy::unchecked_time_subtraction)]
549
    #![allow(clippy::useless_vec)]
550
    #![allow(clippy::needless_pass_by_value)]
551
    #![allow(clippy::string_slice)] // See arti#2571
552
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
553

            
554
    use super::*;
555
    use serial_test::serial;
556
    use static_assertions::{assert_impl_all, assert_not_impl_any};
557

            
558
    #[test]
559
    fn clone_bound() {
560
        // Here we'll make sure that educe bounds work about the way we expect.
561
        #[derive(Clone)]
562
        struct A;
563
        struct B;
564

            
565
        let _x = Sensitive(A).clone();
566
        let _y = Sensitive(B);
567

            
568
        assert_impl_all!(Sensitive<A> : Clone);
569
        assert_not_impl_any!(Sensitive<B> : Clone);
570
    }
571

            
572
    #[test]
573
    #[serial]
574
    fn debug_vec() {
575
        type SVec = Sensitive<Vec<u32>>;
576

            
577
        let mut sv = SVec::default();
578
        assert!(sv.is_empty());
579
        sv.push(104);
580
        sv.push(49);
581
        assert_eq!(sv.len(), 2);
582

            
583
        assert!(!flags::unsafe_logging_enabled());
584
        assert_eq!(format!("{:?}", sv), "[scrubbed]");
585
        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
586
        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
587
        let normal = with_safe_logging_suppressed(|| format!("{:?}", sv));
588
        assert_eq!(normal, "[104, 49]");
589

            
590
        let _g = disable_safe_logging().unwrap();
591
        assert_eq!(format!("{:?}", sv), "[104, 49]");
592

            
593
        assert_eq!(sv, SVec::from(vec![104, 49]));
594
        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
595
        assert_eq!(*sv, vec![104, 49]);
596
    }
597

            
598
    #[test]
599
    #[serial]
600
    #[allow(deprecated)]
601
    fn deprecated() {
602
        type SVec = Sensitive<Vec<u32>>;
603
        let sv = Sensitive(vec![104, 49]);
604

            
605
        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
606
    }
607

            
608
    #[test]
609
    #[serial]
610
    fn display_various() {
611
        let val = Sensitive::<u32>::new(0x0ed19a);
612

            
613
        let closure1 = || {
614
            format!(
615
                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
616
                val, val, val, val, val, val,
617
            )
618
        };
619
        let s1 = closure1();
620
        let s2 = with_safe_logging_suppressed(closure1);
621
        assert_eq!(
622
            s1,
623
            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
624
        );
625
        assert_eq!(
626
            s2,
627
            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
628
        );
629

            
630
        let n = 1.0E32;
631
        let val = Sensitive::<f64>::new(n);
632
        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
633
        let closure2 = || format!("{:?}, {}, {:e}, {:E}", val, val, val, val);
634
        let s1 = closure2();
635
        let s2 = with_safe_logging_suppressed(closure2);
636
        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
637
        assert_eq!(s2, expect);
638

            
639
        let ptr: *const u8 = std::ptr::null();
640
        let val = Sensitive::new(ptr);
641
        let expect = format!("{:?}, {:p}", ptr, ptr);
642
        let closure3 = || format!("{:?}, {:p}", val, val);
643
        let s1 = closure3();
644
        let s2 = with_safe_logging_suppressed(closure3);
645
        assert_eq!(s1, "[scrubbed], [scrubbed]");
646
        assert_eq!(s2, expect);
647
    }
648

            
649
    #[test]
650
    #[serial]
651
    fn box_sensitive() {
652
        let b: BoxSensitive<_> = "hello world".into();
653

            
654
        assert_eq!(b.clone().into_inner(), "hello world");
655

            
656
        let closure = || format!("{} {:?}", b, b);
657
        assert_eq!(closure(), "[scrubbed] [scrubbed]");
658
        assert_eq!(
659
            with_safe_logging_suppressed(closure),
660
            r#"hello world "hello world""#
661
        );
662

            
663
        assert_eq!(b.len(), 11);
664
    }
665

            
666
    #[test]
667
    #[serial]
668
    fn test_redacted() {
669
        let localhost = std::net::Ipv4Addr::LOCALHOST;
670
        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
671

            
672
        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
673
        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
674

            
675
        let closure = |b| {
676
            format!(
677
                "{} {:?}",
678
                localhost.maybe_redacted(b),
679
                localhost.maybe_redacted(b)
680
            )
681
        };
682
        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
683
        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
684

            
685
        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
686
    }
687

            
688
    struct RedactionCheck(u32);
689
    impl DisplayRedacted for RedactionCheck {
690
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
691
            write!(f, "{}", self.0)
692
        }
693

            
694
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695
            let v = self.0.to_string();
696
            write!(f, "{}xxx", v.chars().next().unwrap())
697
        }
698
    }
699
    impl DebugRedacted for RedactionCheck {
700
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
701
            write!(f, "Num({})", self.display_redacted())
702
        }
703

            
704
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
705
            write!(f, "Num({})", self.display_unredacted())
706
        }
707
    }
708
    derive_redacted_debug!(RedactionCheck);
709

            
710
    #[test]
711
    #[serial]
712
    fn display_redacted() {
713
        let n = RedactionCheck(999);
714
        assert_eq!(&n.display_unredacted().to_string(), "999");
715
        assert_eq!(&n.display_redacted().to_string(), "9xxx");
716
        with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
717

            
718
        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
719
        assert_eq!(DispUnredacted(&n).to_string(), "999");
720

            
721
        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
722
        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
723
    }
724
}