diff --git a/README.md b/README.md
index 8bc33a9..3179e86 100755
--- a/README.md
+++ b/README.md
@@ -377,6 +377,22 @@ TabularTables.Books = new Tabular.Table({
If you need to be sure that certain fields are never published or if different users can access different fields, use `allowFields`. Otherwise just use `allow`.
+*XSS: Tabular sanitizes strings completely and objects partially. If you are trying to render an object as data, you should take required actions to prevent XSS.*
+```js
+{
+ title: 'Customer',
+ data: 'customer', // an object
+ render(data){
+ if (!data) {
+ return '';
+ }
+
+ const name = yourSanitizeFunction(data.name);
+ return `${name}}`;
+ }
+}
+```
+
## Caching the Documents
By default, a normal `Meteor.subscribe` is used for the current page's table data. This subscription is stopped and a new one replaces it whenever you switch pages. This means that if your table shows 10 results per page, your client collection will have 10 documents in it on page 1. When you switch to page 2, your client collection will still have only 10 documents in it, but they will be the next 10.
diff --git a/client/main.js b/client/main.js
index 0d59740..2dd55de 100755
--- a/client/main.js
+++ b/client/main.js
@@ -35,6 +35,76 @@ Tabular.getRecord = function (name, collection) {
return Tabular.getTableRecordsCollection(collection._connection).findOne(name);
};
+Tabular.sanitize = function (data) {
+ const _sanitizeJson = (unsafe) => {
+ if (typeof unsafe !== 'string') {
+ return unsafe;
+ }
+
+ return unsafe.replace(/[&<"']/g, (match) => {
+ switch (match) {
+ case '&':
+ return '&';
+ case '<':
+ return '<';
+ case '>':
+ return '>';
+ default:
+ return match;
+ }
+ });
+ };
+
+ const _sanitizeString = (unsafe) => {
+ return unsafe.replace(/[&<"']/g, (match) => {
+ switch (match) {
+ case '&':
+ return '&';
+ case '<':
+ return '<';
+ case '>':
+ return '>';
+ case '"':
+ return '"';
+ case "'":
+ return ''';
+ default:
+ return match;
+ }
+ });
+ };
+
+ const _isJson = function (value) {
+ value = typeof value !== 'string'
+ ? JSON.stringify(value)
+ : value;
+
+ try {
+ value = JSON.parse(value);
+ } catch (e) {
+ return false;
+ }
+
+ return typeof value === 'object' && value !== null;
+ };
+
+ const sanitizeData = (unsafeArray) => {
+ unsafeArray.forEach(unsafeObject => {
+ Object.keys(unsafeObject).forEach(key => {
+ if (_isJson(unsafeObject[key])) {
+ unsafeObject[key] = _sanitizeJson(unsafeObject[key]);
+ } else if (typeof unsafeObject[key] === 'string') {
+ unsafeObject[key] = _sanitizeString(unsafeObject[key]);
+ }
+ });
+ });
+
+ return unsafeArray;
+ };
+
+ return sanitizeData(data);
+};
+
Template.tabular.helpers({
atts() {
// We remove the "table" and "selector" attributes and assume the rest belong
@@ -85,6 +155,7 @@ Template.tabular.onRendered(function () {
// the first subscription, which will then trigger the
// second subscription.
+ template.tabular.data = Tabular.sanitize(template.tabular.data);
//console.log('data', template.tabular.data);
// Update skip
@@ -187,7 +258,6 @@ Template.tabular.onRendered(function () {
var data = Template.currentData();
//console.log('currentData autorun', data);
-
// if we don't have data OR the selector didn't actually change return out
if (!data || (data.selector && template.tabular.selector === data.selector)) return;
diff --git a/package.js b/package.js
index dcecf31..337c147 100755
--- a/package.js
+++ b/package.js
@@ -63,6 +63,7 @@ Package.onTest(function(api) {
api.addFiles('tests/reusedFunctions.js', 'client');
api.addFiles([
'tests/util.js',
+ 'tests/sanitize.js',
'tests/mongoDBQuery.js',
'tests/utilIntegration.js'
], 'client' );
diff --git a/tests/sanitize.js b/tests/sanitize.js
new file mode 100644
index 0000000..5771306
--- /dev/null
+++ b/tests/sanitize.js
@@ -0,0 +1,12 @@
+import Tabular from "../common/Tabular.js";
+
+Tinytest.add('Sanitize - clear data of an array object', function (test) {
+ const data = [{
+ name: ``,
+ surname: 'doe'
+ }]
+
+ const sanitizedData = Tabular.sanitize(data);
+
+ LogResults(data, "<script>alert("xss")</script>", sanitizedData[0].name, test)
+})