@@ -125,7 +125,83 @@ pub fn error_for_enoent() -> String {
125
125
/// invocation to invocation (e.g., assigned TCP port numbers, timestamps)
126
126
///
127
127
/// This allows use to use expectorate to verify the shape of the CLI output.
128
- pub fn redact_variable ( input : & str ) -> String {
128
+ #[ derive( Clone , Debug ) ]
129
+ pub struct Redactor < ' a > {
130
+ basic : bool ,
131
+ uuids : bool ,
132
+ extra : Vec < ( & ' a str , String ) > ,
133
+ }
134
+
135
+ impl Default for Redactor < ' _ > {
136
+ fn default ( ) -> Self {
137
+ Self { basic : true , uuids : true , extra : Vec :: new ( ) }
138
+ }
139
+ }
140
+
141
+ impl < ' a > Redactor < ' a > {
142
+ /// Create a new redactor that does not do any redactions.
143
+ pub fn noop ( ) -> Self {
144
+ Self { basic : false , uuids : false , extra : Vec :: new ( ) }
145
+ }
146
+
147
+ pub fn basic ( & mut self , basic : bool ) -> & mut Self {
148
+ self . basic = basic;
149
+ self
150
+ }
151
+
152
+ pub fn uuids ( & mut self , uuids : bool ) -> & mut Self {
153
+ self . uuids = uuids;
154
+ self
155
+ }
156
+
157
+ pub fn extra_fixed_length (
158
+ & mut self ,
159
+ name : & str ,
160
+ text_to_redact : & ' a str ,
161
+ ) -> & mut Self {
162
+ // Use the same number of chars as the number of bytes in
163
+ // text_to_redact. We're almost entirely in ASCII-land so they're the
164
+ // same, and getting the length right is nice but doesn't matter for
165
+ // correctness.
166
+ //
167
+ // A technically more correct impl would use unicode-width, but ehhh.
168
+ let replacement = fill_redaction_text ( name, text_to_redact. len ( ) ) ;
169
+ self . extra . push ( ( text_to_redact, replacement) ) ;
170
+ self
171
+ }
172
+
173
+ pub fn extra_variable_length (
174
+ & mut self ,
175
+ name : & str ,
176
+ text_to_redact : & ' a str ,
177
+ ) -> & mut Self {
178
+ let replacement = format ! ( "<{}_REDACTED>" , name. to_uppercase( ) ) ;
179
+ self . extra . push ( ( text_to_redact, replacement) ) ;
180
+ self
181
+ }
182
+
183
+ pub fn do_redact ( & self , input : & str ) -> String {
184
+ // Perform extra redactions at the beginning, not the end. This is because
185
+ // some of the built-in redactions in redact_variable might match a
186
+ // substring of something that should be handled by extra_redactions (e.g.
187
+ // a temporary path).
188
+ let mut s = input. to_owned ( ) ;
189
+ for ( name, replacement) in & self . extra {
190
+ s = s. replace ( name, replacement) ;
191
+ }
192
+
193
+ if self . basic {
194
+ s = redact_basic ( & s) ;
195
+ }
196
+ if self . uuids {
197
+ s = redact_uuids ( & s) ;
198
+ }
199
+
200
+ s
201
+ }
202
+ }
203
+
204
+ fn redact_basic ( input : & str ) -> String {
129
205
// Replace TCP port numbers. We include the localhost
130
206
// characters to avoid catching any random sequence of numbers.
131
207
let s = regex:: Regex :: new ( r"\[::1\]:\d{4,5}" )
@@ -141,19 +217,6 @@ pub fn redact_variable(input: &str) -> String {
141
217
. replace_all ( & s, "127.0.0.1:REDACTED_PORT" )
142
218
. to_string ( ) ;
143
219
144
- // Replace uuids.
145
- //
146
- // The length of a UUID is 32 nibbles for the hex encoding of a u128 + 4
147
- // dashes = 36.
148
- const UUID_LEN : usize = 36 ;
149
- let s = regex:: Regex :: new (
150
- "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-\
151
- [a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}",
152
- )
153
- . unwrap ( )
154
- . replace_all ( & s, fill_redaction_text ( "uuid" , UUID_LEN ) )
155
- . to_string ( ) ;
156
-
157
220
// Replace timestamps.
158
221
//
159
222
// Format: RFC 3339 (ISO 8601)
@@ -213,63 +276,14 @@ pub fn redact_variable(input: &str) -> String {
213
276
s
214
277
}
215
278
216
- /// Redact text from a string, allowing for extra redactions to be specified.
217
- pub fn redact_extra (
218
- input : & str ,
219
- extra_redactions : & ExtraRedactions < ' _ > ,
220
- ) -> String {
221
- // Perform extra redactions at the beginning, not the end. This is because
222
- // some of the built-in redactions in redact_variable might match a
223
- // substring of something that should be handled by extra_redactions (e.g.
224
- // a temporary path).
225
- let mut s = input. to_owned ( ) ;
226
- for ( name, replacement) in & extra_redactions. redactions {
227
- s = s. replace ( name, replacement) ;
228
- }
229
- redact_variable ( & s)
230
- }
231
-
232
- /// Represents a list of extra redactions for [`redact_variable`].
233
- ///
234
- /// Extra redactions are applied in-order, before any builtin redactions.
235
- #[ derive( Clone , Debug , Default ) ]
236
- pub struct ExtraRedactions < ' a > {
237
- // A pair of redaction and replacement strings.
238
- redactions : Vec < ( & ' a str , String ) > ,
239
- }
240
-
241
- impl < ' a > ExtraRedactions < ' a > {
242
- pub fn new ( ) -> Self {
243
- Self { redactions : Vec :: new ( ) }
244
- }
245
-
246
- pub fn fixed_length (
247
- & mut self ,
248
- name : & str ,
249
- text_to_redact : & ' a str ,
250
- ) -> & mut Self {
251
- // Use the same number of chars as the number of bytes in
252
- // text_to_redact. We're almost entirely in ASCII-land so they're the
253
- // same, and getting the length right is nice but doesn't matter for
254
- // correctness.
255
- //
256
- // A technically more correct impl would use unicode-width, but ehhh.
257
- let replacement = fill_redaction_text ( name, text_to_redact. len ( ) ) ;
258
- self . redactions . push ( ( text_to_redact, replacement) ) ;
259
- self
260
- }
261
-
262
- pub fn variable_length (
263
- & mut self ,
264
- name : & str ,
265
- text_to_redact : & ' a str ,
266
- ) -> & mut Self {
267
- let gen = format ! ( "<{}_REDACTED>" , name. to_uppercase( ) ) ;
268
- let replacement = gen. to_string ( ) ;
269
-
270
- self . redactions . push ( ( text_to_redact, replacement) ) ;
271
- self
272
- }
279
+ fn redact_uuids ( input : & str ) -> String {
280
+ // The length of a UUID is 32 nibbles for the hex encoding of a u128 + 4
281
+ // dashes = 36.
282
+ const UUID_LEN : usize = 36 ;
283
+ regex:: Regex :: new ( r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" )
284
+ . unwrap ( )
285
+ . replace_all ( & input, fill_redaction_text ( "uuid" , UUID_LEN ) )
286
+ . to_string ( )
273
287
}
274
288
275
289
fn fill_redaction_text ( name : & str , text_to_redact_len : usize ) -> String {
@@ -309,13 +323,11 @@ mod tests {
309
323
let input = "time: 123ms, path: /var/tmp/tmp.456ms123s, \
310
324
path2: /short, \
311
325
path3: /variable-length/path";
312
- let actual = redact_extra (
313
- input,
314
- ExtraRedactions :: new ( )
315
- . fixed_length ( "tp" , "/var/tmp/tmp.456ms123s" )
316
- . fixed_length ( "short_redact" , "/short" )
317
- . variable_length ( "variable" , "/variable-length/path" ) ,
318
- ) ;
326
+ let actual = Redactor :: default ( )
327
+ . extra_fixed_length ( "tp" , "/var/tmp/tmp.456ms123s" )
328
+ . extra_fixed_length ( "short_redact" , "/short" )
329
+ . extra_variable_length ( "variable" , "/variable-length/path" )
330
+ . do_redact ( input) ;
319
331
assert_eq ! (
320
332
actual,
321
333
"time: <REDACTED DURATION>ms, path: ....<REDACTED_TP>....., \
@@ -347,7 +359,7 @@ mod tests {
347
359
for time in times {
348
360
let input = format ! ( "{:?}" , time) ;
349
361
assert_eq ! (
350
- redact_variable ( & input) ,
362
+ Redactor :: default ( ) . do_redact ( & input) ,
351
363
"<REDACTED_TIMESTAMP>" ,
352
364
"Failed to redact {:?}" ,
353
365
time
0 commit comments