0
# Output Formats and Escaping
1
2
FreeMarker's output format system provides automatic escaping and content-type-aware processing to prevent security vulnerabilities like XSS attacks while ensuring proper content formatting.
3
4
## Core Output Format Classes
5
6
### Base Output Format
7
8
```java { .api }
9
abstract class OutputFormat {
10
// Format identification
11
abstract String getName();
12
abstract String getMimeType();
13
abstract boolean isOutputFormatMixingAllowed(OutputFormat otherOutputFormat);
14
15
// Escaping capabilities
16
abstract boolean isAutoEscapedByDefault();
17
18
// Format compatibility
19
boolean isMarkupFormat();
20
boolean isSourceCodeFormat();
21
}
22
```
23
24
### Markup Output Format Base
25
26
```java { .api }
27
abstract class MarkupOutputFormat<MO extends TemplateMarkupOutputModel<MO>> extends OutputFormat {
28
// Markup-specific methods
29
abstract MO fromPlainTextByEscaping(String textToEsc) throws TemplateModelException;
30
abstract MO fromMarkup(String markupText) throws TemplateModelException;
31
abstract MO getEmpty();
32
33
// Markup concatenation
34
abstract MO concat(MO... outputModels) throws TemplateModelException;
35
36
// Format information
37
boolean isAutoEscapedByDefault();
38
boolean isMarkupFormat();
39
}
40
41
// Common markup format base
42
abstract class CommonMarkupOutputFormat<MO extends CommonTemplateMarkupOutputModel<MO>>
43
extends MarkupOutputFormat<MO> {
44
45
// Common escaping functionality
46
abstract String escapePlainText(String plainTextContent);
47
abstract boolean isLegacyBuiltInBypassed(String builtInName);
48
}
49
```
50
51
## Built-in Output Formats
52
53
### HTML Output Format
54
55
```java { .api }
56
class HTMLOutputFormat extends CommonMarkupOutputFormat<TemplateHTMLOutputModel> {
57
// Singleton instance
58
static final HTMLOutputFormat INSTANCE = new HTMLOutputFormat();
59
60
// Format identification
61
String getName();
62
String getMimeType();
63
64
// HTML-specific escaping
65
String escapePlainText(String plainTextContent);
66
TemplateHTMLOutputModel fromPlainTextByEscaping(String textToEsc) throws TemplateModelException;
67
TemplateHTMLOutputModel fromMarkup(String markupText) throws TemplateModelException;
68
TemplateHTMLOutputModel getEmpty();
69
70
// HTML model creation
71
TemplateHTMLOutputModel fromPlainTextByEscaping(String textToEsc);
72
TemplateHTMLOutputModel fromMarkup(String markupText);
73
}
74
75
// HTML output model
76
interface TemplateHTMLOutputModel extends CommonTemplateMarkupOutputModel<TemplateHTMLOutputModel> {
77
String getMarkupString() throws TemplateModelException;
78
}
79
```
80
81
### XML Output Format
82
83
```java { .api }
84
class XMLOutputFormat extends CommonMarkupOutputFormat<TemplateXMLOutputModel> {
85
// Singleton instance
86
static final XMLOutputFormat INSTANCE = new XMLOutputFormat();
87
88
// Format identification
89
String getName();
90
String getMimeType();
91
92
// XML-specific escaping
93
String escapePlainText(String plainTextContent);
94
TemplateXMLOutputModel fromPlainTextByEscaping(String textToEsc) throws TemplateModelException;
95
TemplateXMLOutputModel fromMarkup(String markupText) throws TemplateModelException;
96
TemplateXMLOutputModel getEmpty();
97
}
98
99
// XML output model
100
interface TemplateXMLOutputModel extends CommonTemplateMarkupOutputModel<TemplateXMLOutputModel> {
101
String getMarkupString() throws TemplateModelException;
102
}
103
```
104
105
### XHTML Output Format
106
107
```java { .api }
108
class XHTMLOutputFormat extends HTMLOutputFormat {
109
// Singleton instance
110
static final XHTMLOutputFormat INSTANCE = new XHTMLOutputFormat();
111
112
// Format identification (inherits most from HTMLOutputFormat)
113
String getName();
114
String getMimeType();
115
}
116
```
117
118
### RTF Output Format
119
120
```java { .api }
121
class RTFOutputFormat extends CommonMarkupOutputFormat<TemplateRTFOutputModel> {
122
// Singleton instance
123
static final RTFOutputFormat INSTANCE = new RTFOutputFormat();
124
125
// Format identification
126
String getName();
127
String getMimeType();
128
129
// RTF-specific escaping
130
String escapePlainText(String plainTextContent);
131
TemplateRTFOutputModel fromPlainTextByEscaping(String textToEsc) throws TemplateModelException;
132
TemplateRTFOutputModel fromMarkup(String markupText) throws TemplateModelException;
133
TemplateRTFOutputModel getEmpty();
134
}
135
```
136
137
### Plain Text Output Format
138
139
```java { .api }
140
class PlainTextOutputFormat extends OutputFormat {
141
// Singleton instance
142
static final PlainTextOutputFormat INSTANCE = new PlainTextOutputFormat();
143
144
// Format identification
145
String getName();
146
String getMimeType();
147
boolean isAutoEscapedByDefault();
148
boolean isOutputFormatMixingAllowed(OutputFormat otherOutputFormat);
149
}
150
```
151
152
### Undefined Output Format
153
154
```java { .api }
155
class UndefinedOutputFormat extends OutputFormat {
156
// Singleton instance
157
static final UndefinedOutputFormat INSTANCE = new UndefinedOutputFormat();
158
159
// Format identification
160
String getName();
161
String getMimeType();
162
boolean isAutoEscapedByDefault();
163
boolean isOutputFormatMixingAllowed(OutputFormat otherOutputFormat);
164
}
165
```
166
167
## Configuration and Usage
168
169
### Output Format Configuration
170
171
```java
172
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
173
174
// Set default output format
175
cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
176
177
// Configure auto-escaping policy
178
cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
179
180
// Register custom output formats
181
Set<OutputFormat> customFormats = new HashSet<>();
182
customFormats.add(new CustomJSONOutputFormat());
183
cfg.setRegisteredCustomOutputFormats(customFormats);
184
185
// Recognize standard file extensions for automatic format selection
186
cfg.setRecognizeStandardFileExtensions(true);
187
```
188
189
### Template-Specific Output Format
190
191
```java
192
// Configure specific output format for certain templates
193
TemplateConfiguration htmlConfig = new TemplateConfiguration();
194
htmlConfig.setOutputFormat(HTMLOutputFormat.INSTANCE);
195
htmlConfig.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
196
197
cfg.setTemplateConfigurations(
198
new ConditionalTemplateConfigurationFactory(
199
new FileExtensionMatcher("html"),
200
new FirstMatchTemplateConfigurationFactory(htmlConfig)
201
)
202
);
203
204
// Plain text for email templates
205
TemplateConfiguration textConfig = new TemplateConfiguration();
206
textConfig.setOutputFormat(PlainTextOutputFormat.INSTANCE);
207
208
cfg.setTemplateConfigurations(
209
new ConditionalTemplateConfigurationFactory(
210
new PathGlobMatcher("email/**"),
211
new FirstMatchTemplateConfigurationFactory(textConfig)
212
)
213
);
214
```
215
216
## Auto-Escaping Policies
217
218
```java { .api }
219
// Auto-escaping policy constants in Configuration
220
static final int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 0;
221
static final int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 1;
222
static final int DISABLE_AUTO_ESCAPING_POLICY = 2;
223
```
224
225
### Auto-Escaping Configuration Examples
226
227
```java
228
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
229
230
// Enable auto-escaping only for formats where it's default (like HTML)
231
cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
232
233
// Enable auto-escaping for all markup formats that support it
234
cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
235
236
// Disable auto-escaping completely (not recommended for security)
237
cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
238
```
239
240
## Template Markup Output Models
241
242
### Common Template Markup Output Model
243
244
```java { .api }
245
interface CommonTemplateMarkupOutputModel<MO extends CommonTemplateMarkupOutputModel<MO>>
246
extends TemplateMarkupOutputModel<MO> {
247
248
String getMarkupString() throws TemplateModelException;
249
MO concat(MO... outputModels) throws TemplateModelException;
250
}
251
252
// Base markup output model
253
interface TemplateMarkupOutputModel<MO extends TemplateMarkupOutputModel<MO>>
254
extends TemplateModel {
255
256
MO getOutputFormat();
257
}
258
```
259
260
### Specific Output Models
261
262
```java { .api }
263
// HTML output model implementation
264
class TemplateHTMLOutputModelImpl implements TemplateHTMLOutputModel {
265
TemplateHTMLOutputModelImpl(String markupContent, HTMLOutputFormat outputFormat);
266
String getMarkupString() throws TemplateModelException;
267
TemplateHTMLOutputModel concat(TemplateHTMLOutputModel... outputModels) throws TemplateModelException;
268
HTMLOutputFormat getOutputFormat();
269
}
270
271
// XML output model implementation
272
class TemplateXMLOutputModelImpl implements TemplateXMLOutputModel {
273
TemplateXMLOutputModelImpl(String markupContent, XMLOutputFormat outputFormat);
274
String getMarkupString() throws TemplateModelException;
275
TemplateXMLOutputModel concat(TemplateXMLOutputModel... outputModels) throws TemplateModelException;
276
XMLOutputFormat getOutputFormat();
277
}
278
```
279
280
## Custom Output Formats
281
282
### Creating Custom Output Formats
283
284
```java
285
public class JSONOutputFormat extends OutputFormat {
286
public static final JSONOutputFormat INSTANCE = new JSONOutputFormat();
287
288
private JSONOutputFormat() {
289
// Private constructor for singleton
290
}
291
292
@Override
293
public String getName() {
294
return "JSON";
295
}
296
297
@Override
298
public String getMimeType() {
299
return "application/json";
300
}
301
302
@Override
303
public boolean isAutoEscapedByDefault() {
304
return true;
305
}
306
307
@Override
308
public boolean isOutputFormatMixingAllowed(OutputFormat otherOutputFormat) {
309
return otherOutputFormat instanceof JSONOutputFormat ||
310
otherOutputFormat instanceof PlainTextOutputFormat;
311
}
312
}
313
314
// JSON markup output format with escaping
315
public class JSONMarkupOutputFormat extends MarkupOutputFormat<TemplateJSONOutputModel> {
316
public static final JSONMarkupOutputFormat INSTANCE = new JSONMarkupOutputFormat();
317
318
@Override
319
public TemplateJSONOutputModel fromPlainTextByEscaping(String textToEsc)
320
throws TemplateModelException {
321
return new TemplateJSONOutputModelImpl(escapeJSON(textToEsc), this);
322
}
323
324
@Override
325
public TemplateJSONOutputModel fromMarkup(String markupText)
326
throws TemplateModelException {
327
return new TemplateJSONOutputModelImpl(markupText, this);
328
}
329
330
@Override
331
public TemplateJSONOutputModel getEmpty() {
332
return new TemplateJSONOutputModelImpl("", this);
333
}
334
335
@Override
336
public TemplateJSONOutputModel concat(TemplateJSONOutputModel... outputModels)
337
throws TemplateModelException {
338
StringBuilder sb = new StringBuilder();
339
for (TemplateJSONOutputModel model : outputModels) {
340
sb.append(model.getMarkupString());
341
}
342
return new TemplateJSONOutputModelImpl(sb.toString(), this);
343
}
344
345
private String escapeJSON(String text) {
346
return text.replace("\\", "\\\\")
347
.replace("\"", "\\\"")
348
.replace("\n", "\\n")
349
.replace("\r", "\\r")
350
.replace("\t", "\\t")
351
.replace("\b", "\\b")
352
.replace("\f", "\\f");
353
}
354
355
@Override
356
public String getName() {
357
return "JSON";
358
}
359
360
@Override
361
public String getMimeType() {
362
return "application/json";
363
}
364
365
@Override
366
public boolean isAutoEscapedByDefault() {
367
return true;
368
}
369
370
@Override
371
public boolean isOutputFormatMixingAllowed(OutputFormat otherOutputFormat) {
372
return otherOutputFormat instanceof JSONMarkupOutputFormat ||
373
otherOutputFormat instanceof PlainTextOutputFormat;
374
}
375
}
376
```
377
378
## Template Usage Examples
379
380
### Basic Auto-Escaping
381
382
```html
383
<!-- Template with HTML output format -->
384
<html>
385
<head>
386
<title>${pageTitle}</title>
387
</head>
388
<body>
389
<h1>${heading}</h1>
390
<p>${userComment}</p> <!-- Automatically escaped for HTML -->
391
<p>${safeHtmlContent?no_esc}</p> <!-- Bypass escaping when safe -->
392
</body>
393
</html>
394
```
395
396
### Format-Specific Escaping
397
398
```java
399
// Data model with potentially dangerous content
400
Map<String, Object> dataModel = new HashMap<>();
401
dataModel.put("userInput", "<script>alert('XSS')</script>");
402
dataModel.put("xmlContent", "<item>Value & \"quoted\"</item>");
403
404
// HTML template - userInput will be escaped
405
// Output: <script>alert('XSS')</script>
406
407
// XML template - xmlContent will be escaped for XML
408
// Output: <item>Value & "quoted"</item>
409
410
// Plain text template - no escaping applied
411
// Output: <script>alert('XSS')</script>
412
```
413
414
### Mixed Output Formats
415
416
```html
417
<!-- Main HTML template -->
418
<html>
419
<body>
420
<script type="application/json">
421
<#-- Switch to JSON output format for this section -->
422
<#outputformat "JSON">
423
{
424
"userData": "${userData}", <!-- JSON-escaped -->
425
"message": "${message}" <!-- JSON-escaped -->
426
}
427
</#outputformat>
428
</script>
429
430
<div class="content">
431
${htmlContent} <!-- HTML-escaped -->
432
</div>
433
</body>
434
</html>
435
```
436
437
## Built-in Functions for Output Formats
438
439
### Format-Related Built-ins
440
441
```ftl
442
<!-- Check current output format -->
443
<#if .output_format == "HTML">
444
<!-- HTML-specific content -->
445
</#if>
446
447
<!-- Get output format name -->
448
Current format: ${.output_format}
449
450
<!-- Force escaping regardless of auto-escaping setting -->
451
${dangerousContent?esc}
452
453
<!-- Bypass escaping (use with caution) -->
454
${safeMarkup?no_esc}
455
456
<!-- Convert to different format -->
457
${htmlContent?markup_string} <!-- Get raw markup string -->
458
459
<!-- Check if content is already escaped -->
460
<#if content?is_markup_output>
461
Content is already escaped
462
</#if>
463
```
464
465
### Advanced Format Handling
466
467
```ftl
468
<!-- Conditional escaping based on format -->
469
<#if .output_format?starts_with("HTML")>
470
${content?html}
471
<#elseif .output_format == "XML">
472
${content?xml}
473
<#elseif .output_format == "RTF">
474
${content?rtf}
475
<#else>
476
${content}
477
</#if>
478
479
<!-- Format-aware concatenation -->
480
<#assign combined = markup1 + markup2> <!-- Properly combines markup -->
481
482
<!-- Convert between formats -->
483
<#outputformat "XML">
484
<#assign xmlEscaped = htmlContent?string>
485
</#outputformat>
486
```
487
488
## Security Considerations
489
490
### XSS Prevention
491
492
```java
493
// Proper configuration for web applications
494
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
495
cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
496
cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
497
498
// Template usage - automatic escaping prevents XSS
499
// ${userInput} -> automatically HTML-escaped
500
// ${trustedHtml?no_esc} -> bypasses escaping (use with extreme caution)
501
```
502
503
### Content Security Policies
504
505
```java
506
// Custom output format with CSP-aware escaping
507
public class CSPAwareHTMLOutputFormat extends HTMLOutputFormat {
508
@Override
509
public String escapePlainText(String plainTextContent) {
510
// Enhanced escaping for Content Security Policy compliance
511
return super.escapePlainText(plainTextContent)
512
.replace("javascript:", "blocked:")
513
.replace("data:", "blocked:")
514
.replace("vbscript:", "blocked:");
515
}
516
}
517
```
518
519
### Trusted Content Handling
520
521
```java
522
// Wrapper for trusted content that should not be escaped
523
public class TrustedContent implements TemplateScalarModel, TemplateMarkupOutputModel {
524
private final String content;
525
private final OutputFormat outputFormat;
526
527
public TrustedContent(String content, OutputFormat outputFormat) {
528
this.content = content;
529
this.outputFormat = outputFormat;
530
}
531
532
@Override
533
public String getAsString() throws TemplateModelException {
534
return content;
535
}
536
537
@Override
538
public OutputFormat getOutputFormat() {
539
return outputFormat;
540
}
541
}
542
543
// Usage
544
dataModel.put("trustedHtml", new TrustedContent("<b>Safe HTML</b>", HTMLOutputFormat.INSTANCE));
545
```
546
547
## Performance Considerations
548
549
### Output Format Selection Performance
550
551
```java
552
// Pre-configure output formats for better performance
553
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
554
555
// Use template configurations for automatic format selection
556
cfg.setRecognizeStandardFileExtensions(true); // Automatic based on file extension
557
558
// Pre-register custom formats
559
Set<OutputFormat> customFormats = new HashSet<>();
560
customFormats.add(CustomJSONOutputFormat.INSTANCE);
561
customFormats.add(CustomXMLOutputFormat.INSTANCE);
562
cfg.setRegisteredCustomOutputFormats(customFormats);
563
```
564
565
### Escaping Performance
566
567
```java
568
// Efficient escaping for high-throughput applications
569
public class OptimizedHTMLOutputFormat extends HTMLOutputFormat {
570
private static final char[] HTML_ESCAPE_CHARS = {'&', '<', '>', '"', '\''};
571
572
@Override
573
public String escapePlainText(String plainTextContent) {
574
// Optimized escaping implementation
575
if (plainTextContent == null || plainTextContent.isEmpty()) {
576
return plainTextContent;
577
}
578
579
// Quick check if escaping is needed
580
boolean needsEscaping = false;
581
for (char c : HTML_ESCAPE_CHARS) {
582
if (plainTextContent.indexOf(c) != -1) {
583
needsEscaping = true;
584
break;
585
}
586
}
587
588
if (!needsEscaping) {
589
return plainTextContent;
590
}
591
592
// Perform escaping only when necessary
593
return super.escapePlainText(plainTextContent);
594
}
595
}
596
```