Commit f4bdfb51 authored by Finnur Thorarinsson's avatar Finnur Thorarinsson Committed by Commit Bot

RSS Extension: Build script, closure, hardening.

- Sandbox the iframe and sanitize the input used.
- Remove special 'synchronous' testing flag (not needed).
  - Multi-line quote requirement also no longer needed.
- Change default reader list
  - (replace deprecated Bloglines service with Newsblur).
- Deflake and re-enable the RSS extension tests.
- Up the version by two (from 2.2.3 to 2.2.5, since 2.2.4
  is live already).

Bug: 340354, b/137075999
Change-Id: I6eac37fae9e2228aeabe68ccf1fd34b9b36ac6ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2264059
Commit-Queue: Finnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#785861}
parent 56da23ea
#!/bin/bash
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
closure_path=../../../../../third_party/google-closure-library
compiler_jar=compiler/closure-compiler-v20200614.jar
echo
echo '-------------------------------------------'
echo 'Building the RSS extension.'
echo '-------------------------------------------'
echo
echo 'Recreating out/ from scratch...'
rm -rf out/
mkdir out/
echo 'Copying manifest.json' && cp -r src/manifest.json out/
echo Copying _locales && cp -r src/_locales out/_locales
echo Copying .html && cp src/*.html out/
echo Copying .css && cp src/*.css out/
echo Copying .png && cp src/*.png out/
echo 'Copying javascipt files (all except iframe.js, which will be compiled).'
echo 'Copying background.js' && cp src/background.js out/background.js
echo 'Copying common.js' && cp src/common.js out/common.js
echo 'Copying doc_start.js' && cp src/doc_start.js out/doc_start.js
echo 'Copying feed_finder.js' && cp src/feed_finder.js out/feed_finder.js
echo 'Copying options.js' && cp src/options.js out/options.js
echo 'Copying popup.js' && cp src/popup.js out/popup.js
echo 'Copying sniff_common.js' && cp src/sniff_common.js out/sniff_common.js
echo 'Copying subscribe.js' && cp src/subscribe.js out/subscribe.js
# NOTE: test_*.js files should not be copied over.
echo
# Uncomment to see hashes for scripts.
# echo 'SHA hashes:'
# echo 'subscribe.js sha256-'$(cat src/subscribe.js | openssl dgst -sha256 -binary | openssl enc -base64)
# echo 'freeflow foo sha256-'$(echo -n "console.log('foo');" | openssl dgst -sha256 -binary | openssl enc -base64)
# echo
echo 'Closure compiling:'
echo 'Using' $compiler_jar
echo ' ^ Make sure this is present and up to date.'
echo 'Compiling iframe.js.'
$closure_path/closure/bin/build/closurebuilder.py \
--root=src/ \
--root=$closure_path \
--namespace="RSSExtension.IFrame" \
--output_mode=compiled \
--compiler_jar=$compiler_jar \
> out/iframe.js
echo 'The contents of out/ now contain the extension to be uploaded.'
echo
echo 'Extension:'
grep "\"version\"" out/manifest.json
echo ' ^ Please make sure this version is correct!!'
......@@ -11,8 +11,8 @@ var storageEnabled = window.localStorage != null;
function defaultReaderList() {
// This is the default list, unless replaced by what was saved previously.
return [
{ 'url': 'http://www.bloglines.com/login?r=/sub/%s',
'description': 'Bloglines'
{ 'url': 'http://www.newsblur.com/?url=%s',
'description': 'Newsblur'
},
{ 'url': 'http://add.my.yahoo.com/rss?url=%s',
'description': 'My Yahoo'
......
/* Copyright (c) 2009 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
*/
/* Use only multi-line comments in this file, since during testing
its contents will get read from disk and stuffed into the
iframe .src tag, which is a process that doesn't preserve line
breaks and makes single-line comments comment out the rest of the code.
*/
/* The maximum number of feed items to show in the preview. */
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
goog.provide('RSSExtension.IFrame');
goog.require('goog.html.sanitizer.HtmlSanitizer');
goog.require('goog.dom.safe');
// The maximum number of feed items to show in the preview.
var maxFeedItems = 10;
/* The maximum number of characters to show in the feed item title. */
// The maximum number of characters to show in the feed item title.
var maxTitleCount = 1024;
/* Find the token and target origin for this conversation from the HTML. The
token is used for secure communication, and is generated and stuffed into the
frame by subscribe.js.
*/
// Find the token and target origin for this conversation from the HTML. The
// token is used for secure communication, and is generated and stuffed into the
// frame by subscribe.js.
var token = '';
var targetOrigin = '';
var html = document.documentElement.outerHTML;
......@@ -45,20 +42,20 @@ if (token.length > 0) {
if (doc) {
buildPreview(doc);
} else {
/* Already handled in subscribe.html */
// Already handled in subscribe.html.
}
}
}
function buildPreview(doc) {
/* Start building the part we render inside an IFRAME. We use a table to
ensure that items are separated vertically from each other. */
// Start building the part we render inside an IFRAME. We use a table to
// ensure that items are separated vertically from each other.
var table = document.createElement("table");
var tbody = document.createElement("tbody");
table.appendChild(tbody);
/* Now parse the rest. Some use <entry> for each feed item, others use
<channel><item>. */
// Now parse the rest. Some use <entry> for each feed item, others use
// <channel><item>.
var entries = doc.getElementsByTagName('entry');
if (entries.length == 0)
entries = doc.getElementsByTagName('item');
......@@ -66,19 +63,19 @@ function buildPreview(doc) {
for (i = 0; i < entries.length && i < maxFeedItems; ++i) {
item = entries.item(i);
/* Grab the title for the feed item. */
// Grab the title for the feed item.
var itemTitle = item.getElementsByTagName('title')[0];
if (itemTitle)
itemTitle = itemTitle.textContent;
else
itemTitle = "Unknown title";
/* Ensure max length for title. */
// Ensure max length for title.
if (itemTitle.length > maxTitleCount)
itemTitle = itemTitle.substring(0, maxTitleCount) + "...";
/* Grab the description.
TODO(aa): Do we need to check for type=html here? */
// Grab the description.
// TODO(aa): Do we need to check for type=html here?
var itemDesc = item.getElementsByTagName('description')[0];
if (!itemDesc)
itemDesc = item.getElementsByTagName('summary')[0];
......@@ -90,7 +87,7 @@ function buildPreview(doc) {
else
itemDesc = "";
/* Grab the link URL. */
// Grab the link URL.
var itemLink = item.getElementsByTagName('link');
var link = "";
if (itemLink.length > 0) {
......@@ -104,21 +101,28 @@ function buildPreview(doc) {
var tr = document.createElement("tr");
var td = document.createElement("td");
/* If we found a link we'll create an anchor element,
otherwise just use a bold headline for the title. */
var sanitizer = window.domAutomationController === undefined ?
new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
.withCustomUrlPolicy(goog.html.SafeUrl.sanitize)
.build()
: goog.html.sanitizer.HtmlSanitizer;
// If we found a link we'll create an anchor element,
// otherwise just use a bold headline for the title.
var anchor = (link != "") ? document.createElement("a") :
document.createElement("strong");
anchor.id = "anchor_" + String(i);
if (link != "")
anchor.href = link;
anchor.innerHTML = itemTitle;
goog.dom.safe.setAnchorHref(anchor, goog.html.SafeUrl.sanitize(link));
goog.dom.safe.setInnerHtml(anchor, sanitizer.sanitize(itemTitle));
anchor.target = "_top";
anchor.className = "item_title";
var span = document.createElement("span");
span.id = "desc_" + String(i);
span.className = "item_desc";
span.innerHTML = itemDesc;
goog.dom.safe.setInnerHtml(span, sanitizer.sanitize(itemDesc));
td.appendChild(anchor);
td.appendChild(document.createElement("br"));
......@@ -132,4 +136,8 @@ function buildPreview(doc) {
table.appendChild(tbody);
document.body.appendChild(table);
if (window.domAutomationController) {
window.domAutomationController.send('PreviewReady');
}
}
......@@ -25,6 +25,6 @@
"default_title": "__MSG_rss_subscription_default_title__"
},
"permissions": [ "tabs", "http://*/*", "https://*/*", "storage" ],
"version": "2.2.3",
"version": "2.2.5",
"web_accessible_resources": [ "iframe.js", "style.css" ]
}
......@@ -12,14 +12,6 @@ var feedUrl = decodeURIComponent(queryString[0]);
// This extension's ID.
var extension_id = chrome.i18n.getMessage("@@extension_id");
// During testing, we cannot load the iframe and stylesheet from an external
// source, so we allow loading them synchronously and stuff the frame with the
// results. Since this is only allowed during testing, it isn't supported for
// the official extension ID.
var synchronousRequest =
extension_id != "nlbjncdgjeocebhnmkbbbdekmmmcbfjd" ?
queryString[1] == "synchronous" : false;
// The XMLHttpRequest object that tries to load and parse the feed, and (if
// testing) also the style sheet and the frame js.
var req;
......@@ -96,38 +88,19 @@ function main() {
crypto.getRandomValues(tokenArray);
token = [].join.call(tokenArray);
// Now fetch the data.
req = new XMLHttpRequest();
if (synchronousRequest) {
// Tests that load the html page directly through a file:// url don't have
// access to the js and css from the frame so we must load them first and
// inject them into the src for the iframe.
req.open("GET", "style.css", false);
req.send(null);
styleSheet = "<style>" + req.responseText + "</style>";
req.open("GET", "iframe.js", false);
req.send(null);
if (req.responseText.indexOf('//') != -1) {
console.log('Error: Single-line comment(s) found in iframe.js');
} else {
frameScript = "<script>" +
req.responseText +
"<" + "/script>";
}
} else {
// Normal loading just requires links to the css and the js file.
styleSheet = "<link rel='stylesheet' type='text/css' href='" +
chrome.extension.getURL("style.css") + "'>";
frameScript = "<script src='" + chrome.extension.getURL("iframe.js") +
"'></" + "script>";
}
styleSheet = "<link rel='stylesheet' type='text/css' href='" +
chrome.extension.getURL("style.css") + "'>";
frameScript = window.domAutomationController !== undefined ? "<script src='" +
chrome.extension.getURL("test_support.js") +
"'></" + "script>" : "";
frameScript += "<script src='" + chrome.extension.getURL("iframe.js") +
"'></" + "script>";
// Now fetch the feed data.
req = new XMLHttpRequest();
req.onload = handleResponse;
req.onerror = handleError;
req.open("GET", feedUrl, !synchronousRequest);
req.open("GET", feedUrl, true);
// Not everyone sets the mime type correctly, which causes handleResponse
// to fail to XML parse the response text from the server. By forcing
// it to text/xml we avoid this.
......@@ -157,6 +130,10 @@ function handleFeedParsingFailed(error) {
// The tests always expect an IFRAME, so add one showing the error.
var html = "<body><span id=\"error\" class=\"item_desc\">" + error +
"</span></body>";
if (window.domAutomationController) {
html += "<script src='" + chrome.extension.getURL("test_send_error.js") +
"'></" + "script>";
}
var error_frame = createFrame('error', html);
var itemsTag = document.getElementById('items');
......@@ -164,15 +141,12 @@ function handleFeedParsingFailed(error) {
}
function createFrame(frame_id, html) {
// During testing, we stuff the iframe with the script directly, so we relax
// the policy on running scripts under that scenario.
var csp = synchronousRequest ?
'<meta http-equiv="content-security-policy" ' +
'content="object-src \'none\'">' :
'<meta http-equiv="content-security-policy" ' +
var csp = '<meta http-equiv="content-security-policy" ' +
'content="object-src \'none\'; script-src \'self\'">';
frame = document.createElement('iframe');
frame.id = frame_id;
frame.name = "preview";
frame.sandbox = "allow-scripts";
frame.src = "data:text/html;charset=utf-8,<html>" + csp +
"<!--Token:" + extension_id + token +
"-->" + html + "</html>";
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Used to communicate feed parsing errors to the testing framework.
if (window.domAutomationController) {
window.domAutomationController.send('Error');
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides a default implementation for the sanitizer (a pass-through), so that
// tests can be run on the extension directly (without having to Closure compile
// it first).
var goog = {
dom: {
safe: {
setAnchorHref: function(element, href) {
element.href = href;
},
setInnerHtml: function(element, html) {
element.innerHTML = html;
}
}
},
html: {
sanitizer: {
HtmlSanitizer: {
sanitize: function(data) {
return data;
}
}
},
SafeUrl: {
sanitize: function(data) {
return data;
}
}
},
provide: function(namespace) {},
require: function(namespace) {}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment