Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(sanitize-results): add a module to sanitize results
Browse files Browse the repository at this point in the history
  • Loading branch information
rayrutjes committed Jul 21, 2017
1 parent a65116b commit e78cacd
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 0 deletions.
251 changes: 251 additions & 0 deletions src/__tests__/sanitize-results.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import sanitizeResults from '../sanitize-results';

test('it ensures results are provided in the correct format', () => {
expect(() => {
sanitizeResults();
}).toThrow(new TypeError('Results should be provided as an array.'));

expect(() => {
sanitizeResults({});
}).toThrow(new TypeError('Results should be provided as an array.'));
});

test('it ensures tags are provided in the correct format', () => {
expect(() => {
sanitizeResults([]);
}).toThrow(
new TypeError('preTag and postTag should be provided as strings.')
);

expect(() => {
sanitizeResults([], 'pre');
}).toThrow(
new TypeError('preTag and postTag should be provided as strings.')
);

expect(() => {
sanitizeResults([], {}, 'post');
}).toThrow(
new TypeError('preTag and postTag should be provided as strings.')
);
});

test('it should escape HTML of highlighted values', () => {
const results = [
{
name: 'name',
_highlightResult: {
name: {
value: "<script>alert('Yay')</script>",
matchLevel: 'full',
},
},
},
];

const sanitized = sanitizeResults(results, 'pre', 'post');

expect(sanitized).toEqual([
{
name: 'name',
_highlightResult: {
name: {
value: '&lt;script&gt;alert(&#39;Yay&#39;)&lt;/script&gt;',
matchLevel: 'full',
},
},
},
]);
});

test('it should escape HTML of snippetted values', () => {
const results = [
{
name: 'name',
_snippetResult: {
name: {
value: "<script>alert('Yay')</script>",
matchLevel: 'full',
},
},
},
];

const sanitized = sanitizeResults(results, 'pre', 'post');

expect(sanitized).toEqual([
{
name: 'name',
_snippetResult: {
name: {
value: '&lt;script&gt;alert(&#39;Yay&#39;)&lt;/script&gt;',
matchLevel: 'full',
},
},
},
]);
});

test('it should not escape HTML of non highlighted attributes', () => {
const results = [
{
name: '<h1>Test</h1>',
},
];

const sanitized = sanitizeResults(results, 'pre', 'post');
expect(sanitized).toEqual(results);
});

test('it should replace pre-post tags in highlighted values', () => {
const results = [
{
name: 'name',
_snippetResult: {
name: {
value: 'My __ais-highlight__resu__/ais-highlight__lt',
matchLevel: 'full',
},
},
},
];

const sanitized = sanitizeResults(
results,
'__ais-highlight__',
'__/ais-highlight__'
);
expect(sanitized).toEqual([
{
name: 'name',
_snippetResult: {
name: {
value: 'My <em>resu</em>lt',
matchLevel: 'full',
},
},
},
]);
});

test('it should replace multiple occurences of pre-post tags in highlighted values', () => {
const results = [
{
name: 'name',
_snippetResult: {
name: {
value:
'__ais-highlight__My__/ais-highlight__ __ais-highlight__resu__/ais-highlight__lt',
matchLevel: 'full',
},
},
},
];

const sanitized = sanitizeResults(
results,
'__ais-highlight__',
'__/ais-highlight__'
);
expect(sanitized).toEqual([
{
name: 'name',
_snippetResult: {
name: {
value: '<em>My</em> <em>resu</em>lt',
matchLevel: 'full',
},
},
},
]);
});

test('it should handle nested attributes', () => {
const results = [
{
name: 'name',
_snippetResult: {
tags: [
{
value: '__ais-highlight__Ta__/ais-highlight__g 2',
matchLevel: 'full',
},
{
value: '__ais-highlight__Ta__/ais-highlight__g 2',
matchLevel: 'full',
},
],
},
},
];

const sanitized = sanitizeResults(
results,
'__ais-highlight__',
'__/ais-highlight__'
);
expect(sanitized).toEqual([
{
name: 'name',
_snippetResult: {
tags: [
{
value: '<em>Ta</em>g 2',
matchLevel: 'full',
},
{
value: '<em>Ta</em>g 2',
matchLevel: 'full',
},
],
},
},
]);
});

test('it should handle deeply nested attributes', () => {
const results = [
{
name: 'name',
_snippetResult: {
info: {
tags: [
{
value: '__ais-highlight__Ta__/ais-highlight__g 2',
matchLevel: 'full',
},
{
value: '__ais-highlight__Ta__/ais-highlight__g 2',
matchLevel: 'full',
},
],
},
},
},
];

const sanitized = sanitizeResults(
results,
'__ais-highlight__',
'__/ais-highlight__'
);
expect(sanitized).toEqual([
{
name: 'name',
_snippetResult: {
info: {
tags: [
{
value: '<em>Ta</em>g 2',
matchLevel: 'full',
},
{
value: '<em>Ta</em>g 2',
matchLevel: 'full',
},
],
},
},
},
]);
});
75 changes: 75 additions & 0 deletions src/sanitize-results.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import escapeHtml from 'escape-html';

export default function(results, preTag, postTag, tagName = 'em') {
if (!Array.isArray(results)) {
throw new TypeError('Results should be provided as an array.');
}

if (typeof preTag !== 'string' || typeof postTag !== 'string') {
throw new TypeError('preTag and postTag should be provided as strings.');
}

const sanitized = [];
for (const result of results) {
if ('_highlightResult' in result) {
result._highlightResult = sanitizeHighlights(
result._highlightResult,
preTag,
postTag,
tagName
);
}

if ('_snippetResult' in result) {
result._snippetResult = sanitizeHighlights(
result._snippetResult,
preTag,
postTag,
tagName
);
}

sanitized.push(result);
}

return sanitized;
}

const sanitizeHighlights = function(data, preTag, postTag, tagName) {
if (containsValue(data)) {
const sanitized = Object.assign({}, data, {
value: escapeHtml(data.value)
.replace(new RegExp(preTag, 'g'), `<${tagName}>`)
.replace(new RegExp(postTag, 'g'), `</${tagName}>`),
});

return sanitized;
}

if (Array.isArray(data)) {
const child = [];
data.forEach(item => {
child.push(sanitizeHighlights(item, preTag, postTag, tagName));
});

return child;
}

if (isObject(data)) {
const keys = Object.keys(data);
const child = {};
keys.forEach(key => {
child[key] = sanitizeHighlights(data[key], preTag, postTag, tagName);
});

return child;
}

return data;
};

const containsValue = function(data) {
return isObject(data) && 'matchLevel' in data && 'value' in data;
};

const isObject = value => typeof value === 'object' && value !== null;

0 comments on commit e78cacd

Please sign in to comment.