Переглянути джерело

Added back `escapeMarkup`

This is needed to escape any bad markup that is passed through
user-entered data. Users can prevent their markup from being
escaped by using a no-op `escapeMarkup` function.

This closes https://github.com/select2/select2/issues/2990.
Kevin Brown 10 роки тому
батько
коміт
5a0f7f5518

+ 23 - 3
dist/js/select2.amd.full.js

@@ -220,6 +220,22 @@ define(['jquery'], function ($) {define('select2/utils',[
       $el.innerWidth() < el.scrollWidth);
   };
 
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
   return Utils;
 });
 
@@ -698,13 +714,14 @@ define('select2/results',[
 
   Results.prototype.template = function (result, container) {
     var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
     var content = template(result);
 
     if (content == null) {
       container.style.display = 'none';
     } else {
-      container.innerHTML = content;
+      container.innerHTML = escapeMarkup(content);
     }
   };
 
@@ -942,8 +959,9 @@ define('select2/selection/single',[
 
   SingleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   SingleSelection.prototype.selectionContainer = function () {
@@ -1020,8 +1038,9 @@ define('select2/selection/multiple',[
 
   MultipleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   MultipleSelection.prototype.selectionContainer = function () {
@@ -3894,6 +3913,7 @@ define('select2/defaults',[
     this.defaults = {
       amdBase: 'select2/',
       amdLanguageBase: 'select2/i18n/',
+      escapeMarkup: Utils.escapeMarkup,
       language: EnglishTranslation,
       matcher: matcher,
       minimumInputLength: 0,

+ 23 - 3
dist/js/select2.amd.js

@@ -220,6 +220,22 @@ define(['jquery'], function ($) {define('select2/utils',[
       $el.innerWidth() < el.scrollWidth);
   };
 
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
   return Utils;
 });
 
@@ -698,13 +714,14 @@ define('select2/results',[
 
   Results.prototype.template = function (result, container) {
     var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
     var content = template(result);
 
     if (content == null) {
       container.style.display = 'none';
     } else {
-      container.innerHTML = content;
+      container.innerHTML = escapeMarkup(content);
     }
   };
 
@@ -942,8 +959,9 @@ define('select2/selection/single',[
 
   SingleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   SingleSelection.prototype.selectionContainer = function () {
@@ -1020,8 +1038,9 @@ define('select2/selection/multiple',[
 
   MultipleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   MultipleSelection.prototype.selectionContainer = function () {
@@ -3894,6 +3913,7 @@ define('select2/defaults',[
     this.defaults = {
       amdBase: 'select2/',
       amdLanguageBase: 'select2/i18n/',
+      escapeMarkup: Utils.escapeMarkup,
       language: EnglishTranslation,
       matcher: matcher,
       minimumInputLength: 0,

+ 23 - 3
dist/js/select2.full.js

@@ -658,6 +658,22 @@ define('select2/utils',[
       $el.innerWidth() < el.scrollWidth);
   };
 
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
   return Utils;
 });
 
@@ -1136,13 +1152,14 @@ define('select2/results',[
 
   Results.prototype.template = function (result, container) {
     var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
     var content = template(result);
 
     if (content == null) {
       container.style.display = 'none';
     } else {
-      container.innerHTML = content;
+      container.innerHTML = escapeMarkup(content);
     }
   };
 
@@ -1380,8 +1397,9 @@ define('select2/selection/single',[
 
   SingleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   SingleSelection.prototype.selectionContainer = function () {
@@ -1458,8 +1476,9 @@ define('select2/selection/multiple',[
 
   MultipleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   MultipleSelection.prototype.selectionContainer = function () {
@@ -4332,6 +4351,7 @@ define('select2/defaults',[
     this.defaults = {
       amdBase: 'select2/',
       amdLanguageBase: 'select2/i18n/',
+      escapeMarkup: Utils.escapeMarkup,
       language: EnglishTranslation,
       matcher: matcher,
       minimumInputLength: 0,

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/js/select2.full.min.js


+ 23 - 3
dist/js/select2.js

@@ -658,6 +658,22 @@ define('select2/utils',[
       $el.innerWidth() < el.scrollWidth);
   };
 
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
   return Utils;
 });
 
@@ -1136,13 +1152,14 @@ define('select2/results',[
 
   Results.prototype.template = function (result, container) {
     var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
     var content = template(result);
 
     if (content == null) {
       container.style.display = 'none';
     } else {
-      container.innerHTML = content;
+      container.innerHTML = escapeMarkup(content);
     }
   };
 
@@ -1380,8 +1397,9 @@ define('select2/selection/single',[
 
   SingleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   SingleSelection.prototype.selectionContainer = function () {
@@ -1458,8 +1476,9 @@ define('select2/selection/multiple',[
 
   MultipleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   MultipleSelection.prototype.selectionContainer = function () {
@@ -4332,6 +4351,7 @@ define('select2/defaults',[
     this.defaults = {
       amdBase: 'select2/',
       amdLanguageBase: 'select2/i18n/',
+      escapeMarkup: Utils.escapeMarkup,
       language: EnglishTranslation,
       matcher: matcher,
       minimumInputLength: 0,

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/js/select2.min.js


+ 2 - 0
docs/examples.html

@@ -229,6 +229,7 @@ $(".js-data-example-ajax").select2({
     },
     cache: true
   },
+  escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
   minimumInputLength: 1,
   templateResult: formatRepo, // omitted for brevity, see the source of this page
   templateSelection: formatRepoSelection // omitted for brevity, see the source of this page
@@ -949,6 +950,7 @@ $.fn.select2.amd.require(
       },
       cache: true
     },
+    escapeMarkup: function (markup) { return markup; },
     minimumInputLength: 1,
     templateResult: function (repo) {
       if (repo.loading) return repo.text;

+ 1 - 0
src/js/select2/defaults.js

@@ -309,6 +309,7 @@ define([
     this.defaults = {
       amdBase: 'select2/',
       amdLanguageBase: 'select2/i18n/',
+      escapeMarkup: Utils.escapeMarkup,
       language: EnglishTranslation,
       matcher: matcher,
       minimumInputLength: 0,

+ 2 - 1
src/js/select2/results.js

@@ -473,13 +473,14 @@ define([
 
   Results.prototype.template = function (result, container) {
     var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
     var content = template(result);
 
     if (content == null) {
       container.style.display = 'none';
     } else {
-      container.innerHTML = content;
+      container.innerHTML = escapeMarkup(content);
     }
   };
 

+ 2 - 1
src/js/select2/selection/multiple.js

@@ -52,8 +52,9 @@ define([
 
   MultipleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   MultipleSelection.prototype.selectionContainer = function () {

+ 2 - 1
src/js/select2/selection/single.js

@@ -65,8 +65,9 @@ define([
 
   SingleSelection.prototype.display = function (data) {
     var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
 
-    return template(data);
+    return escapeMarkup(template(data));
   };
 
   SingleSelection.prototype.selectionContainer = function () {

+ 16 - 0
src/js/select2/utils.js

@@ -220,5 +220,21 @@ define([
       $el.innerWidth() < el.scrollWidth);
   };
 
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
   return Utils;
 });

+ 22 - 0
tests/selection/multiple-tests.js

@@ -48,3 +48,25 @@ test('empty update clears the selection', function (assert) {
 
   assert.equal($rendered.text(), '');
 });
+
+test('escapePlaceholder is being used', function (assert) {
+  var selection = new MultipleSelection(
+    $('#qunit-fixture .multiple'),
+    options
+  );
+
+  var $selection = selection.render();
+  var $rendered = $selection.find('.select2-selection__rendered');
+
+  var unescapedText = '<script>bad("stuff");</script>';
+
+  selection.update([{
+    text: unescapedText
+  }]);
+
+  assert.equal(
+    $rendered.text().indexOf(unescapedText),
+    1,
+    'The text should be escaped by default to prevent injection'
+  );
+});

+ 22 - 0
tests/selection/single-tests.js

@@ -64,3 +64,25 @@ test('update renders the data text', function (assert) {
 
   assert.equal($rendered.text(), 'test');
 });
+
+test('escapePlaceholder is being used', function (assert) {
+  var selection = new SingleSelection(
+    $('#qunit-fixture .single'),
+    options
+  );
+
+  var $selection = selection.render();
+  var $rendered = $selection.find('.select2-selection__rendered');
+
+  var unescapedText = '<script>bad("stuff");</script>';
+
+  selection.update([{
+    text: unescapedText
+  }]);
+
+  assert.equal(
+    $rendered.text(),
+    unescapedText,
+    'The text should be escaped by default to prevent injection'
+  );
+});

+ 27 - 0
tests/utils/escapeMarkup-tests.js

@@ -0,0 +1,27 @@
+module('Utils - escapeMarkup');
+
+var Utils = require('select2/utils');
+
+test('text passes through', function (assert) {
+  var text = 'testing this';
+  var escaped = Utils.escapeMarkup(text);
+
+  assert.equal(text, escaped);
+});
+
+test('html tags are escaped', function (assert) {
+  var text = '<script>alert("bad");</script>';
+  var escaped = Utils.escapeMarkup(text);
+
+  assert.notEqual(text, escaped);
+  assert.equal(escaped.indexOf('<script>'), -1);
+});
+
+test('quotes are killed as well', function (assert) {
+  var text = 'testin\' these "quotes"';
+  var escaped = Utils.escapeMarkup(text);
+
+  assert.notEqual(text, escaped);
+  assert.equal(escaped.indexOf('\''), -1);
+  assert.equal(escaped.indexOf('"'), -1);
+});

+ 19 - 0
tests/utils/escapeMarkup.html

@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+  <head>
+    <link rel="stylesheet" href="../vendor/qunit-1.14.0.css" type="text/css" />
+    <link rel="stylesheet" href="../../dist/css/select2.css" type="text/css" />
+  </head>
+  <body>
+    <div id="qunit"></div>
+    <div id="qunit-fixture"></div>
+
+    <script src="../vendor/qunit-1.14.0.js" type="text/javascript"></script>
+    <script src="../../vendor/jquery-2.1.0.js" type="text/javascript"></script>
+    <script src="../../dist/js/select2.full.js" type="text/javascript"></script>
+
+    <script src="../helpers.js" type="text/javascript"></script>
+
+    <script src="escapeMarkup-tests.js" type="text/javascript"></script>
+  </body>
+</html>

Деякі файли не було показано, через те що забагато файлів було змінено