0
# Filter System
1
2
Built-in and extensible filtering system for data transformation with automatic escaping support and custom filter registration.
3
4
## Capabilities
5
6
### Built-in Filters
7
8
Dust provides several built-in filters for common data transformations and security escaping.
9
10
```javascript { .api }
11
/**
12
* Built-in filter functions
13
*/
14
const filters: {
15
/** HTML escape filter - escapes HTML entities for safe output */
16
h: (value: any) => string;
17
/** JavaScript string escape filter - escapes for safe JS string literals */
18
j: (value: any) => string;
19
/** URL encode filter - encodes using encodeURI */
20
u: (value: any) => string;
21
/** URL component encode filter - encodes using encodeURIComponent */
22
uc: (value: any) => string;
23
/** JSON stringify filter - safe JSON conversion for JavaScript */
24
js: (value: any) => string;
25
/** JSON parse filter - parses JSON strings to objects */
26
jp: (value: any) => any;
27
};
28
```
29
30
**Usage Examples:**
31
32
```javascript
33
const dust = require('dustjs-linkedin');
34
35
// Template usage with built-in filters:
36
// {name|h} - HTML escape
37
// {script|j} - JavaScript string escape
38
// {url|u} - URL encode
39
// {param|uc} - URL component encode
40
// {data|js} - JSON stringify
41
// {jsonString|jp} - JSON parse
42
43
// Test built-in filters directly
44
console.log(dust.filters.h('<script>alert("xss")</script>'));
45
// Output: "<script>alert("xss")</script>"
46
47
console.log(dust.filters.j('Hello "world"\nNew line'));
48
// Output: "Hello \\\"world\\\"\\nNew line"
49
50
console.log(dust.filters.u('hello world'));
51
// Output: "hello%20world"
52
53
console.log(dust.filters.uc('hello@world.com'));
54
// Output: "hello%40world.com"
55
56
console.log(dust.filters.js({name: 'test', value: 123}));
57
// Output: '{"name":"test","value":123}'
58
59
console.log(dust.filters.jp('{"name":"test","value":123}'));
60
// Output: {name: 'test', value: 123}
61
```
62
63
### Filter Application
64
65
Core function for applying filters to values with automatic escaping.
66
67
```javascript { .api }
68
/**
69
* Applies filter chain to string value
70
* @param string - Input value to filter
71
* @param auto - Automatic filter (usually 'h' for HTML escape)
72
* @param filters - Array of filter names to apply in sequence
73
* @param context - Template context for filter execution
74
* @returns Filtered string value
75
*/
76
function filter(
77
string: any,
78
auto: string | null,
79
filters: string[],
80
context: Context
81
): string;
82
```
83
84
**Usage Examples:**
85
86
```javascript
87
const context = dust.context({});
88
89
// Apply single filter
90
const htmlEscaped = dust.filter('<script>alert("xss")</script>', null, ['h'], context);
91
console.log(htmlEscaped); // "<script>alert("xss")</script>"
92
93
// Apply multiple filters in sequence
94
const multiFiltered = dust.filter('Hello World', null, ['j', 'u'], context);
95
// First applies 'j' (JavaScript escape), then 'u' (URL encode)
96
97
// Apply with automatic filter
98
const autoFiltered = dust.filter('<div>Content</div>', 'h', [], context);
99
console.log(autoFiltered); // "<div>Content</div>" (auto HTML escaped)
100
101
// Chain filters
102
const chained = dust.filter({name: 'test'}, null, ['js', 'u'], context);
103
// First converts to JSON, then URL encodes the result
104
```
105
106
### Escape Functions
107
108
Individual escape functions used by filters and available for direct use.
109
110
```javascript { .api }
111
/**
112
* HTML entity escape function
113
* @param string - String to escape
114
* @returns String with HTML entities escaped
115
*/
116
function escapeHtml(string: any): string;
117
118
/**
119
* JavaScript string escape function
120
* @param string - String to escape for JS
121
* @returns String safe for JavaScript string literals
122
*/
123
function escapeJs(string: any): string;
124
125
/**
126
* Safe JSON stringify function
127
* @param obj - Object to stringify
128
* @returns JSON string safe for JavaScript execution
129
*/
130
function escapeJSON(obj: any): string;
131
```
132
133
**Usage Examples:**
134
135
```javascript
136
// Direct use of escape functions
137
const htmlSafe = dust.escapeHtml('<script>alert("xss")</script>');
138
console.log(htmlSafe); // "<script>alert("xss")</script>"
139
140
const jsSafe = dust.escapeJs('Line 1\nLine 2\t"Quote"');
141
console.log(jsSafe); // "Line 1\\nLine 2\\t\\\"Quote\\\""
142
143
const jsonSafe = dust.escapeJSON({
144
name: 'Test',
145
code: '<script>alert("xss")</script>',
146
special: '\u2028\u2029' // Line/paragraph separators
147
});
148
console.log(jsonSafe); // Safely escaped JSON string
149
150
// Use in custom helpers
151
dust.helpers.safeOutput = (chunk, context, bodies, params) => {
152
const rawData = params.data;
153
const escaped = dust.escapeHtml(rawData);
154
return chunk.write(escaped);
155
};
156
```
157
158
## Custom Filters
159
160
### Filter Registration
161
162
How to register custom filters for template use.
163
164
```javascript { .api }
165
/**
166
* Register custom filter by adding to filters object
167
*/
168
dust.filters.customFilter = (value: any) => string;
169
```
170
171
**Usage Examples:**
172
173
```javascript
174
// Register a simple text transformation filter
175
dust.filters.capitalize = (value) => {
176
if (typeof value !== 'string') return value;
177
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
178
};
179
180
// Register a number formatting filter
181
dust.filters.currency = (value) => {
182
const number = parseFloat(value);
183
if (isNaN(number)) return value;
184
return '$' + number.toFixed(2);
185
};
186
187
// Register a date formatting filter
188
dust.filters.dateFormat = (value) => {
189
const date = new Date(value);
190
if (isNaN(date.getTime())) return value;
191
return date.toLocaleDateString('en-US');
192
};
193
194
// Register a text truncation filter
195
dust.filters.truncate = (value) => {
196
if (typeof value !== 'string') return value;
197
return value.length > 50 ? value.substring(0, 50) + '...' : value;
198
};
199
200
// Template usage:
201
// {name|capitalize}
202
// {price|currency}
203
// {date|dateFormat}
204
// {description|truncate}
205
```
206
207
### Advanced Custom Filters
208
209
More complex filter implementations with error handling and configuration.
210
211
```javascript
212
// Filter with configuration
213
dust.filters.truncateBy = (value, length = 50) => {
214
if (typeof value !== 'string') return value;
215
const maxLength = parseInt(length) || 50;
216
return value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
217
};
218
219
// Filter with multiple transformations
220
dust.filters.slug = (value) => {
221
if (typeof value !== 'string') return value;
222
return value
223
.toLowerCase()
224
.replace(/[^\w\s-]/g, '') // Remove special characters
225
.replace(/\s+/g, '-') // Replace spaces with hyphens
226
.replace(/-+/g, '-') // Collapse multiple hyphens
227
.trim('-'); // Remove leading/trailing hyphens
228
};
229
230
// Filter with error handling
231
dust.filters.safeNumber = (value) => {
232
try {
233
const number = parseFloat(value);
234
return isNaN(number) ? '0' : number.toString();
235
} catch (err) {
236
dust.log('Filter error in safeNumber: ' + err.message, 'ERROR');
237
return '0';
238
}
239
};
240
241
// Filter that uses context (advanced - requires custom implementation)
242
dust.filters.translate = function(value, context) {
243
const translations = context.get('translations') || {};
244
return translations[value] || value;
245
};
246
```
247
248
### Filter Chaining
249
250
Understanding how multiple filters work together in sequence.
251
252
```javascript
253
// Filters are applied left to right
254
// Template: {name|capitalize|truncate}
255
// 1. First applies capitalize filter
256
// 2. Then applies truncate filter to the result
257
258
// Example filter chain execution:
259
const input = 'hello world this is a long string';
260
261
// Step 1: Apply capitalize
262
const step1 = dust.filters.capitalize(input); // "Hello world this is a long string"
263
264
// Step 2: Apply truncate
265
const final = dust.filters.truncate(step1); // "Hello world this is a long string..."
266
267
// Register filters that work well together
268
dust.filters.clean = (value) => {
269
if (typeof value !== 'string') return value;
270
return value.trim().replace(/\s+/g, ' '); // Clean whitespace
271
};
272
273
dust.filters.title = (value) => {
274
if (typeof value !== 'string') return value;
275
return value.replace(/\w\S*/g, (txt) =>
276
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
277
);
278
};
279
280
// Template usage for chaining:
281
// {text|clean|title} - First clean whitespace, then title case
282
```
283
284
## Automatic Escaping
285
286
### Default Auto-Escape Behavior
287
288
Dust automatically applies HTML escaping by default for security.
289
290
```javascript
291
// By default, all variable references are HTML escaped
292
// Template: {userInput}
293
// If userInput = '<script>alert("xss")</script>'
294
// Output: <script>alert("xss")</script>
295
296
// Disable auto-escaping with 's' filter (suppress)
297
// Template: {userInput|s}
298
// Output: <script>alert("xss")</script> (DANGEROUS!)
299
300
// Override auto-escape with specific filter
301
// Template: {userInput|j} - JavaScript escape instead of HTML
302
```
303
304
### Configuring Auto-Escape
305
306
Control automatic escaping behavior in templates.
307
308
```javascript
309
// The default auto-escape filter is 'h' (HTML escape)
310
// This is applied automatically to all references unless:
311
// 1. Explicitly suppressed with |s
312
// 2. Overridden with another filter
313
314
// Example configurations in templates:
315
// {value} - Auto HTML escaped
316
// {value|s} - No escaping (suppress)
317
// {value|j} - JavaScript escaped (overrides auto HTML)
318
// {value|h|j} - HTML escaped, then JavaScript escaped
319
// {value|s|h} - Suppress auto, then explicitly HTML escape
320
```
321
322
### Security Considerations
323
324
Best practices for safe filter usage.
325
326
```javascript
327
// SAFE: Always escape user content for HTML output
328
// Template: <div>{userContent}</div>
329
// Automatically HTML escaped by default
330
331
// DANGEROUS: Suppressing escaping
332
// Template: <div>{userContent|s}</div>
333
// Only use |s for trusted content!
334
335
// SAFE: Appropriate escaping for context
336
// Template: <script>var data = "{jsonData|js}";</script>
337
// Uses JavaScript escaping for script context
338
339
// SAFE: URL context escaping
340
// Template: <a href="?q={query|uc}">Link</a>
341
// Uses URL component encoding for query parameters
342
343
// Register security-focused filters
344
dust.filters.stripTags = (value) => {
345
if (typeof value !== 'string') return value;
346
return value.replace(/<[^>]*>/g, ''); // Remove HTML tags
347
};
348
349
dust.filters.allowedTags = (value) => {
350
if (typeof value !== 'string') return value;
351
// Allow only specific safe tags
352
return value.replace(/<(?!\/?(?:b|i|em|strong|p|br)\b)[^>]*>/gi, '');
353
};
354
```
355
356
## Filter Performance and Best Practices
357
358
### Performance Optimization
359
360
Best practices for efficient filter usage.
361
362
```javascript
363
// EFFICIENT: Simple, focused filters
364
dust.filters.upper = (value) => {
365
return typeof value === 'string' ? value.toUpperCase() : value;
366
};
367
368
// LESS EFFICIENT: Complex processing in filters
369
dust.filters.complexProcess = (value) => {
370
// Avoid heavy computations in filters
371
// Consider doing this in helpers or before rendering
372
return expensiveTransformation(value);
373
};
374
375
// GOOD: Cache expensive computations
376
const computationCache = new Map();
377
dust.filters.cachedTransform = (value) => {
378
if (computationCache.has(value)) {
379
return computationCache.get(value);
380
}
381
382
const result = expensiveComputation(value);
383
computationCache.set(value, result);
384
return result;
385
};
386
```
387
388
### Error Handling in Filters
389
390
Robust error handling patterns for custom filters.
391
392
```javascript
393
// Robust filter with error handling
394
dust.filters.robustFilter = (value) => {
395
try {
396
// Type checking
397
if (value === null || value === undefined) {
398
return '';
399
}
400
401
// Safe conversion
402
const stringValue = String(value);
403
404
// Process with error handling
405
return processValue(stringValue);
406
407
} catch (err) {
408
// Log error for debugging
409
dust.log(`Filter error: ${err.message}`, 'ERROR');
410
411
// Return safe fallback
412
return String(value || '');
413
}
414
};
415
416
// Filter with validation
417
dust.filters.emailMask = (value) => {
418
if (typeof value !== 'string') return value;
419
420
// Validate email format
421
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
422
if (!emailRegex.test(value)) {
423
return '[Invalid Email]';
424
}
425
426
// Mask email
427
const [local, domain] = value.split('@');
428
const maskedLocal = local.charAt(0) + '*'.repeat(local.length - 2) + local.charAt(local.length - 1);
429
return `${maskedLocal}@${domain}`;
430
};
431
```
432
433
### Filter Testing
434
435
Patterns for testing custom filters.
436
437
```javascript
438
// Test custom filters
439
function testFilters() {
440
const testCases = [
441
{ input: 'hello world', expected: 'Hello World', filter: 'title' },
442
{ input: 123.456, expected: '$123.46', filter: 'currency' },
443
{ input: '<script>alert()</script>', expected: '[sanitized]', filter: 'sanitize' }
444
];
445
446
testCases.forEach(testCase => {
447
const result = dust.filters[testCase.filter](testCase.input);
448
console.log(`Filter ${testCase.filter}: ${result === testCase.expected ? 'PASS' : 'FAIL'}`);
449
});
450
}
451
452
// Unit test helper for filters
453
function testFilter(filterName, testCases) {
454
const filter = dust.filters[filterName];
455
if (!filter) {
456
console.error(`Filter ${filterName} not found`);
457
return;
458
}
459
460
testCases.forEach((testCase, index) => {
461
const result = filter(testCase.input);
462
const passed = result === testCase.expected;
463
console.log(`Test ${index + 1}: ${passed ? 'PASS' : 'FAIL'}`);
464
if (!passed) {
465
console.log(` Expected: ${testCase.expected}`);
466
console.log(` Actual: ${result}`);
467
}
468
});
469
}
470
```