Skip to content

Commit

Permalink
feat(core): add raw filter to stop sanitizer
Browse files Browse the repository at this point in the history
  • Loading branch information
oscar60310 committed Aug 31, 2022
1 parent 2ef3491 commit 5b52324
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 1 deletion.
Expand Up @@ -5,3 +5,4 @@ export const EXECUTE_FILTER_NAME = 'execute';
export const REFERENCE_SEARCH_MAX_DEPTH = 100;
export const SANITIZER_NAME = 'sanitize';
export const PARAMETERIZER_VAR_NAME = 'parameterizer';
export const RAW_FILTER_NAME = 'raw';
Expand Up @@ -4,6 +4,8 @@ import { ExecutorRunner } from './executorRunner';
import { ExecutorBuilder } from './executorBuilder';
import { SanitizerBuilder } from './sanitizerBuilder';
import { SanitizerRunner } from './sanitizerRunner';
import { RawBuilder } from './rawBuilder';
import { RawRunner } from './rawRunner';

export default [
ReqTagBuilder,
Expand All @@ -12,4 +14,6 @@ export default [
ExecutorBuilder,
SanitizerBuilder,
SanitizerRunner,
RawBuilder,
RawRunner,
];
@@ -0,0 +1,10 @@
import {
FilterBuilder,
VulcanInternalExtension,
} from '@vulcan-sql/core/models';
import { RAW_FILTER_NAME } from './constants';

@VulcanInternalExtension()
export class RawBuilder extends FilterBuilder {
public filterName = RAW_FILTER_NAME;
}
@@ -0,0 +1,18 @@
import {
FilterRunner,
FilterRunnerTransformOptions,
VulcanInternalExtension,
} from '@vulcan-sql/core/models';
import { RAW_FILTER_NAME } from './constants';

@VulcanInternalExtension()
export class RawRunner extends FilterRunner {
public filterName = RAW_FILTER_NAME;

public async transform({
value,
}: FilterRunnerTransformOptions): Promise<any> {
// Do nothing, this filer is only a place holder to block sanitizer
return value;
}
}
Expand Up @@ -4,7 +4,7 @@ import {
} from '@vulcan-sql/core/models';
import * as nunjucks from 'nunjucks';
import { visitChildren } from '../../extension-utils';
import { SANITIZER_NAME } from './constants';
import { RAW_FILTER_NAME, SANITIZER_NAME } from './constants';

@VulcanInternalExtension()
export class SanitizerBuilder extends FilterBuilder {
Expand All @@ -15,6 +15,15 @@ export class SanitizerBuilder extends FilterBuilder {

private addSanitizer(node: nunjucks.nodes.Node, parentHasOutputNode = false) {
visitChildren(node, (child, replace) => {
// Visitor should be stopped by raw filter
if (
child instanceof nunjucks.nodes.Filter &&
child.name instanceof nunjucks.nodes.Symbol &&
child.name.value === RAW_FILTER_NAME
) {
return;
}

if (this.isNodeNeedToBeSanitize(child)) {
if (!parentHasOutputNode && !(node instanceof nunjucks.nodes.Output))
return;
Expand All @@ -41,6 +50,7 @@ export class SanitizerBuilder extends FilterBuilder {
private isNodeNeedToBeSanitize(node: nunjucks.nodes.Node): boolean {
return (
node instanceof nunjucks.nodes.LookupVal ||
// includes FunCall, Filter
node instanceof nunjucks.nodes.FunCall ||
node instanceof nunjucks.nodes.Symbol
);
Expand Down
Expand Up @@ -157,3 +157,74 @@ it('The binding keys should be in order which they are used', async () => {
'$2',
]);
});

it('Should render raw value when using raw filter', async () => {
// Arrange
const { compiler, loader, builder, executor } = await createTestCompiler();
const { compiledData } = await compiler.compile(
`{{ context.params.id | raw }}{{ context.params.name | upper | raw }}`
);
builder.value.onFirstCall().resolves([{ id: 1, name: 'freda' }]);
// Action
loader.setSource('test', compiledData);
await compiler.execute('test', {
context: { params: { id: 1, name: 'freda' } },
});
// Assert
expect(executor.createBuilder.firstCall.args[0]).toBe('1FREDA');
});

it('Raw value should be wrapped again when accessing its children', async () => {
// Arrange
const { compiler, loader, builder, executor } = await createTestCompiler();
const { compiledData } = await compiler.compile(
`{{ (context.params.data | raw).name }}`
);
builder.value.onFirstCall().resolves([{ id: 1, name: 'freda' }]);
// Action
loader.setSource('test', compiledData);
await compiler.execute('test', {
context: { params: { data: { id: 1, name: 'freda' } } },
});
// Assert
expect(executor.createBuilder.firstCall.args[0]).toBe('$1');
expect(executor.createBuilder.firstCall.args[1].get('$1')).toBe('freda');
});

it('Raw value should be wrapped again when raw filter is chaining again', async () => {
// Arrange
const { compiler, loader, builder, executor } = await createTestCompiler();
const { compiledData } = await compiler.compile(
`{{ context.params.name | raw | upper }}`
);
builder.value.onFirstCall().resolves([{ id: 1, name: 'freda' }]);
// Action
loader.setSource('test', compiledData);
await compiler.execute('test', {
context: { params: { name: 'freda' } },
});
// Assert
expect(executor.createBuilder.firstCall.args[0]).toBe('$1');
expect(executor.createBuilder.firstCall.args[1].get('$1')).toBe('FREDA');
});

it('Raw value should be wrapped again when it is assigned to variables and is rendered', async () => {
// Arrange
const { compiler, loader, builder, executor } = await createTestCompiler();
const { compiledData } = await compiler.compile(
`{% set someVar = (context.params.name | upper | raw) %}
{% if someVar == "FREDA" %}This should be rendered{% endif %}
{{ someVar }}`
);
builder.value.onFirstCall().resolves([{ id: 1, name: 'freda' }]);
// Action
loader.setSource('test', compiledData);
await compiler.execute('test', {
context: { params: { name: 'freda' } },
});
// Assert
expect(executor.createBuilder.firstCall.args[0]).toBe(
'This should be rendered\n$1'
);
expect(executor.createBuilder.firstCall.args[1].get('$1')).toBe('FREDA');
});

0 comments on commit 5b52324

Please sign in to comment.