19
19
*/
20
20
package org .zaproxy .addon .exim .sites ;
21
21
22
+ import com .fasterxml .jackson .annotation .JsonInclude ;
23
+ import com .fasterxml .jackson .databind .ObjectMapper ;
24
+ import com .fasterxml .jackson .databind .SerializationFeature ;
25
+ import com .fasterxml .jackson .dataformat .yaml .YAMLFactory ;
26
+ import com .fasterxml .jackson .dataformat .yaml .YAMLGenerator ;
22
27
import java .io .BufferedWriter ;
23
28
import java .io .File ;
24
29
import java .io .FileInputStream ;
25
30
import java .io .FileWriter ;
26
31
import java .io .IOException ;
27
32
import java .io .InputStream ;
28
33
import java .io .Writer ;
29
- import java .nio .charset .StandardCharsets ;
30
34
import java .util .ArrayList ;
31
- import java .util .Base64 ;
32
- import java .util .Collection ;
33
35
import java .util .LinkedHashMap ;
34
36
import java .util .List ;
35
- import java .util .Map ;
36
37
import org .apache .commons .httpclient .URI ;
37
38
import org .apache .commons .httpclient .URIException ;
38
39
import org .apache .logging .log4j .LogManager ;
48
49
import org .parosproxy .paros .network .HttpMalformedHeaderException ;
49
50
import org .parosproxy .paros .network .HttpMessage ;
50
51
import org .parosproxy .paros .network .HttpRequestHeader ;
51
- import org .yaml .snakeyaml .DumperOptions ;
52
52
import org .yaml .snakeyaml .LoaderOptions ;
53
53
import org .yaml .snakeyaml .Yaml ;
54
- import org .yaml .snakeyaml .representer .Representer ;
55
54
import org .zaproxy .addon .exim .ExporterResult ;
56
55
import org .zaproxy .addon .exim .ExtensionExim ;
57
56
import org .zaproxy .zap .model .NameValuePair ;
@@ -61,27 +60,27 @@ public class SitesTreeHandler {
61
60
62
61
private static final Logger LOGGER = LogManager .getLogger (SitesTreeHandler .class );
63
62
64
- private static final Yaml YAML ;
63
+ private static final ObjectMapper YAML_MAPPER ;
64
+ private static final Yaml YAML_PARSER ;
65
65
66
66
static {
67
- // YAML is used for encoding with improved configuration
68
- DumperOptions options = new DumperOptions ();
69
- options .setDefaultFlowStyle (DumperOptions .FlowStyle .BLOCK );
70
- options .setPrettyFlow (true );
71
- options .setIndent (2 );
72
- options .setIndicatorIndent (0 );
73
- options .setWidth (Integer .MAX_VALUE ); // Prevent wrapping
74
- options .setAllowUnicode (true ); // Better Unicode handling
75
- options .setNonPrintableStyle (
76
- DumperOptions .NonPrintableStyle .ESCAPE ); // Escape problematic chars
77
-
78
- Representer representer = new Representer (options );
79
- representer .setDefaultScalarStyle (DumperOptions .ScalarStyle .DOUBLE_QUOTED );
80
-
81
- // For handling special chars
82
- representer .getPropertyUtils ().setSkipMissingProperties (true );
83
-
84
- YAML = new Yaml (representer , options );
67
+ // Configure YAML mapper with appropriate settings
68
+ YAML_MAPPER =
69
+ new ObjectMapper (
70
+ new YAMLFactory ()
71
+ .disable (YAMLGenerator .Feature .WRITE_DOC_START_MARKER )
72
+ .enable (YAMLGenerator .Feature .MINIMIZE_QUOTES )
73
+ .enable (YAMLGenerator .Feature .LITERAL_BLOCK_STYLE )
74
+ .configure (YAMLGenerator .Feature .SPLIT_LINES , false )
75
+ .configure (
76
+ YAMLGenerator .Feature .ALWAYS_QUOTE_NUMBERS_AS_STRINGS ,
77
+ false ));
78
+
79
+ YAML_MAPPER .setSerializationInclusion (JsonInclude .Include .NON_NULL );
80
+ YAML_MAPPER .configure (SerializationFeature .INDENT_OUTPUT , true );
81
+
82
+ // Use snake yaml only for parsing
83
+ YAML_PARSER = new Yaml (new LoaderOptions ());
85
84
}
86
85
87
86
public static void exportSitesTree (File file , ExporterResult result ) throws IOException {
@@ -113,9 +112,14 @@ private static void outputKV(
113
112
fw .write (key );
114
113
fw .write (": " );
115
114
116
- // Convert value to YAML and handle formatting
117
- Object sanitizedValue = sanitizeForYaml (value );
118
- String yamlValue = YAML .dump (sanitizedValue ).trim ();
115
+ // Let Jackson handle the YAML formatting
116
+ if (value == null ) {
117
+ fw .write ("null" );
118
+ fw .newLine ();
119
+ return ;
120
+ }
121
+
122
+ String yamlValue = YAML_MAPPER .writeValueAsString (value ).trim ();
119
123
120
124
// For simple single-line values
121
125
if (!yamlValue .contains ("\n " )) {
@@ -272,86 +276,15 @@ public static PruneSiteResult pruneSiteNodes(File file) {
272
276
protected static PruneSiteResult pruneSiteNodes (InputStream is , SiteMap siteMap ) {
273
277
PruneSiteResult res = new PruneSiteResult ();
274
278
// Don't load yaml using the Constructor class - that throws exceptions that
275
- // don't give
276
- // enough info
277
- Yaml yaml = new Yaml (new LoaderOptions ());
278
-
279
- Object obj = yaml .load (is );
279
+ // don't give enough info
280
+ Object obj = YAML_PARSER .load (is );
280
281
if (obj instanceof ArrayList <?> list ) {
281
282
EximSiteNode rootNode = new EximSiteNode ((LinkedHashMap <?, ?>) list .get (0 ));
282
283
pruneSiteNodes (rootNode , res , siteMap );
283
284
} else {
285
+ LOGGER .error ("Unexpected root node in yaml" );
284
286
res .setError (Constant .messages .getString ("exim.sites.error.prune.badformat" ));
285
287
}
286
288
return res ;
287
289
}
288
-
289
- private static Object sanitizeForYaml (Object value ) {
290
- if (value == null ) {
291
- return "" ;
292
- }
293
-
294
- if (value instanceof String strValue ) {
295
-
296
- // Remove control characters that might break YAML
297
- strValue = strValue .replaceAll ("[\\ p{Cntrl}&&[^\r \n \t ]]" , "" );
298
-
299
- // Handle known problematic sequences
300
- strValue = strValue .replace ("\u0000 " , "" );
301
-
302
- // For especially problematic strings, consider Base64 encoding
303
- if (containsProhibitedYamlCharacters (strValue )) {
304
- return Base64 .getEncoder ()
305
- .encodeToString (strValue .getBytes (StandardCharsets .UTF_8 ));
306
- }
307
-
308
- return strValue ;
309
- } else if (value instanceof Map ) {
310
- // Process map values recursively
311
- Map <Object , Object > sanitizedMap = new LinkedHashMap <>();
312
- ((Map <?, ?>) value )
313
- .forEach ((k , v ) -> sanitizedMap .put (sanitizeForYaml (k ), sanitizeForYaml (v )));
314
- return sanitizedMap ;
315
- } else if (value instanceof Collection ) {
316
- // Process collection values recursively
317
- List <Object > sanitizedList = new ArrayList <>();
318
- ((Collection <?>) value ).forEach (item -> sanitizedList .add (sanitizeForYaml (item )));
319
- return sanitizedList ;
320
- }
321
-
322
- // For other types, return as is
323
- return value ;
324
- }
325
-
326
- private static boolean containsProhibitedYamlCharacters (String inputText ) {
327
- // Character code constants
328
- final int TAB = 9 ;
329
- final int LINE_FEED = 10 ;
330
- final int CARRIAGE_RETURN = 13 ;
331
- final int CONTROL_CHARS_UPPER_BOUND = 32 ;
332
- final int LINE_SEPARATOR = 0x2028 ;
333
- final int PARAGRAPH_SEPARATOR = 0x2029 ;
334
- final int BYTE_ORDER_MARK = 0xFEFF ;
335
- final int SURROGATE_PAIR_START = 0xD800 ;
336
- final int SURROGATE_PAIR_END = 0xDFFF ;
337
-
338
- // Check for characters known to cause YAML issues
339
- return inputText
340
- .chars ()
341
- .anyMatch (
342
- characterCode ->
343
- (characterCode < CONTROL_CHARS_UPPER_BOUND
344
- && characterCode != TAB
345
- && characterCode != LINE_FEED
346
- && characterCode != CARRIAGE_RETURN )
347
- || // Control chars except tab, LF, CR
348
- (characterCode == LINE_SEPARATOR )
349
- || (characterCode == PARAGRAPH_SEPARATOR )
350
- || // Line/paragraph separators
351
- (characterCode == BYTE_ORDER_MARK )
352
- || // BOM (Byte Order Mark)
353
- (characterCode >= SURROGATE_PAIR_START
354
- && characterCode
355
- <= SURROGATE_PAIR_END )); // Surrogate pairs
356
- }
357
290
}
0 commit comments