Skip to content

Commit f96ab47

Browse files
authored
Original document security settings apply to speedreader document (#21958)
* Original HTML meta tags preserved in speedreader mode.
1 parent 9dd8366 commit f96ab47

File tree

138 files changed

+345
-168
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+345
-168
lines changed

browser/speedreader/speedreader_browsertest.cc

+18-1
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ const char kTestPageSimple[] = "/simple.html";
7070
const char kTestPageReadable[] = "/speedreader/article/guardian.html";
7171
const char kTestEsPageReadable[] = "/speedreader/article/es.html";
7272
const char kTestPageReadableOnUnreadablePath[] =
73-
"/speedreader/rewriter/pages/news_pages/abcnews.com/distilled.html";
73+
"/speedreader/rewriter/pages/news_pages/abcnews.com/original.html";
7474
const char kTestPageRedirect[] = "/articles/redirect_me.html";
7575
const char kTestXml[] = "/speedreader/article/rss.xml";
7676
const char kTestTtsSimple[] = "/speedreader/article/simple.html";
7777
const char kTestTtsTags[] = "/speedreader/article/tags.html";
7878
const char kTestTtsStructure[] = "/speedreader/article/structure.html";
7979
const char kTestErrorPage[] = "/speedreader/article/page_not_reachable.html";
80+
const char kTestCSPHtmlPage[] = "/speedreader/article/csp_html.html";
81+
const char kTestCSPHttpPage[] = "/speedreader/article/csp_http.html";
8082

8183
class SpeedReaderBrowserTest : public InProcessBrowserTest {
8284
public:
@@ -877,6 +879,21 @@ IN_PROC_BROWSER_TEST_F(SpeedReaderBrowserTest, ErrorPage) {
877879
tab_helper()->PageDistillState()));
878880
}
879881

882+
IN_PROC_BROWSER_TEST_F(SpeedReaderBrowserTest, Csp) {
883+
ToggleSpeedreader();
884+
885+
for (const auto* page : {kTestCSPHtmlPage, kTestCSPHttpPage}) {
886+
content::WebContentsConsoleObserver console_observer(ActiveWebContents());
887+
console_observer.SetPattern(
888+
"Refused to load the image 'https://a.test/should_fail.png' because it "
889+
"violates the following Content Security Policy directive: \"img-src "
890+
"'none'\".*");
891+
NavigateToPageSynchronously(page, WindowOpenDisposition::CURRENT_TAB);
892+
893+
EXPECT_TRUE(console_observer.Wait());
894+
}
895+
}
896+
880897
class SpeedReaderWithDistillationServiceBrowserTest
881898
: public SpeedReaderBrowserTest {
882899
public:

components/speedreader/renderer/speedreader_js_handler.cc

+1-4
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,11 @@ SpeedreaderJSHandler::~SpeedreaderJSHandler() = default;
3737
// static
3838
void SpeedreaderJSHandler::Install(
3939
base::WeakPtr<SpeedreaderRenderFrameObserver> owner,
40-
int32_t isolated_world_id) {
40+
v8::Local<v8::Context> context) {
4141
DCHECK(owner);
4242
v8::Isolate* isolate = blink::MainThreadIsolate();
4343
v8::HandleScope handle_scope(isolate);
4444

45-
v8::Local<v8::Context> context =
46-
owner->render_frame()->GetWebFrame()->GetScriptContextFromWorldId(
47-
isolate, isolated_world_id);
4845
if (context.IsEmpty()) {
4946
return;
5047
}

components/speedreader/renderer/speedreader_js_handler.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class SpeedreaderJSHandler final : public gin::Wrappable<SpeedreaderJSHandler> {
2222
SpeedreaderJSHandler& operator=(const SpeedreaderJSHandler&) = delete;
2323

2424
static void Install(base::WeakPtr<SpeedreaderRenderFrameObserver> owner,
25-
int32_t isolated_world_id);
25+
v8::Local<v8::Context> context);
2626

2727
private:
2828
explicit SpeedreaderJSHandler(

components/speedreader/renderer/speedreader_render_frame_observer.cc

+7-4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ SpeedreaderRenderFrameObserver::SpeedreaderRenderFrameObserver(
1818

1919
SpeedreaderRenderFrameObserver::~SpeedreaderRenderFrameObserver() = default;
2020

21-
void SpeedreaderRenderFrameObserver::DidClearWindowObject() {
22-
if (!render_frame()->IsMainFrame()) {
21+
void SpeedreaderRenderFrameObserver::DidCreateScriptContext(
22+
v8::Local<v8::Context> context,
23+
int32_t world_id) {
24+
if (!render_frame() || !render_frame()->IsMainFrame() ||
25+
isolated_world_id_ != world_id) {
2326
return;
2427
}
25-
SpeedreaderJSHandler::Install(weak_ptr_factory_.GetWeakPtr(),
26-
isolated_world_id_);
28+
29+
SpeedreaderJSHandler::Install(weak_ptr_factory_.GetWeakPtr(), context);
2730
}
2831

2932
void SpeedreaderRenderFrameObserver::OnDestruct() {

components/speedreader/renderer/speedreader_render_frame_observer.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class SpeedreaderRenderFrameObserver : public content::RenderFrameObserver {
2323
~SpeedreaderRenderFrameObserver() override;
2424

2525
// RenderFrameObserver implementation.
26-
void DidClearWindowObject() override;
26+
void DidCreateScriptContext(v8::Local<v8::Context> context,
27+
int32_t world_id) override;
2728

2829
private:
2930
// RenderFrameObserver implementation.

components/speedreader/resources/speedreader-desktop.js

+30
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ class speedreaderUtils {
2323
return document.getElementById(id)
2424
}
2525

26+
static adoptStyles = () => {
27+
for (const style of document.styleSheets) {
28+
style.disabled = true
29+
}
30+
31+
const braveStyles =
32+
document.querySelectorAll('script[type="brave-style-data"]')
33+
for (const styleData of braveStyles) {
34+
const style = new CSSStyleSheet()
35+
style.replaceSync(styleData.innerText)
36+
document.adoptedStyleSheets.push(style)
37+
}
38+
document.body.hidden = false
39+
}
40+
2641
static initShowOriginalLink = () => {
2742
const link = this.$(this.showOriginalLinkId)
2843
if (!link)
@@ -67,6 +82,20 @@ class speedreaderUtils {
6782
return
6883
}
6984

85+
{
86+
const spans =
87+
this.$(this.contentDivId)?.querySelectorAll('p > span')
88+
89+
for (const span of spans) {
90+
const p = span.parentNode
91+
92+
while (span.childNodes.length > 0) {
93+
p.insertBefore(span.firstChild, span)
94+
}
95+
p.removeChild(span)
96+
}
97+
}
98+
7099
let textToSpeak = 0
71100

72101
const makeParagraph = (elem) => {
@@ -267,6 +296,7 @@ class speedreaderUtils {
267296
speedreaderUtils.defaultSpeedreaderData,
268297
window.speedreaderData)
269298

299+
speedreaderUtils.adoptStyles()
270300
speedreaderUtils.initShowOriginalLink()
271301
speedreaderUtils.calculateReadtime()
272302
speedreaderUtils.initTextToSpeak()

components/speedreader/rust/lib/src/readability/src/extractor.rs

+28-13
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub struct Meta {
8383
pub description: Option<String>,
8484
pub charset: Option<String>,
8585
pub last_modified: Option<OffsetDateTime>,
86+
pub preserved_meta: Vec<Handle>,
8687
}
8788

8889
impl Meta {
@@ -102,6 +103,7 @@ impl Meta {
102103
};
103104
self.charset = self.charset.or(other.charset);
104105
self.last_modified = self.last_modified.or(other.last_modified);
106+
self.preserved_meta.extend(other.preserved_meta);
105107
self
106108
}
107109
}
@@ -188,14 +190,14 @@ pub fn extract_metadata(dom: &Sink) -> Meta {
188190
}
189191
} else if let Some(charset) = attribute.get(local_name!("charset")) {
190192
meta_tags.charset = Some(charset.to_string());
191-
} else if attribute
192-
.get(local_name!("http-equiv"))
193-
.map(|e| e.to_ascii_lowercase() == "content-type")
194-
.unwrap_or(false)
195-
{
196-
if let Some(content) = attribute.get(local_name!("content")) {
197-
if let Some(charset) = content.split("charset=").nth(1) {
198-
meta_tags.charset = Some(charset.trim().to_string());
193+
} else if let Some(http_equiv) = attribute.get(local_name!("http-equiv")) {
194+
meta_tags.preserved_meta.push(node.clone());
195+
196+
if http_equiv.to_ascii_lowercase() == "content-type" {
197+
if let Some(content) = attribute.get(local_name!("content")) {
198+
if let Some(charset) = content.split("charset=").nth(1) {
199+
meta_tags.charset = Some(charset.trim().to_string());
200+
}
199201
}
200202
}
201203
}
@@ -285,11 +287,24 @@ pub fn extract_dom(
285287

286288
// Our CSS formats based on id="article".
287289
dom::set_attr("id", "article", body.clone(), true);
290+
dom::set_attr("hidden", "true", body.clone(), true);
288291
body.to_string()
289292
}
290293
_ => top_candidate.to_string(),
291294
};
292295

296+
for node in meta.preserved_meta.iter() {
297+
if let Some(data) = node.as_element() {
298+
let attributes = data.attributes.borrow();
299+
300+
let mut val: String = String::from("<meta ");
301+
for attr in attributes.map.iter() {
302+
val += &format!(" {}=\"{}\" ", attr.0.local, attr.1.value);
303+
}
304+
content = val + ">" + &content;
305+
}
306+
}
307+
293308
if let Some(ref charset) = meta.charset {
294309
// Since we strip out the entire head, we need to include charset if one
295310
// was provided. Otherwise the browser will use the default encoding,
@@ -305,18 +320,18 @@ pub fn extract_dom(
305320
if theme.is_some() || font_family.is_some() || font_size.is_some() || column_width.is_some() {
306321
let mut header: String = String::from("<html");
307322
if let Some(theme) = theme {
308-
header = [header, format!(" data-theme=\"{}\"", theme)].concat();
323+
header += &format!(" data-theme=\"{}\"", theme);
309324
}
310325
if let Some(font_family) = font_family {
311-
header = [header, format!(" data-font-family=\"{}\"", font_family)].concat();
326+
header += &format!(" data-font-family=\"{}\"", font_family);
312327
}
313328
if let Some(font_size) = font_size {
314-
header = [header, format!(" data-font-size=\"{}\"", font_size)].concat();
329+
header += &format!(" data-font-size=\"{}\"", font_size);
315330
}
316331
if let Some(column_width) = column_width {
317-
header = [header, format!(" data-column-width=\"{}\"", column_width)].concat();
332+
header += &format!(" data-column-width=\"{}\"", column_width);
318333
}
319-
content = [header, ">".to_string(), content, "</html>".to_string()].concat();
334+
content = header + ">" + &content + "</html>";
320335
}
321336

322337
Ok(Product { meta, content })

components/speedreader/speedreader_rewriter_service.cc

+21-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "brave/components/speedreader/speedreader_rewriter_service.h"
77

8+
#include <string_view>
89
#include <utility>
910

1011
#include "base/base64.h"
@@ -38,25 +39,31 @@ constexpr const char kSpeedreaderStylesheet[] = "speedreader-stylesheet";
3839
std::string WrapStylesheetWithCSP(const std::string& stylesheet,
3940
const std::string& atkinson,
4041
const std::string& open_dyslexic) {
41-
auto get_sha256 = [](const std::string& v) {
42-
const std::string& style_hash = crypto::SHA256HashString(v);
43-
return base::Base64Encode(base::as_bytes(base::make_span(style_hash)));
44-
};
45-
4642
constexpr const char kCSP[] = R"html(
4743
<meta name="referrer" content="no-referrer">
4844
<meta http-equiv="Content-Security-Policy"
49-
content="script-src 'none';
50-
style-src-elem 'sha256-%s' 'sha256-%s' 'sha256-%s'"
45+
content="default-src 'none';
46+
script-src 'none';
47+
img-src *;
48+
font-src 'none';
49+
base-uri 'none';
50+
form-action 'none';
51+
upgrade-insecure-requests;"
5152
>)html";
5253

53-
return base::StrCat(
54-
{base::StringPrintf(kCSP, get_sha256(stylesheet).c_str(),
55-
get_sha256(atkinson).c_str(),
56-
get_sha256(open_dyslexic).c_str()),
57-
"<style id=\"brave_speedreader_style\">", stylesheet, "</style>",
58-
"<style id=\"atkinson_hyperligible_font\">", atkinson, "</style>",
59-
"<style id=\"open_dyslexic_font\">", open_dyslexic, "</style>"});
54+
const auto make_style_data = [](std::string_view id, std::string_view data) {
55+
const std::string& hash = crypto::SHA256HashString(data);
56+
const std::string& sha256 =
57+
base::Base64Encode(base::as_bytes(base::make_span(hash)));
58+
59+
return base::StrCat({"<script type=\"brave-style-data\" id=\"", id,
60+
"\" integrity=\"", sha256, "\">", data, "</script>"});
61+
};
62+
63+
return base::StrCat({kCSP,
64+
make_style_data("brave_speedreader_style", stylesheet),
65+
make_style_data("atkinson_hyperligible_font", atkinson),
66+
make_style_data("open_dyslexic_font", open_dyslexic)});
6067
}
6168

6269
std::string GetDistilledPageStylesheet(const base::FilePath& stylesheet_path) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
<!DOCTYPE html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="canonical" href="https://www.eff.org/deeplinks/2010/06/encrypt-web-https-everywhere-firefox-extension" />
6+
<meta name="referrer" content="no-referrer">
7+
<meta http-equiv="Content-Security-Policy" content="img-src 'none';" />
8+
<title>Encrypt the Web with the HTTPS Everywhere Firefox Extension | Electronic Frontier Foundation</title>
9+
</head>
10+
<body>
11+
<div id="main-content">
12+
<div class="main-column">
13+
<div class="panel-pane pane-page-title">
14+
<h1>Encrypt the Web with the HTTPS Everywhere Firefox Extension</h1>
15+
</div>
16+
<div class="field__item even">
17+
<p>This page is copied from https://www.eff.org/deeplinks/2010/06/encrypt-web-https-everywhere-firefox-extension, with modification. Today EFF and the Tor Project are launching a public beta of a new Firefox extension called
18+
<a href="https://eff.org/https-everywhere">HTTPS Everywhere</a>.</p>
19+
<p><img
20+
src="https://a.test/should_fail.png" border="0" alt="This image is not loaded"
21+
name="click here to encrypt the web" id="click here to encrypt the web" /></p>
22+
<p>This Firefox extension was inspired by the launch of Google's <a
23+
href="https://www.eff.org/deeplinks/2010/05/google-launches-encrypted-search">encrypted
24+
search option</a>. We wanted a way to ensure that every search our browsers sent was
25+
encrypted. At the same time, we were also able to encrypt most or all of the browser's
26+
communications with some other sites:</p>
27+
<ul>
28+
<li>Google Search</li>
29+
<li>Wikipedia</li>
30+
<li>Twitter and Identi.ca</li>
31+
<li>Facebook</li>
32+
<li>EFF and Tor</li>
33+
<li>Ixquick, DuckDuckGo, Scroogle and other small search engines</li>
34+
<li>and lots more!</li>
35+
</ul>
36+
<p>Firefox users can install HTTPS Everywhere by following <a
37+
href="https://www.eff.org/files/https-everywhere-latest.xpi">this link</a>. As always,
38+
even if you're at an HTTPS page, remember that unless Firefox displays a colored address bar
39+
and an unbroken lock icon in the bottom-right corner, the page is not completely encrypted
40+
and you may still be vulnerable to various forms of eavesdropping or hacking (in many cases,
41+
HTTPS Everywhere can't prevent this because sites incorporate insecure third-party content).
42+
</p>
43+
</div>
44+
</div>
45+
</div>
46+
</div>
47+
</body>
48+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
<!DOCTYPE html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="canonical" href="https://www.eff.org/deeplinks/2010/06/encrypt-web-https-everywhere-firefox-extension" />
6+
<title>Encrypt the Web with the HTTPS Everywhere Firefox Extension | Electronic Frontier Foundation</title>
7+
</head>
8+
<body>
9+
<div id="main-content">
10+
<div class="main-column">
11+
<div class="panel-pane pane-page-title">
12+
<h1>Encrypt the Web with the HTTPS Everywhere Firefox Extension</h1>
13+
</div>
14+
<div class="field__item even">
15+
<p>This page is copied from https://www.eff.org/deeplinks/2010/06/encrypt-web-https-everywhere-firefox-extension, with modification. Today EFF and the Tor Project are launching a public beta of a new Firefox extension called
16+
<a href="https://eff.org/https-everywhere">HTTPS Everywhere</a>.</p>
17+
<p><img
18+
src="https://a.test/should_fail.png" border="0" alt="This image is not loaded"
19+
name="click here to encrypt the web" id="click here to encrypt the web" /></p>
20+
<p>This Firefox extension was inspired by the launch of Google's <a
21+
href="https://www.eff.org/deeplinks/2010/05/google-launches-encrypted-search">encrypted
22+
search option</a>. We wanted a way to ensure that every search our browsers sent was
23+
encrypted. At the same time, we were also able to encrypt most or all of the browser's
24+
communications with some other sites:</p>
25+
<ul>
26+
<li>Google Search</li>
27+
<li>Wikipedia</li>
28+
<li>Twitter and Identi.ca</li>
29+
<li>Facebook</li>
30+
<li>EFF and Tor</li>
31+
<li>Ixquick, DuckDuckGo, Scroogle and other small search engines</li>
32+
<li>and lots more!</li>
33+
</ul>
34+
<p>Firefox users can install HTTPS Everywhere by following <a
35+
href="https://www.eff.org/files/https-everywhere-latest.xpi">this link</a>. As always,
36+
even if you're at an HTTPS page, remember that unless Firefox displays a colored address bar
37+
and an unbroken lock icon in the bottom-right corner, the page is not completely encrypted
38+
and you may still be vulnerable to various forms of eavesdropping or hacking (in many cases,
39+
HTTPS Everywhere can't prevent this because sites incorporate insecure third-party content).
40+
</p>
41+
</div>
42+
</div>
43+
</div>
44+
</div>
45+
</body>
46+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
HTTP/1.1 200 OK
2+
Content-Type: text/html;
3+
Content-Security-Policy: default-src 'none';img-src 'none';style-src 'none';frame-ancestors 'none';form-action 'none';sandbox
4+

0 commit comments

Comments
 (0)