diff --git a/template/index.html b/template/index.html index f2972f7..a49c3f4 100644 --- a/template/index.html +++ b/template/index.html @@ -231,9 +231,7 @@
-
-
-
+
diff --git a/template/static/javascripts/app.js b/template/static/javascripts/app.js index 5921e5b..cc45b9d 100644 --- a/template/static/javascripts/app.js +++ b/template/static/javascripts/app.js @@ -10,22 +10,6 @@ var FONTS = [ "Verdana", ] -DOMPurify.addHook('afterSanitizeAttributes', function (node) { - // set all elements owning target to target=_blank - if ('target' in node) { - node.setAttribute('target', '_blank'); - } - // set non-HTML/MathML links to xlink:show=new - if ( - !node.hasAttribute('target') && - (node.hasAttribute('xlink:href') || node.hasAttribute('href')) - ) { - node.setAttribute('xlink:show', 'new'); - } -}); - -Vue.prototype.$sanitize = DOMPurify.sanitize - var debounce = function(callback, wait) { var timeout return function() { @@ -37,6 +21,25 @@ var debounce = function(callback, wait) { } } +var sanitize = function(content, base) { + var sanitizer = new DOMPurify + sanitizer.addHook('afterSanitizeAttributes', function(node) { + // set all elements owning target to target=_blank + if ('target' in node) + node.setAttribute('target', '_blank') + // set non-HTML/MathML links to xlink:show=new + if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) + node.setAttribute('xlink:show', 'new') + + // set absolute urls + if (node.attributes.href && node.attributes.href.value) + node.href = new URL(node.attributes.href.value, base).toString() + if (node.attributes.src && node.attributes.src.value) + node.src = new URL(node.attributes.src.value, base).toString() + }) + return sanitizer.sanitize(content) +} + Vue.directive('scroll', { inserted: function(el, binding) { el.addEventListener('scroll', debounce(function(event) { @@ -205,6 +208,20 @@ var vm = new Vue({ return acc }, {unread: 0, starred: 0}) }, + itemSelectedContent: function() { + if (!this.itemSelected) return '' + + if (this.itemSelectedReadability) + return this.itemSelectedReadability + + var content = '' + if (this.itemSelectedDetails.content) + content = this.itemSelectedDetails.content + else if (this.itemSelectedDetails.description) + content = this.itemSelectedDetails.description + + return sanitize(content, this.feedsById[this.itemSelectedDetails.feed_id].link || this.itemSelectedDetails.link) + }, }, watch: { 'theme': { @@ -479,7 +496,8 @@ var vm = new Vue({ if (item.link) { api.crawl(item.link).then(function(body) { if (!body.length) return - var doc = new DOMParser().parseFromString(body, 'text/html') + var bodyClean = sanitize(body, vm.feedsById[item.feed_id].link || item.link) + var doc = new DOMParser().parseFromString(bodyClean, 'text/html') var parsed = new Readability(doc).parse() if (parsed && parsed.content) { vm.itemSelectedReadability = parsed.content