diff --git a/src/css/dashboard-common.css b/src/css/dashboard-common.css index f5c3d161c..dd0a8c19c 100644 --- a/src/css/dashboard-common.css +++ b/src/css/dashboard-common.css @@ -68,6 +68,7 @@ input[type="checkbox"][disabled] + label { border: 1px dotted black; background-color: #F8F8F8; font-size: 13px; + white-space: pre-line; } .whatisthis-expandable > p { margin-top: 1em; diff --git a/src/js/i18n.js b/src/js/i18n.js index 6dc2ba937..1ff610e8a 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -1,7 +1,7 @@ /******************************************************************************* - µBlock - a browser extension to block requests. - Copyright (C) 2014 Raymond Hill + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-2016 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ Home: https://github.com/gorhill/uBlock */ -/* global vAPI, uDom */ +'use strict'; /******************************************************************************/ @@ -28,7 +28,82 @@ (function() { -'use strict'; +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/2084 +// Anything else than , , , , , , and will +// be rendered as plain text. +// For , only the type attribute is allowed. +// For , only href attribute must be present, and it MUST starts with +// `https://`, and includes no single- or double-quotes. +// No HTML entities are allowed, there is code to handle existing HTML +// entities already present in translation files until they are all gone. + +var reSafeTags = /^([\s\S]*?)<(b|code|em|i|span)>(.+?)<\/\2>([\s\S]*)$/, + reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/, + reInput = /^input type=(['"])([a-z]+)\1$/, + reSafeLink = /^([\s\S]*?)<(a href=['"]https:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/, + reLink = /^a href=(['"])(https:\/\/[^'"]+)\1$/; + +var safeTextToTagNode = function(text) { + var matches, node; + if ( text.lastIndexOf('a ', 0) === 0 ) { + matches = reLink.exec(text); + if ( matches === null ) { return null; } + node = document.createElement('a'); + node.setAttribute('href', matches[2]); + return node; + } + if ( text.lastIndexOf('input ', 0) === 0 ) { + matches = reInput.exec(text); + if ( matches === null ) { return null; } + node = document.createElement('input'); + node.setAttribute('type', matches[2]); + return node; + } + return document.createElement(text); +}; + +var safeTextToTextNode = function(text) { + // TODO: remove once no more HTML entities in translation files. + if ( text.indexOf('&') !== -1 ) { + text = text.replace(/“/g, '“') + .replace(/”/g, '”') + .replace(/‘/g, '‘') + .replace(/’/g, '’'); + } + return document.createTextNode(text); +}; + +var safeTextToDOM = function(text, parent) { + if ( text === '' ) { return; } + // Fast path (most common). + if ( text.indexOf('<') === -1 ) { + return parent.appendChild(safeTextToTextNode(text)); + } + // Slow path. + // `

` no longer allowed. Code below can be remove once all

's are + // gone from translation files. + text = text.replace(/^

|<\/p>/g, '') + .replace(/

/g, '\n\n'); + // Parse allowed HTML tags. + var matches = reSafeTags.exec(text); + if ( matches === null ) { + matches = reSafeLink.exec(text); + if ( matches === null ) { + matches = reSafeInput.exec(text); + if ( matches === null ) { + parent.appendChild(safeTextToTextNode(text)); + return; + } + } + } + safeTextToDOM(matches[1], parent); + var node = safeTextToTagNode(matches[2]) || parent; + safeTextToDOM(matches[3], node); + parent.appendChild(node); + safeTextToDOM(matches[4], parent); +}; /******************************************************************************/ @@ -46,10 +121,11 @@ vAPI.i18n.render = function(context) { if ( !text ) { continue; } + // TODO: remove once it's all replaced with if ( text.indexOf('{') !== -1 ) { text = text.replace(/\{\{input:([^}]+)\}\}/g, ''); } - uDom(elem).html(text); + safeTextToDOM(text, elem); } elems = root.querySelectorAll('[title]'); diff --git a/src/js/udom.js b/src/js/udom.js index ea6757f9a..dde91826f 100644 --- a/src/js/udom.js +++ b/src/js/udom.js @@ -1,7 +1,7 @@ /******************************************************************************* - µBlock - a browser extension to block requests. - Copyright (C) 2014 Raymond Hill + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-2016 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -537,19 +537,6 @@ DOMList.prototype.val = function(value) { /******************************************************************************/ -DOMList.prototype.html = function(html) { - var i = this.nodes.length; - if ( html === undefined ) { - return i ? this.nodes[0].innerHTML : ''; - } - while ( i-- ) { - vAPI.insertHTML(this.nodes[i], html); - } - return this; -}; - -/******************************************************************************/ - DOMList.prototype.text = function(text) { var i = this.nodes.length; if ( text === undefined ) {