diff --git a/node/utils/ExportHtml.js b/node/utils/ExportHtml.js
index 7296012fc..afeafd3a9 100644
--- a/node/utils/ExportHtml.js
+++ b/node/utils/ExportHtml.js
@@ -19,6 +19,7 @@ var async = require("async");
var Changeset = CommonCode.require("/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
+var Security = CommonCode.require('/security');
function getPadPlainText(pad, revNum)
{
@@ -270,7 +271,7 @@ function getHTMLFromAtext(pad, atext)
//from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), "");
- assem.append(_escapeHTML(s));
+ assem.append(_encodeWhitespace(Security.escapeHTML(s)));
} // end iteration over spans in line
var tags2close = [];
@@ -293,7 +294,7 @@ function getHTMLFromAtext(pad, atext)
var url = urlData[1];
var urlLength = url.length;
processNextChars(startIndex - idx);
- assem.append('');
+ assem.append('');
processNextChars(urlLength);
assem.append('');
});
@@ -494,25 +495,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
});
}
-function _escapeHTML(s)
-{
- var re = /[&"<>]/g;
- if (!re.MAP)
- {
- // persisted across function calls!
- re.MAP = {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- };
- }
-
- s = s.replace(re, function (c)
- {
- return re.MAP[c];
- });
-
+function _encodeWhitespace(s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
{
return "" +c.charCodeAt(0) + ";"
diff --git a/node/utils/tar.json b/node/utils/tar.json
index 9bab03c07..e922dddeb 100644
--- a/node/utils/tar.json
+++ b/node/utils/tar.json
@@ -1,6 +1,7 @@
{
"pad.js": [
"jquery.js"
+ , "security.js"
, "pad.js"
, "ace2_common.js"
, "pad_utils.js"
@@ -25,6 +26,7 @@
]
, "timeslider.js": [
"jquery.js"
+ , "security.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
@@ -53,6 +55,7 @@
"ace2_common.js"
, "AttributePoolFactory.js"
, "Changeset.js"
+ , "security.js"
, "skiplist.js"
, "virtual_lines.js"
, "cssmanager.js"
diff --git a/static/js/ace2_common.js b/static/js/ace2_common.js
index 1ce7810aa..b4c72a92f 100644
--- a/static/js/ace2_common.js
+++ b/static/js/ace2_common.js
@@ -20,6 +20,7 @@
* limitations under the License.
*/
+var Security = require('/security');
function isNodeText(node)
{
@@ -137,14 +138,7 @@ function binarySearchInfinite(expectedLength, func)
function htmlPrettyEscape(str)
{
- return str.replace(/[&"<>]/g, function (c) {
- return {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- }[c] || c;
- }).replace(/\r?\n/g, '\\n');
+ return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
}
exports.isNodeText = isNodeText;
diff --git a/static/js/domline.js b/static/js/domline.js
index 8d8c2ea9e..15528bf7a 100644
--- a/static/js/domline.js
+++ b/static/js/domline.js
@@ -26,6 +26,7 @@
// requires: plugins
// requires: undefined
+var Security = require('/security');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
@@ -103,17 +104,17 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
if (listType)
{
listType = listType[1];
- start = start?'start="'+start[1]+'"':'';
+ start = start?'start="'+Security.escapeHTMLAttribute(start[1])+'"':'';
if (listType)
{
if(listType.indexOf("number") < 0)
{
- preHtml = '
- ';
+ preHtml = '';
}
else
{
- preHtml = '
- ';
+ preHtml = '
- ';
postHtml = '
';
}
}
@@ -168,7 +169,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
href = "http://"+href;
}
- extraOpenTags = extraOpenTags + '';
+ extraOpenTags = extraOpenTags + '';
extraCloseTags = '' + extraCloseTags;
}
if (simpleTags)
@@ -178,7 +179,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
simpleTags.reverse();
extraCloseTags = '' + simpleTags.join('>') + '>' + extraCloseTags;
}
- html.push('', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '');
+ html.push('', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '');
}
};
result.clearSpans = function()
@@ -224,27 +225,6 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
return result;
};
-domline.escapeHTML = function(s)
-{
- var re = /[&<>'"]/g;
- /']/; // stupid indentation thing
- if (!re.MAP)
- {
- // persisted across function calls!
- re.MAP = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
- }
- return s.replace(re, function(c)
- {
- return re.MAP[c];
- });
-};
-
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
diff --git a/static/js/pad_utils.js b/static/js/pad_utils.js
index 3ecc34bf8..fb538211f 100644
--- a/static/js/pad_utils.js
+++ b/static/js/pad_utils.js
@@ -20,6 +20,8 @@
* limitations under the License.
*/
+var Security = require('/security');
+
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
@@ -69,14 +71,7 @@ function readCookie(name)
var padutils = {
escapeHtml: function(x)
{
- return String(x).replace(/[&"<>]/g, function (c) {
- return {
- '&': '&',
- '"': '"',
- '<': '<',
- '>': '>'
- }[c] || c;
- });
+ return Security.escapeHTML(String(x));
},
uniqueId: function()
{
@@ -205,7 +200,7 @@ var padutils = {
{
if (i > idx)
{
- pieces.push(padutils.escapeHtml(text.substring(idx, i)));
+ pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i;
}
}
@@ -216,7 +211,7 @@ var padutils = {
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex);
- pieces.push('');
+ pieces.push('');
advanceTo(startIndex + href.length);
pieces.push('');
}
diff --git a/static/js/security.js b/static/js/security.js
new file mode 100644
index 000000000..6f42d0516
--- /dev/null
+++ b/static/js/security.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var HTML_ENTITY_MAP = {
+ '&': '&'
+, '<': '<'
+, '>': '>'
+, '"': '"'
+, "'": '''
+, '/': '/'
+};
+
+// OSWASP Guidlines: &, <, >, ", ' plus forward slash.
+var HTML_CHARACTERS_EXPRESSION = /[&"'<>\/]/g;
+function escapeHTML(text) {
+ return text && text.replace(HTML_CHARACTERS_EXPRESSION, function (c) {
+ return HTML_ENTITY_MAP[c] || c;
+ });
+}
+
+// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+var HTML_ATTRIBUTE_CHARACTERS_EXPRESSION =
+ /[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
+function escapeHTMLAttribute(text) {
+ return text && text.replace(HTML_ATTRIBUTE_CHARACTERS_EXPRESSION, function (c) {
+ return "" + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ";";
+ });
+};
+
+// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+var JAVASCRIPT_CHARACTERS_EXPRESSION =
+ /[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
+function escapeJavaScriptData(text) {
+ return text && text.replace(JAVASCRIPT_CHARACTERS_EXPRESSION, function (c) {
+ return "\\x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ });
+}
+
+exports.escapeHTML = escapeHTML;
+exports.escapeHTMLAttribute = escapeHTMLAttribute;
+exports.escapeJavaScriptData = escapeJavaScriptData;