From dbe1ddbc1ddd9b40c6e0e5c896768f91cb6994e0 Mon Sep 17 00:00:00 2001 From: hackademix Date: Sun, 15 Jul 2018 23:15:34 +0200 Subject: Refactor and fix HTTP response filtering to touch only scripts, either external or embedded inside HTML documents and sub-documents. --- bg/ResponseProcessor.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 bg/ResponseProcessor.js (limited to 'bg/ResponseProcessor.js') diff --git a/bg/ResponseProcessor.js b/bg/ResponseProcessor.js new file mode 100644 index 0000000..3f3151b --- /dev/null +++ b/bg/ResponseProcessor.js @@ -0,0 +1,110 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS. If not, see . +*/ + +/** + An abstraction layer over the StreamFilter API, allowing its clients to process + only the "interesting" HTML and script requests and leaving the other alone +*/ + +let {ResponseMetaData} = require("./ResponseMetaData.js"); + +let listeners = new WeakMap(); +let webRequestEvent = browser.webRequest.onHeadersReceived; + +class ResponseProcessor { + + static install(handler, types = ["main_frame", "sub_frame", "script"]) { + if (listeners.has(handler)) return false; + let listener = + request => new ResponseTextFilter(request).process(handler); + listeners.set(handler, listener); + webRequestEvent.addListener( + listener, + {urls: [""], types}, + ["blocking", "responseHeaders"] + ); + return true; + } + + static uninstall(handler) { + let listener = listeners.get(handler); + if (listener) { + webRequestEvent.removeListener(listener); + } + } +} + +class ResponseTextFilter { + constructor(request) { + this.request = request; + let {type, statusCode} = request; + let md = this.metaData = new ResponseMetaData(request); + this.canProcess = // we want to process html documents and scripts only + (statusCode < 300 || statusCode >= 400) && // skip redirections + !md.disposition && // skip forced downloads + (type === "script" || /\bhtml\b/i.test(md.contentType)); + } + + process(handler) { + if (!this.canProcess) return {}; + let metaData = this.metaData; + let {requestId, responseHeaders} = this.request; + let filter = browser.webRequest.filterResponseData(requestId); + let buffer = []; + + filter.ondata = event => { + buffer.push(event.data); + }; + + filter.onstop = async event => { + let decoder = metaData.createDecoder(); + let params = {stream: true}; + let text = this.text = buffer.map( + chunk => decoder.decode(chunk, params)) + .join(''); + let editedText = null; + try { + let response = { + request: this.request, + metaData, + text, + }; + editedText = await handler(response); + } catch(e) { + console.error(e); + } + if (metaData.forcedUTF8 || + editedText !== null && text !== editedText) { + // if we changed the charset, the text or both, let's re-encode + filter.write(new TextEncoder().encode(editedText)); + } else { + // ... otherwise pass all the raw bytes through + for (let chunk of buffer) filter.write(chunk); + } + + filter.disconnect(); + } + + return metaData.forceUTF8() ? {responseHeaders} : {}; + } +} + +module.exports = { ResponseProcessor }; -- cgit v1.2.3