You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by br...@apache.org on 2019/05/30 09:20:48 UTC

[jspwiki] branch master updated: 2.11.0-M5-git-03 [JSPWIKI-1112] Changenote/Comment signature vulnerabilities

This is an automated email from the ASF dual-hosted git repository.

brushed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jspwiki.git


The following commit(s) were added to refs/heads/master by this push:
     new f77ecab  2.11.0-M5-git-03    [JSPWIKI-1112] Changenote/Comment signature vulnerabilities
f77ecab is described below

commit f77ecab5d7809adc6eac37dd2d9b779b030b87ee
Author: brushed <di...@gmail.com>
AuthorDate: Thu May 30 11:20:33 2019 +0200

    2.11.0-M5-git-03    [JSPWIKI-1112] Changenote/Comment signature vulnerabilities
---
 ChangeLog                                          |  12 +-
 .../src/main/java/org/apache/wiki/Release.java     |   2 +-
 jspwiki-war/src/main/config/wro/wro-haddock.xml    |   7 +-
 .../src/main/scripts/behaviors/Collapsible.js      |  42 ++-
 .../src/main/scripts/lib/mootools-core-1.6.0.js    | 206 -------------
 .../src/main/scripts/lib/mootools-more-1.6.0.js    | 325 +--------------------
 jspwiki-war/src/main/scripts/util/polyfills.js     |  52 ++++
 .../src/main/scripts/wiki-edit/Wiki.Edit.js        |   2 +-
 .../src/main/scripts/wiki/Wiki.Behaviors.js        |   6 +-
 jspwiki-war/src/main/webapp/Comment.jsp            |   2 +-
 .../webapp/templates/default/AttachmentTab.jsp     |   4 +-
 .../main/webapp/templates/default/InfoContent.jsp  |   6 +-
 12 files changed, 110 insertions(+), 556 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 57c7ec1..8b5b21f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2019-05-28  Dirk Frederickx (brushed AT apache DOT org)
 
+       * 2.11.0-M5-git-03
+
+        * [JSPWIKI-1112] EDITOR input fields (changenote,comment-signature )
+          vulnerable to XSS.
+
+       * [JSPWIKI-1097] Minor JS updates (cookie handling, %%collapse)
+
+
+2019-05-28  Dirk Frederickx (brushed AT apache DOT org)
+
        * 2.11.0-M5-git-02
 
         * Improved styling of the WYSIWYG editor toolbar
@@ -10,7 +20,7 @@
          You can now install JSPWIKI on the homescreen of your mobile device or tablet
          for quicker access and improve experience.
 
-       * [JSPWIKI-1097] JS updates to replace mootools js  (cookie handling)
+       * [JSPWIKI-1097] JS updates to start replacing mootools.js  (cookie handling)
 
 
 2019-05-17  Dirk Frederickx (brushed AT apache DOT org)
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/Release.java b/jspwiki-main/src/main/java/org/apache/wiki/Release.java
index 3e5d980..f20e5f3 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/Release.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/Release.java
@@ -72,7 +72,7 @@ public final class Release {
      *  <p>
      *  If the build identifier is empty, it is not added.
      */
-    public static final String     BUILD         = "02";
+    public static final String     BUILD         = "03";
 
     /**
      *  This is the generic version string you should use when printing out the version.  It is of
diff --git a/jspwiki-war/src/main/config/wro/wro-haddock.xml b/jspwiki-war/src/main/config/wro/wro-haddock.xml
index c415f71..240751d 100644
--- a/jspwiki-war/src/main/config/wro/wro-haddock.xml
+++ b/jspwiki-war/src/main/config/wro/wro-haddock.xml
@@ -27,6 +27,7 @@
     <js>/scripts/lib/mootools-more-1.6.0.js</js>
     <js>/scripts/lib/prettify4mar13.js</js>
     <js>/scripts/util/cookies.js</js>
+    <js>/scripts/util/polyfill.js</js>
   </group>
 
   <group name="mooextend" abstract="true">
@@ -34,13 +35,13 @@
     <js>/scripts/moo-extend/Behavior.js</js>
     <js>/scripts/moo-extend/String.Extend.js</js>
     <!--<js>/scripts/moo-extend/Date.Extend.js</js>-->
+    <js>/scripts/moo-extend/Cookie.Flags.js</js>
     <js>/scripts/moo-extend/Array.Extend.js</js>
     <js>/scripts/moo-extend/Array.NaturalSort.js</js>
     <js>/scripts/moo-extend/Element.Extend.js</js>
     <js>/scripts/moo-extend/Color.js</js>
     <js>/scripts/moo-extend/HighlightQuery.js</js>
     <js>/scripts/moo-extend/Accesskey.js</js>
-    <js>/scripts/moo-extend/Cookie.Flags.js</js>
     <js>/scripts/moo-extend/Tips.js</js>
     <js>/scripts/moo-extend/Request.File.js</js>
     <js>/scripts/moo-extend/Form.MultipleFile.js</js>
@@ -76,9 +77,9 @@
     <js>/scripts/wiki/Search.js</js>
     <js>/scripts/wiki/Recents.js</js>
     <js>/scripts/wiki/Findpages.js</js>
-    <js>/scripts/wiki/Category.js</js>
-    <js>/scripts/wiki/Wiki.Behaviors.js</js>
     <js>/scripts/wiki/Prefs.js</js>
+    <js>/scripts/wiki/Wiki.Behaviors.js</js>
+    <js>/scripts/wiki/Category.js</js>
 
     <css>/styles/haddock/default/build.less</css>
   </group>
diff --git a/jspwiki-war/src/main/scripts/behaviors/Collapsible.js b/jspwiki-war/src/main/scripts/behaviors/Collapsible.js
index 9f15607..1fcacdf 100644
--- a/jspwiki-war/src/main/scripts/behaviors/Collapsible.js
+++ b/jspwiki-war/src/main/scripts/behaviors/Collapsible.js
@@ -74,10 +74,10 @@ var TCollapsible = this.Collapsible = new Class({
         open: "xpand",
         close: "clpse",
 
-        //cookie: null,    //Cookie.Flags - persist the state of the targets
+        //cookie: null,    //cookie-parameters persist the state of the targets
         //target: "ul,ol", //the elements which will expand/collapse
         //nested: "li",    //(optional) css selector of nested container elements
-        //collapsed: "ol", //css selector to check if default state is collapsed
+        expand: "ul", //css selector to check if default state is expanded or collapse (collapsed == OL)
 
         fx: "height",    //style attribute to animate on collapse
         fxy: "y",        //scroll direction to animate on collapse,
@@ -91,6 +91,11 @@ var TCollapsible = this.Collapsible = new Class({
         self.element = element = document.getElement(element);
         //note: setOptions() makes a copy of all objects, so first copy the cookie!
         self.cookie = options && options.cookie;
+        self.nodes = [];
+        self.states = ( (self.cookie && $.cookie(self.cookie)) || "").split("");
+
+        //console.log(self.cookie, self.nodes,self.states.join(''));
+
         options = self.setOptions(options).options;
 
         if( options.nested ){
@@ -123,7 +128,7 @@ var TCollapsible = this.Collapsible = new Class({
             bullet = element.getElement(bullet) || bullet.slick().inject(element,"top");
             target = element.getElement(options.target);
 
-            if( target && (target.get("text").trim()!="") ){
+            if( target && (target.textContent.trim()!="") ){
 
                 //console.log("FX tween",bullet,target,self.initState(element,target));
                 if( options.fx ){
@@ -144,15 +149,22 @@ var TCollapsible = this.Collapsible = new Class({
     },
 
     //function initState: returns true:expanded; false:collapsed
-    //cookies always overwrite the initial state
+    //state from cookie
     initState:function( element, target ){
 
-        var cookie = this.cookie,
-            isCollapsed = this.options.collapsed;
+        var self = this,
+            expand = target.matches(self.options.expand),
+            nodes = self.nodes,
+            states = self.states,
+            offset = nodes.length;
 
-        isCollapsed = !(isCollapsed && target.match(isCollapsed) );
+        if( offset < states.length ){
+            expand = (states[offset] == 'T');
+        }
+        self.nodes[offset] = element;
+        states[offset] = expand ? "T":"F";
 
-        return cookie ? cookie.get(target, isCollapsed) : isCollapsed;
+        return expand;
     },
 
     //function getState: returns true:expanded, false:collapsed
@@ -169,15 +181,23 @@ var TCollapsible = this.Collapsible = new Class({
             options = self.options,
             nested = options.nested,
             element = nested ? bullet.getParent(nested) : self.element,
-            target, state;
+            target, state, offset;
 
         if( element ){
             target = element.getElement(options.target);
 
             if( target ){
-                state = !self.getState(target);
+                state = !self.getState(target); //toggle state
                 self.update( bullet, target, state );
-                if( cookie ){ cookie.write(target, state); }
+
+                if( cookie ){
+                    offset = self.nodes.indexOf(element);
+                    if( offset >= 0 ){
+                        self.states[offset] = (state ? "T" : "F");
+                        console.log("write",cookie,self.states.join(""))
+                        $.cookie( cookie, self.states.join("") ); //write cookie
+                    }
+                }
             }
         }
     },
diff --git a/jspwiki-war/src/main/scripts/lib/mootools-core-1.6.0.js b/jspwiki-war/src/main/scripts/lib/mootools-core-1.6.0.js
index 6104e7f..9c0f284 100644
--- a/jspwiki-war/src/main/scripts/lib/mootools-core-1.6.0.js
+++ b/jspwiki-war/src/main/scripts/lib/mootools-core-1.6.0.js
@@ -6069,212 +6069,6 @@ Element.implement({
 
 });
 
-/*
----
-
-name: JSON
-
-description: JSON encoder and decoder.
-
-license: MIT-style license.
-
-SeeAlso: <http://www.json.org/>
-
-requires: [Array, String, Number, Function]
-
-provides: JSON
-
-...
-*/
-
-if (typeof JSON == 'undefined') this.JSON = {};
-
-
-
-(function(){
-
-var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
-
-var escape = function(chr){
-	return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
-};
-
-JSON.validate = function(string){
-	string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
-					replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
-					replace(/(?:^|:|,)(?:\s*\[)+/g, '');
-
-	return (/^[\],:{}\s]*$/).test(string);
-};
-
-JSON.encode = JSON.stringify ? function(obj){
-	return JSON.stringify(obj);
-} : function(obj){
-	if (obj && obj.toJSON) obj = obj.toJSON();
-
-	switch (typeOf(obj)){
-		case 'string':
-			return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
-		case 'array':
-			return '[' + obj.map(JSON.encode).clean() + ']';
-		case 'object': case 'hash':
-			var string = [];
-			Object.each(obj, function(value, key){
-				var json = JSON.encode(value);
-				if (json) string.push(JSON.encode(key) + ':' + json);
-			});
-			return '{' + string + '}';
-		case 'number': case 'boolean': return '' + obj;
-		case 'null': return 'null';
-	}
-
-	return null;
-};
-
-JSON.secure = true;
-
-
-JSON.decode = function(string, secure){
-	if (!string || typeOf(string) != 'string') return null;
-
-	if (secure == null) secure = JSON.secure;
-	if (secure){
-		if (JSON.parse) return JSON.parse(string);
-		if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
-	}
-
-	return eval('(' + string + ')');
-};
-
-})();
-
-/*
----
-
-name: Request.JSON
-
-description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
-
-license: MIT-style license.
-
-requires: [Request, JSON]
-
-provides: Request.JSON
-
-...
-*/
-
-Request.JSON = new Class({
-
-	Extends: Request,
-
-	options: {
-		/*onError: function(text, error){},*/
-		secure: true
-	},
-
-	initialize: function(options){
-		this.parent(options);
-		Object.append(this.headers, {
-			'Accept': 'application/json',
-			'X-Request': 'JSON'
-		});
-	},
-
-	success: function(text){
-		var json;
-		try {
-			json = this.response.json = JSON.decode(text, this.options.secure);
-		} catch (error){
-			this.fireEvent('error', [text, error]);
-			return;
-		}
-		if (json == null){
-			this.failure();
-		} else {
-			this.onSuccess(json, text);
-			this.resolve({json: json, text: text});
-		}
-	}
-
-});
-
-/*
----
-
-name: Cookie
-
-description: Class for creating, reading, and deleting browser Cookies.
-
-license: MIT-style license.
-
-credits:
-  - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
-
-requires: [Options, Browser]
-
-provides: Cookie
-
-...
-*/
-
-var Cookie = new Class({
-
-	Implements: Options,
-
-	options: {
-		path: '/',
-		domain: false,
-		duration: false,
-		secure: false,
-		document: document,
-		encode: true,
-		httpOnly: false
-	},
-
-	initialize: function(key, options){
-		this.key = key;
-		this.setOptions(options);
-	},
-
-	write: function(value){
-		if (this.options.encode) value = encodeURIComponent(value);
-		if (this.options.domain) value += '; domain=' + this.options.domain;
-		if (this.options.path) value += '; path=' + this.options.path;
-		if (this.options.duration){
-			var date = new Date();
-			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
-			value += '; expires=' + date.toGMTString();
-		}
-		if (this.options.secure) value += '; secure';
-		if (this.options.httpOnly) value += '; HttpOnly';
-		this.options.document.cookie = this.key + '=' + value;
-		return this;
-	},
-
-	read: function(){
-		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
-		return (value) ? decodeURIComponent(value[1]) : null;
-	},
-
-	dispose: function(){
-		new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
-		return this;
-	}
-
-});
-
-Cookie.write = function(key, value, options){
-	return new Cookie(key, options).write(value);
-};
-
-Cookie.read = function(key){
-	return new Cookie(key).read();
-};
-
-Cookie.dispose = function(key, options){
-	return new Cookie(key, options).dispose();
-};
 
 /*
 ---
diff --git a/jspwiki-war/src/main/scripts/lib/mootools-more-1.6.0.js b/jspwiki-war/src/main/scripts/lib/mootools-more-1.6.0.js
index 2cd85ef..8341800 100644
--- a/jspwiki-war/src/main/scripts/lib/mootools-more-1.6.0.js
+++ b/jspwiki-war/src/main/scripts/lib/mootools-more-1.6.0.js
@@ -1,4 +1,4 @@
-/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2016 [Valerio Proietti](http://mad4milk.net/).*/ 
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2016 [Valerio Proietti](http://mad4milk.net/).*/
 /*!
 Web Build: http://mootools.net/more/builder/3ad489866f41084fa4f33070311ab35a
 */
@@ -933,326 +933,3 @@ Fx.Accordion = new Class({
 
 });
 
-
-
-/*
----
-
-name: Hash
-
-description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
-
-license: MIT-style license.
-
-requires:
-  - Core/Object
-  - MooTools.More
-
-provides: [Hash]
-
-...
-*/
-
-(function(){
-
-if (this.Hash) return;
-
-var Hash = this.Hash = new Type('Hash', function(object){
-	if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
-	for (var key in object) this[key] = object[key];
-	return this;
-});
-
-this.$H = function(object){
-	return new Hash(object);
-};
-
-Hash.implement({
-
-	forEach: function(fn, bind){
-		Object.forEach(this, fn, bind);
-	},
-
-	getClean: function(){
-		var clean = {};
-		for (var key in this){
-			if (this.hasOwnProperty(key)) clean[key] = this[key];
-		}
-		return clean;
-	},
-
-	getLength: function(){
-		var length = 0;
-		for (var key in this){
-			if (this.hasOwnProperty(key)) length++;
-		}
-		return length;
-	}
-
-});
-
-Hash.alias('each', 'forEach');
-
-Hash.implement({
-
-	has: Object.prototype.hasOwnProperty,
-
-	keyOf: function(value){
-		return Object.keyOf(this, value);
-	},
-
-	hasValue: function(value){
-		return Object.contains(this, value);
-	},
-
-	extend: function(properties){
-		Hash.each(properties || {}, function(value, key){
-			Hash.set(this, key, value);
-		}, this);
-		return this;
-	},
-
-	combine: function(properties){
-		Hash.each(properties || {}, function(value, key){
-			Hash.include(this, key, value);
-		}, this);
-		return this;
-	},
-
-	erase: function(key){
-		if (this.hasOwnProperty(key)) delete this[key];
-		return this;
-	},
-
-	get: function(key){
-		return (this.hasOwnProperty(key)) ? this[key] : null;
-	},
-
-	set: function(key, value){
-		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
-		return this;
-	},
-
-	empty: function(){
-		Hash.each(this, function(value, key){
-			delete this[key];
-		}, this);
-		return this;
-	},
-
-	include: function(key, value){
-		if (this[key] == undefined) this[key] = value;
-		return this;
-	},
-
-	map: function(fn, bind){
-		return new Hash(Object.map(this, fn, bind));
-	},
-
-	filter: function(fn, bind){
-		return new Hash(Object.filter(this, fn, bind));
-	},
-
-	every: function(fn, bind){
-		return Object.every(this, fn, bind);
-	},
-
-	some: function(fn, bind){
-		return Object.some(this, fn, bind);
-	},
-
-	getKeys: function(){
-		return Object.keys(this);
-	},
-
-	getValues: function(){
-		return Object.values(this);
-	},
-
-	toQueryString: function(base){
-		return Object.toQueryString(this, base);
-	}
-
-});
-
-Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
-
-
-})();
-
-
-/*
----
-
-script: Hash.Cookie.js
-
-name: Hash.Cookie
-
-description: Class for creating, reading, and deleting Cookies in JSON format.
-
-license: MIT-style license
-
-authors:
-  - Valerio Proietti
-  - Aaron Newton
-
-requires:
-  - Core/Cookie
-  - Core/JSON
-  - MooTools.More
-  - Hash
-
-provides: [Hash.Cookie]
-
-...
-*/
-
-Hash.Cookie = new Class({
-
-	Extends: Cookie,
-
-	options: {
-		autoSave: true
-	},
-
-	initialize: function(name, options){
-		this.parent(name, options);
-		this.load();
-	},
-
-	save: function(){
-		var value = JSON.encode(this.hash);
-		if (!value || value.length > 4096) return false; //cookie would be truncated!
-		if (value == '{}') this.dispose();
-		else this.write(value);
-		return true;
-	},
-
-	load: function(){
-		this.hash = new Hash(JSON.decode(this.read(), true));
-		return this;
-	}
-
-});
-
-Hash.each(Hash.prototype, function(method, name){
-	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
-		var value = method.apply(this.hash, arguments);
-		if (this.options.autoSave) this.save();
-		return value;
-	});
-});
-
-/*
----
-
-name: Swiff
-
-description: Wrapper for embedding SWF movies. Supports External Interface Communication.
-
-license: MIT-style license.
-
-credits:
-  - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
-
-requires: [Core/Options, Core/Object, Core/Element]
-
-provides: Swiff
-
-...
-*/
-
-(function(){
-
-var Swiff = this.Swiff = new Class({
-
-	Implements: Options,
-
-	options: {
-		id: null,
-		height: 1,
-		width: 1,
-		container: null,
-		properties: {},
-		params: {
-			quality: 'high',
-			allowScriptAccess: 'always',
-			wMode: 'window',
-			swLiveConnect: true
-		},
-		callBacks: {},
-		vars: {}
-	},
-
-	toElement: function(){
-		return this.object;
-	},
-
-	initialize: function(path, options){
-		this.instance = 'Swiff_' + String.uniqueID();
-
-		this.setOptions(options);
-		options = this.options;
-		var id = this.id = options.id || this.instance;
-		var container = document.id(options.container);
-
-		Swiff.CallBacks[this.instance] = {};
-
-		var params = options.params, vars = options.vars, callBacks = options.callBacks;
-		var properties = Object.append({height: options.height, width: options.width}, options.properties);
-
-		var self = this;
-
-		for (var callBack in callBacks){
-			Swiff.CallBacks[this.instance][callBack] = (function(option){
-				return function(){
-					return option.apply(self.object, arguments);
-				};
-			})(callBacks[callBack]);
-			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
-		}
-
-		params.flashVars = Object.toQueryString(vars);
-		if ('ActiveXObject' in window){
-			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
-			params.movie = path;
-		} else {
-			properties.type = 'application/x-shockwave-flash';
-		}
-		properties.data = path;
-
-		var build = '<object id="' + id + '"';
-		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
-		build += '>';
-		for (var param in params){
-			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
-		}
-		build += '</object>';
-		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
-	},
-
-	replaces: function(element){
-		element = document.id(element, true);
-		element.parentNode.replaceChild(this.toElement(), element);
-		return this;
-	},
-
-	inject: function(element){
-		document.id(element, true).appendChild(this.toElement());
-		return this;
-	},
-
-	remote: function(){
-		return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments));
-	}
-
-});
-
-Swiff.CallBacks = {};
-
-Swiff.remote = function(obj, fn){
-	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
-	return eval(rs);
-};
-
-})();
diff --git a/jspwiki-war/src/main/scripts/util/polyfills.js b/jspwiki-war/src/main/scripts/util/polyfills.js
new file mode 100644
index 0000000..077d19d
--- /dev/null
+++ b/jspwiki-war/src/main/scripts/util/polyfills.js
@@ -0,0 +1,52 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); fyou 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.
+*/
+/*eslint-env browser */
+
+/*
+Polyfill  IE11
+*/
+
+//https://github.com/jserz/js_piece/blob/master/DOM/ChildNode/remove()/remove().md
+(function (ElementRemove, TextRemove) {
+    function remove() { this.parentNode && this.parentNode.removeChild(this); }
+
+    if (!ElementRemove) ElementRemove = remove;
+    if (Text && !TextRemove) TextRemove = remove;
+
+})(Element.prototype.remove, Text.prototype.remove);
+
+
+if (!Element.prototype.matches) {
+    Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector
+}
+
+//https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+//https://github.com/jonathantneal/closest/blob/master/src/index.js
+if (!Element.prototype.closest) {
+    Element.prototype.closest = function (selector) {
+        var element = this;
+
+        while (element && (element.nodeType === 1) && !element.matches(selector)) {
+            element = element.parentNode;
+        }
+        return element ? element : null;
+    }
+}
diff --git a/jspwiki-war/src/main/scripts/wiki-edit/Wiki.Edit.js b/jspwiki-war/src/main/scripts/wiki-edit/Wiki.Edit.js
index c8088a5..618d43a 100644
--- a/jspwiki-war/src/main/scripts/wiki-edit/Wiki.Edit.js
+++ b/jspwiki-war/src/main/scripts/wiki-edit/Wiki.Edit.js
@@ -97,7 +97,7 @@ wiki.add("textarea#editorarea", function( main ){
             var cache = localStorage.getItem(LocalCache),
                 modal = getFormElem(".localstorage");
 
-            modal.grab("pre".slick({text:cache}) )
+            modal.appendChild("pre".slick({text:cache}) )
                 .openModal( function(){
                     snipe.set("value", cache);
                 });
diff --git a/jspwiki-war/src/main/scripts/wiki/Wiki.Behaviors.js b/jspwiki-war/src/main/scripts/wiki/Wiki.Behaviors.js
index fd4a27a..b2ea887 100644
--- a/jspwiki-war/src/main/scripts/wiki/Wiki.Behaviors.js
+++ b/jspwiki-war/src/main/scripts/wiki/Wiki.Behaviors.js
@@ -190,7 +190,7 @@ Behavior:Alert (based on Bootstrap)
 */
     .add(".alert", function(element){
 
-        element.addClass("alert-dismissable").grab(
+        element.addClass("alert-dismissable").appendChild(
 
             "button.close[type=button][html=&times;]".slick()
                 .addEvent("click", function(){
@@ -209,7 +209,7 @@ Behavior: Quote (based on Bootstrap)
 */
     .add(".quote", function(element){
 
-        "blockquote".slick().wraps( "p".slick().wraps(element) );
+        "blockquote".slick().wraps( element );
 
     })
 
@@ -551,7 +551,7 @@ Behavior: Table behaviors
 
     .add(".sortable,div[class*=table-]", function(element){
 
-        element.ifClass(element.hasClass("sortable"), "table-sort");
+        element.ifClass(element.matches(".sortable"), "table-sort");
 
         var args = "table".sliceArgs(element),
             arg,
diff --git a/jspwiki-war/src/main/webapp/Comment.jsp b/jspwiki-war/src/main/webapp/Comment.jsp
index dfbf892..fee11bc 100644
--- a/jspwiki-war/src/main/webapp/Comment.jsp
+++ b/jspwiki-war/src/main/webapp/Comment.jsp
@@ -224,7 +224,7 @@
         {
             if( link != null )
             {
-                Cookie linkcookie = new Cookie("link", link);
+                Cookie linkcookie = new Cookie("link", TextUtil.urlEncodeUTF8(link) );
                 linkcookie.setMaxAge(1001*24*60*60);
                 response.addCookie( linkcookie );
             }
diff --git a/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp b/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
index 3cff055..dd61fbe 100644
--- a/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
+++ b/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
@@ -122,7 +122,7 @@
       <c:set var="type" value="${ fn:length(parts)>1 ? fn:escapeXml(parts[fn:length(parts)-1]) : ''}" />
 
       <td class="attach-name" title="${att.fileName}">
-        <wiki:LinkTo>${fn:escapeXml(att.fileName)}</wiki:LinkTo>
+        <wiki:LinkTo><c:out value="${att.fileName}" /></wiki:LinkTo>
       </td>
 
       <td><wiki:PageVersion /></td>
@@ -153,7 +153,7 @@
       </td>
 
       <c:set var="changenote" value="<%= (String)att.getAttribute( WikiPage.CHANGENOTE ) %>" />
-      <td class="changenote">${changenote}</td>
+      <td class="changenote"><c:out value="${changenote}"/></td>
 
     </tr>
     </wiki:AttachmentsIterator>
diff --git a/jspwiki-war/src/main/webapp/templates/default/InfoContent.jsp b/jspwiki-war/src/main/webapp/templates/default/InfoContent.jsp
index cf545e6..cc14d6e 100644
--- a/jspwiki-war/src/main/webapp/templates/default/InfoContent.jsp
+++ b/jspwiki-war/src/main/webapp/templates/default/InfoContent.jsp
@@ -204,7 +204,7 @@
         </td>
 
         <c:set var="changenote" value="<%= (String)currentPage.getAttribute( WikiPage.CHANGENOTE ) %>" />
-        <td class="changenote">${changenote}</td>
+        <td class="changenote"><c:out value="${changenote}"/></td>
 
       </tr>
       </c:if>
@@ -340,7 +340,7 @@
     <wiki:HistoryIterator id="att"><%-- <wiki:AttachmentsIterator id="att"> --%>
     <tr>
 
-      <td class="attach-name"><wiki:LinkTo version="${att.version}">${fn:escapeXml(att.fileName)}</wiki:LinkTo></td>
+      <td class="attach-name"><wiki:LinkTo version="${att.version}"><c:out value="${att.fileName}" /></wiki:LinkTo></td>
 
       <td><wiki:PageVersion /></td>
 
@@ -370,7 +370,7 @@
       --%>
 
       <c:set var="changenote" value="<%= (String)att.getAttribute( WikiPage.CHANGENOTE ) %>" />
-      <td class="changenote">${changenote}</td>
+        <td class="changenote"><c:out value="${changenote}"/></td>
 
     </tr>
     </wiki:HistoryIterator><%-- </wiki:AttachmentsIterator> --%>