22
33import com .easypost .easyvcr .internalutilities .Tools ;
44import com .easypost .easyvcr .internalutilities .json .Serialization ;
5- import com .google .gson .JsonSyntaxException ;
5+ import com .google .gson .JsonParseException ;
66import org .apache .http .NameValuePair ;
77import org .apache .http .client .utils .URLEncodedUtils ;
88
@@ -22,6 +22,10 @@ public final class Censors {
2222 * The body parameters to censor.
2323 */
2424 private final List <String > bodyParamsToCensor ;
25+ /**
26+ * Whether censor keys are case sensitive.
27+ */
28+ private final boolean caseSensitive ;
2529 /**
2630 * The string to replace censored data with.
2731 */
@@ -48,10 +52,21 @@ public Censors() {
4852 * @param censorString The string to use to censor sensitive information.
4953 */
5054 public Censors (String censorString ) {
51- queryParamsToCensor = new ArrayList <>();
52- bodyParamsToCensor = new ArrayList <>();
53- headersToCensor = new ArrayList <>();
54- censorText = censorString ;
55+ this (censorString , false );
56+ }
57+
58+ /**
59+ * Initialize a new instance of the Censors factory.
60+ *
61+ * @param censorString The string to use to censor sensitive information.
62+ * @param caseSensitive Whether to use case sensitive censoring.
63+ */
64+ public Censors (String censorString , boolean caseSensitive ) {
65+ this .queryParamsToCensor = new ArrayList <>();
66+ this .bodyParamsToCensor = new ArrayList <>();
67+ this .headersToCensor = new ArrayList <>();
68+ this .censorText = censorString ;
69+ this .caseSensitive = caseSensitive ;
5570 }
5671
5772 /**
@@ -70,48 +85,44 @@ public static Censors regular() {
7085 */
7186 public static Censors strict () {
7287 Censors censors = new Censors ();
73- for (String key : Statics .DEFAULT_CREDENTIAL_HEADERS_TO_HIDE ) {
74- censors .hideHeader (key );
75- }
76- for (String key : Statics .DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE ) {
77- censors .hideQueryParameter (key );
78- censors .hideBodyParameter (key );
79- }
88+ censors .hideHeaders (Statics .DEFAULT_CREDENTIAL_HEADERS_TO_HIDE );
89+ censors .hideBodyParameters (Statics .DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE );
90+ censors .hideQueryParameters (Statics .DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE );
8091 return censors ;
8192 }
8293
8394 /**
84- * Add a rule to censor a specified body parameter .
95+ * Add a rule to censor specified body parameters .
8596 * Note: Only top-level pairs can be censored.
8697 *
87- * @param parameterKey Key of body parameter to censor.
98+ * @param parameterKeys Keys of body parameters to censor.
8899 * @return This Censors factory.
89100 */
90- public Censors hideBodyParameter ( String parameterKey ) {
91- bodyParamsToCensor .add ( parameterKey );
101+ public Censors hideBodyParameters ( List < String > parameterKeys ) {
102+ bodyParamsToCensor .addAll ( parameterKeys );
92103 return this ;
93104 }
94105
95106 /**
96- * Add a rule to censor a specified header key .
97- * Note: This will censor the header key in both the request and response.
107+ * Add a rule to censor specified header keys .
108+ * Note: This will censor the header keys in both the request and response.
98109 *
99- * @param headerKey Key of header to censor.
110+ * @param headerKeys Keys of headers to censor.
100111 * @return This Censors factory.
101112 */
102- public Censors hideHeader ( String headerKey ) {
103- headersToCensor .add ( headerKey );
113+ public Censors hideHeaders ( List < String > headerKeys ) {
114+ headersToCensor .addAll ( headerKeys );
104115 return this ;
105116 }
106117
107118 /**
108- * Add a rule to censor a specified query parameter .
119+ * Add a rule to censor specified query parameters .
109120 *
110- * @param parameterKey Key of query parameter to censor.
121+ * @param parameterKeys Keys of query parameters to censor.
111122 * @return This Censors factory.
112123 */
113- public Censors hideQueryParameter ( String parameterKey ) {
114- queryParamsToCensor .add ( parameterKey );
124+ public Censors hideQueryParameters ( List < String > parameterKeys ) {
125+ queryParamsToCensor .addAll ( parameterKeys );
115126 return this ;
116127 }
117128
@@ -121,32 +132,19 @@ public Censors hideQueryParameter(String parameterKey) {
121132 * @param body String representation of request body to apply censors to.
122133 * @return Censored string representation of request body.
123134 */
124- public String applyBodyParametersCensors (String body ) {
135+ public String censorBodyParameters (String body ) {
125136 if (body == null || body .length () == 0 ) {
126137 // short circuit if body is null or empty
127138 return body ;
128139 }
129140
130- Map <String , Object > bodyParameters ;
131- try {
132- bodyParameters = Serialization .convertJsonToObject (body , Map .class );
133- } catch (JsonSyntaxException ignored ) {
134- // short circuit if body is not a JSON dictionary
135- return body ;
136- }
137-
138- if (bodyParameters == null || bodyParameters .size () == 0 ) {
139- // short circuit if there are no body parameters
141+ if (bodyParamsToCensor .size () == 0 ) {
142+ // short circuit if there are no censors to apply
140143 return body ;
141144 }
142145
143- for (String parameterKey : bodyParamsToCensor ) {
144- if (bodyParameters .containsKey (parameterKey )) {
145- bodyParameters .put (parameterKey , censorText );
146- }
147- }
148-
149- return Serialization .convertObjectToJson (bodyParameters );
146+ // TODO: Future different content type support here, only JSON is supported currently
147+ return censorJsonBodyParameters (body );
150148 }
151149
152150 /**
@@ -155,12 +153,17 @@ public String applyBodyParametersCensors(String body) {
155153 * @param headers Map of headers to apply censors to.
156154 * @return Censored map of headers.
157155 */
158- public Map <String , List <String >> applyHeadersCensors (Map <String , List <String >> headers ) {
156+ public Map <String , List <String >> censorHeaders (Map <String , List <String >> headers ) {
159157 if (headers == null || headers .size () == 0 ) {
160158 // short circuit if there are no headers to censor
161159 return headers ;
162160 }
163161
162+ if (headersToCensor .size () == 0 ) {
163+ // short circuit if there are no censors to apply
164+ return headers ;
165+ }
166+
164167 final Map <String , List <String >> headersCopy = new HashMap <>(headers );
165168
166169 for (String headerKey : headersToCensor ) {
@@ -177,11 +180,17 @@ public Map<String, List<String>> applyHeadersCensors(Map<String, List<String>> h
177180 * @param url Full URL string to apply censors to.
178181 * @return Censored URL string.
179182 */
180- public String applyQueryParametersCensors (String url ) {
183+ public String censorQueryParameters (String url ) {
181184 if (url == null || url .length () == 0 ) {
182185 // short circuit if url is null
183186 return url ;
184187 }
188+
189+ if (queryParamsToCensor .size () == 0 ) {
190+ // short circuit if there are no censors to apply
191+ return url ;
192+ }
193+
185194 URI uri = URI .create (url );
186195 Map <String , String > queryParameters = Tools .queryParametersToMap (uri );
187196 if (queryParameters .size () == 0 ) {
@@ -204,4 +213,118 @@ public String applyQueryParametersCensors(String url) {
204213
205214 return uri .getScheme () + "://" + uri .getHost () + uri .getPath () + "?" + formattedQueryParameters ;
206215 }
216+
217+ private List <Object > applyBodyCensors (List <Object > list ) {
218+ if (list == null || list .size () == 0 ) {
219+ // short circuit if list is null or empty
220+ return list ;
221+ }
222+
223+ List <Object > censoredList = new ArrayList <>();
224+
225+ for (Object object : list ) {
226+ Object value = object ;
227+ if (Utilities .isDictionary (value )) {
228+ // recursively censor inner dictionaries
229+ try {
230+ // change the value if can be parsed as a dictionary
231+ value = applyBodyCensors ((Map <String , Object >) value );
232+ } catch (ClassCastException e ) {
233+ // otherwise, skip censoring
234+ }
235+ } else if (Utilities .isList (value )) {
236+ // recursively censor list elements
237+ try {
238+ // change the value if can be parsed as a list
239+ value = applyBodyCensors ((List <Object >) value );
240+ } catch (ClassCastException e ) {
241+ // otherwise, skip censoring
242+ }
243+ } // either a primitive or null, no censoring needed
244+
245+ censoredList .add (value );
246+ }
247+
248+ return censoredList ;
249+
250+ }
251+
252+ private Map <String , Object > applyBodyCensors (Map <String , Object > dictionary ) {
253+ if (dictionary == null || dictionary .size () == 0 ) {
254+ // short circuit if dictionary is null or empty
255+ return dictionary ;
256+ }
257+
258+ Map <String , Object > censoredBodyDictionary = new HashMap <>();
259+
260+ for (Map .Entry <String , Object > entry : dictionary .entrySet ()) {
261+ String key = entry .getKey ();
262+ Object value = entry .getValue ();
263+ if (keyShouldBeCensored (key , this .bodyParamsToCensor )) {
264+ if (value == null ) {
265+ // don't need to worry about censoring something that's null
266+ // (don't replace null with the censor string)
267+ continue ;
268+ } else if (Utilities .isDictionary (value )) {
269+ // replace with empty dictionary
270+ censoredBodyDictionary .put (key , new HashMap <>());
271+ } else if (Utilities .isList (value )) {
272+ // replace with empty array
273+ censoredBodyDictionary .put (key , new ArrayList <>());
274+ } else {
275+ // replace with censor text
276+ censoredBodyDictionary .put (key , this .censorText );
277+ }
278+ } else {
279+ if (Utilities .isDictionary (value )) {
280+ // recursively censor inner dictionaries
281+ try {
282+ // change the value if can be parsed as a dictionary
283+ value = applyBodyCensors ((Map <String , Object >) value );
284+ } catch (ClassCastException e ) {
285+ // otherwise, skip censoring
286+ }
287+ } else if (Utilities .isList (value )) {
288+ // recursively censor list elements
289+ try {
290+ // change the value if can be parsed as a list
291+ value = applyBodyCensors ((List <Object >) value );
292+ } catch (ClassCastException e ) {
293+ // otherwise, skip censoring
294+ }
295+ }
296+
297+ censoredBodyDictionary .put (key , value );
298+ }
299+ }
300+
301+ return censoredBodyDictionary ;
302+ }
303+
304+ private String censorJsonBodyParameters (String body ) {
305+ Map <String , Object > bodyDictionary ;
306+ try {
307+ bodyDictionary = Serialization .convertJsonToObject (body , Map .class );
308+ Map <String , Object > censoredBodyDictionary = applyBodyCensors (bodyDictionary );
309+ return censoredBodyDictionary == null ? body : Serialization .convertObjectToJson (censoredBodyDictionary );
310+ } catch (Exception ignored ) {
311+ // body is not a JSON dictionary
312+ try {
313+ List <Object > bodyList = Serialization .convertJsonToObject (body , List .class );
314+ List <Object > censoredBodyList = applyBodyCensors (bodyList );
315+ return censoredBodyList == null ? body : Serialization .convertObjectToJson (censoredBodyList );
316+ } catch (Exception notJsonData ) {
317+ throw new JsonParseException ("Body is not a JSON dictionary or list" );
318+ }
319+ }
320+ }
321+
322+ private boolean keyShouldBeCensored (String foundKey , List <String > keysToCensor ) {
323+ // keysToCensor are already cased as needed
324+ if (!this .caseSensitive ) {
325+ foundKey = foundKey .toLowerCase ();
326+ }
327+
328+ return keysToCensor .contains (foundKey );
329+ }
207330}
0 commit comments