2011-12-04 16:33:56 +01:00
|
|
|
/**
|
2019-04-16 00:34:29 +02:00
|
|
|
* This code is mostly from the old Etherpad. Please help us to comment this code.
|
2011-12-04 16:33:56 +01:00
|
|
|
* This helps other people to understand this code better and helps them to improve it.
|
|
|
|
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
|
|
|
|
*/
|
|
|
|
|
2011-03-26 14:10:41 +01:00
|
|
|
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
|
|
|
|
// %APPJET%: import("etherpad.admin.plugins");
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// requires: top
|
|
|
|
// requires: plugins
|
|
|
|
// requires: undefined
|
2012-01-16 06:52:03 +01:00
|
|
|
|
2012-03-07 02:27:03 +01:00
|
|
|
var Security = require('./security');
|
2012-03-11 00:08:09 +01:00
|
|
|
var hooks = require('./pluginfw/hooks');
|
2012-03-17 13:36:42 +01:00
|
|
|
var _ = require('./underscore');
|
2012-04-07 02:12:42 +02:00
|
|
|
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
2013-02-03 15:03:10 +01:00
|
|
|
var noop = function(){};
|
2012-03-17 13:36:42 +01:00
|
|
|
|
2012-01-16 06:52:03 +01:00
|
|
|
|
2011-03-26 14:10:41 +01:00
|
|
|
var domline = {};
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
domline.addToLineClass = function(lineClass, cls)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// an "empty span" at any point can be used to add classes to
|
|
|
|
// the line, using line:className. otherwise, we ignore
|
|
|
|
// the span.
|
2011-07-07 19:59:34 +02:00
|
|
|
cls.replace(/\S+/g, function(c)
|
|
|
|
{
|
|
|
|
if (c.indexOf("line:") == 0)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// add class to line
|
2011-07-07 19:59:34 +02:00
|
|
|
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return lineClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if "document" is falsy we don't create a DOM node, just
|
|
|
|
// an object with innerHTML and className
|
2011-07-07 19:59:34 +02:00
|
|
|
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
|
|
|
|
{
|
|
|
|
var result = {
|
|
|
|
node: null,
|
2012-02-19 14:11:32 +01:00
|
|
|
appendSpan: noop,
|
|
|
|
prepareForAdd: noop,
|
|
|
|
notifyAdded: noop,
|
|
|
|
clearSpans: noop,
|
|
|
|
finishUpdate: noop,
|
2011-07-07 19:59:34 +02:00
|
|
|
lineMarker: 0
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
var document = optDocument;
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
if (document)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
result.node = document.createElement("div");
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
result.node = {
|
|
|
|
innerHTML: '',
|
|
|
|
className: ''
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var html = [];
|
2015-12-23 15:35:52 +01:00
|
|
|
var preHtml = '',
|
2012-04-07 02:12:42 +02:00
|
|
|
postHtml = '';
|
2011-03-26 14:10:41 +01:00
|
|
|
var curHTML = null;
|
2011-07-07 19:59:34 +02:00
|
|
|
|
|
|
|
function processSpaces(s)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
return domline.processSpaces(s, doesWrap);
|
|
|
|
}
|
2012-02-19 14:11:32 +01:00
|
|
|
|
2012-03-17 13:36:42 +01:00
|
|
|
var perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
|
|
|
|
var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
|
2011-03-26 14:10:41 +01:00
|
|
|
var lineClass = 'ace-line';
|
2015-01-21 15:25:24 +01:00
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
result.appendSpan = function(txt, cls)
|
|
|
|
{
|
2015-01-21 15:25:24 +01:00
|
|
|
|
2012-04-07 02:12:42 +02:00
|
|
|
var processedMarker = false;
|
|
|
|
// Handle lineAttributeMarker, if present
|
|
|
|
if (cls.indexOf(lineAttributeMarker) >= 0)
|
2011-07-07 19:59:34 +02:00
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var listType = /(?:^| )list:(\S+)/.exec(cls);
|
2012-01-15 18:20:20 +01:00
|
|
|
var start = /(?:^| )start:(\S+)/.exec(cls);
|
2014-03-05 00:14:15 +01:00
|
|
|
|
|
|
|
_.map(hooks.callAll("aceDomLinePreProcessLineAttributes", {
|
|
|
|
domline: domline,
|
|
|
|
cls: cls
|
|
|
|
}), function(modifier)
|
|
|
|
{
|
|
|
|
preHtml += modifier.preHtml;
|
|
|
|
postHtml += modifier.postHtml;
|
|
|
|
processedMarker |= modifier.processedMarker;
|
|
|
|
});
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
if (listType)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
listType = listType[1];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (listType)
|
|
|
|
{
|
2012-01-15 18:20:20 +01:00
|
|
|
if(listType.indexOf("number") < 0)
|
|
|
|
{
|
2014-03-05 22:44:32 +01:00
|
|
|
preHtml += '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
|
|
|
|
postHtml = '</li></ul>' + postHtml;
|
2012-01-15 18:20:20 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-04-09 02:22:09 +02:00
|
|
|
if(start){ // is it a start of a list with more than one item in?
|
2013-04-09 15:29:55 +02:00
|
|
|
if(start[1] == 1){ // if its the first one at this level?
|
2013-04-11 17:33:03 +02:00
|
|
|
lineClass = lineClass + " " + "list-start-" + listType; // Add start class to DIV node
|
2013-04-09 02:22:09 +02:00
|
|
|
}
|
2014-03-05 22:44:32 +01:00
|
|
|
preHtml += '<ol start='+start[1]+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
|
2014-03-05 00:14:15 +01:00
|
|
|
}else{
|
2014-03-05 22:44:32 +01:00
|
|
|
preHtml += '<ol class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; // Handles pasted contents into existing lists
|
2013-04-09 02:22:09 +02:00
|
|
|
}
|
2014-03-05 22:44:32 +01:00
|
|
|
postHtml += '</li></ol>';
|
2012-01-15 18:20:20 +01:00
|
|
|
}
|
2019-04-16 00:34:29 +02:00
|
|
|
}
|
2012-04-07 02:12:42 +02:00
|
|
|
processedMarker = true;
|
|
|
|
}
|
|
|
|
_.map(hooks.callAll("aceDomLineProcessLineAttributes", {
|
|
|
|
domline: domline,
|
|
|
|
cls: cls
|
|
|
|
}), function(modifier)
|
|
|
|
{
|
|
|
|
preHtml += modifier.preHtml;
|
|
|
|
postHtml += modifier.postHtml;
|
|
|
|
processedMarker |= modifier.processedMarker;
|
|
|
|
});
|
|
|
|
if( processedMarker ){
|
2011-03-26 14:10:41 +01:00
|
|
|
result.lineMarker += txt.length;
|
|
|
|
return; // don't append any text
|
2019-04-16 00:34:29 +02:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
var href = null;
|
|
|
|
var simpleTags = null;
|
2011-07-07 19:59:34 +02:00
|
|
|
if (cls.indexOf('url') >= 0)
|
|
|
|
{
|
|
|
|
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
|
|
|
|
{
|
|
|
|
href = url;
|
|
|
|
return space + "url";
|
2011-03-26 14:10:41 +01:00
|
|
|
});
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
if (cls.indexOf('tag') >= 0)
|
|
|
|
{
|
|
|
|
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
|
|
|
|
{
|
|
|
|
if (!simpleTags) simpleTags = [];
|
|
|
|
simpleTags.push(tag.toLowerCase());
|
|
|
|
return space + tag;
|
2011-03-26 14:10:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var extraOpenTags = "";
|
|
|
|
var extraCloseTags = "";
|
|
|
|
|
2012-03-17 13:36:42 +01:00
|
|
|
_.map(hooks.callAll("aceCreateDomLine", {
|
2011-07-07 19:59:34 +02:00
|
|
|
domline: domline,
|
|
|
|
cls: cls
|
2012-03-17 13:36:42 +01:00
|
|
|
}), function(modifier)
|
2011-07-07 19:59:34 +02:00
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
cls = modifier.cls;
|
2011-07-07 19:59:34 +02:00
|
|
|
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
|
|
|
|
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
|
2011-03-26 14:10:41 +01:00
|
|
|
});
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
if ((!txt) && cls)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
lineClass = domline.addToLineClass(lineClass, cls);
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (txt)
|
|
|
|
{
|
|
|
|
if (href)
|
|
|
|
{
|
2016-01-30 19:03:42 +01:00
|
|
|
urn_schemes = new RegExp("^(about|geo|mailto|tel):");
|
|
|
|
if(!~href.indexOf("://") && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
|
2011-11-20 05:24:58 +01:00
|
|
|
{
|
|
|
|
href = "http://"+href;
|
|
|
|
}
|
2015-01-27 12:11:07 +01:00
|
|
|
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
|
|
|
|
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
|
referer: change referrer policy. Stop sending referers as much as possible
Pull request with discussion: https://github.com/ether/etherpad-lite/pull/3636
What's already there:
* `meta name=referrer`: already done in 1.6.1:
https://github.com/ether/etherpad-lite/pull/3044
https://caniuse.com/#feat=referrer-policy
https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta
(Chrome>=78, Firefox>=70, Safari>=13, Opera>=64, ~IE[1], ~Edge[1])
The previous two commits (by @joelpurra) I backported in this batch:
* `<a rel=noreferrer>`: a pull request denied before:
https://github.com/ether/etherpad-lite/pull/2498
https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
(Firefox>=37, I can't find more info about support)
This commit adds the following:
* `<a rel="noopener">`: fixing a not-so-well-known way to extract referer
https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
(Chrome>=49, Firefox>=52, Safari>=10.1, Opera>=36, !IE, !Edge)
* `Referrer-Policy: same-origin`: the last bastion of referrer security
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
(Chrome>=61, Firefox>=52, Safari>=11.1, Opera>=48, !IE, !Edge)
meta name=referrer wasn't enough. I happened to leak a few referrers with my
Firefox browser, though for some browsers it could have been enough.
[1] IE>=11, Edge>=18 use a different syntax for meta name=referrer, making it
most probably incompatible (but I may be wrong on that, they may support
both, but I have no way to test it currently). The next Edge release will be
based on Chromium, so for that the Chrome version applies.
2019-11-23 08:18:07 +01:00
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
|
|
|
|
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
|
|
|
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
|
|
|
// https://mathiasbynens.github.io/rel-noopener/
|
|
|
|
// https://github.com/ether/etherpad-lite/pull/3636
|
|
|
|
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '" rel="noreferrer noopener">';
|
2011-07-07 19:59:34 +02:00
|
|
|
extraCloseTags = '</a>' + extraCloseTags;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
if (simpleTags)
|
|
|
|
{
|
|
|
|
simpleTags.sort();
|
|
|
|
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
|
|
|
|
simpleTags.reverse();
|
|
|
|
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2012-01-18 12:58:13 +01:00
|
|
|
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
result.clearSpans = function()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
html = [];
|
2016-09-13 01:17:04 +02:00
|
|
|
lineClass = 'ace-line';
|
2011-03-26 14:10:41 +01:00
|
|
|
result.lineMarker = 0;
|
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
|
|
|
function writeHTML()
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var newHTML = perHtmlLineProcess(html.join(''));
|
2011-07-07 19:59:34 +02:00
|
|
|
if (!newHTML)
|
|
|
|
{
|
|
|
|
if ((!document) || (!optBrowser))
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
newHTML += ' ';
|
|
|
|
}
|
2015-01-21 03:55:03 +01:00
|
|
|
else if (!optBrowser.msie)
|
2011-07-07 19:59:34 +02:00
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
newHTML += '<br/>';
|
|
|
|
}
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
if (nonEmpty)
|
|
|
|
{
|
|
|
|
newHTML = (preHtml || '') + newHTML + (postHtml || '');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2012-04-07 02:12:42 +02:00
|
|
|
html = preHtml = postHtml = ''; // free memory
|
2011-07-07 19:59:34 +02:00
|
|
|
if (newHTML !== curHTML)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
curHTML = newHTML;
|
|
|
|
result.node.innerHTML = curHTML;
|
|
|
|
}
|
|
|
|
if (lineClass !== null) result.node.className = lineClass;
|
2013-12-05 08:41:29 +01:00
|
|
|
|
2014-03-05 00:14:15 +01:00
|
|
|
hooks.callAll("acePostWriteDomLineHTML", {
|
|
|
|
node: result.node
|
2014-03-05 00:36:16 +01:00
|
|
|
});
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
result.prepareForAdd = writeHTML;
|
|
|
|
result.finishUpdate = writeHTML;
|
2011-07-07 19:59:34 +02:00
|
|
|
result.getInnerHTML = function()
|
|
|
|
{
|
|
|
|
return curHTML || '';
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
domline.processSpaces = function(s, doesWrap)
|
|
|
|
{
|
|
|
|
if (s.indexOf("<") < 0 && !doesWrap)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
// short-cut
|
|
|
|
return s.replace(/ /g, ' ');
|
|
|
|
}
|
|
|
|
var parts = [];
|
2011-07-07 19:59:34 +02:00
|
|
|
s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
|
|
|
|
{
|
|
|
|
parts.push(m);
|
|
|
|
});
|
|
|
|
if (doesWrap)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var endOfLine = true;
|
|
|
|
var beforeSpace = false;
|
|
|
|
// last space in a run is normal, others are nbsp,
|
|
|
|
// end of line is nbsp
|
2011-07-07 19:59:34 +02:00
|
|
|
for (var i = parts.length - 1; i >= 0; i--)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var p = parts[i];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (p == " ")
|
|
|
|
{
|
|
|
|
if (endOfLine || beforeSpace) parts[i] = ' ';
|
|
|
|
endOfLine = false;
|
|
|
|
beforeSpace = true;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (p.charAt(0) != "<")
|
|
|
|
{
|
|
|
|
endOfLine = false;
|
|
|
|
beforeSpace = false;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// beginning of line is nbsp
|
2011-07-07 19:59:34 +02:00
|
|
|
for (var i = 0; i < parts.length; i++)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var p = parts[i];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (p == " ")
|
|
|
|
{
|
|
|
|
parts[i] = ' ';
|
|
|
|
break;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else if (p.charAt(0) != "<")
|
|
|
|
{
|
|
|
|
break;
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (var i = 0; i < parts.length; i++)
|
|
|
|
{
|
2011-03-26 14:10:41 +01:00
|
|
|
var p = parts[i];
|
2011-07-07 19:59:34 +02:00
|
|
|
if (p == " ")
|
|
|
|
{
|
|
|
|
parts[i] = ' ';
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parts.join('');
|
|
|
|
};
|
2012-01-16 02:23:48 +01:00
|
|
|
|
|
|
|
exports.domline = domline;
|