1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/renderer/content_settings_observer.h"
7 #include "chrome/common/render_messages.h"
8 #include "chrome/common/url_constants.h"
9 #include "content/public/renderer/document_state.h"
10 #include "content/public/renderer/navigation_state.h"
11 #include "content/public/renderer/render_view.h"
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrameClient.h"
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
17 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
19 #include "webkit/glue/weburlresponse_extradata_impl.h"
21 using WebKit::WebDataSource
;
22 using WebKit::WebFrame
;
23 using WebKit::WebFrameClient
;
24 using WebKit::WebSecurityOrigin
;
25 using WebKit::WebString
;
27 using WebKit::WebView
;
28 using content::DocumentState
;
29 using content::NavigationState
;
33 GURL
GetOriginOrURL(const WebFrame
* frame
) {
34 WebString top_origin
= frame
->top()->document().securityOrigin().toString();
35 // The the |top_origin| is unique ("null") e.g., for file:// URLs. Use the
36 // document URL as the primary URL in those cases.
37 if (top_origin
== "null")
38 return frame
->top()->document().url();
39 return GURL(top_origin
);
42 ContentSetting
GetContentSettingFromRules(
43 const ContentSettingsForOneType
& rules
,
44 const WebFrame
* frame
,
45 const GURL
& secondary_url
) {
46 ContentSettingsForOneType::const_iterator it
;
47 // If there is only one rule, it's the default rule and we don't need to match
49 if (rules
.size() == 1) {
50 DCHECK(rules
[0].primary_pattern
== ContentSettingsPattern::Wildcard());
51 DCHECK(rules
[0].secondary_pattern
== ContentSettingsPattern::Wildcard());
52 return rules
[0].setting
;
54 const GURL
& primary_url
= GetOriginOrURL(frame
);
55 for (it
= rules
.begin(); it
!= rules
.end(); ++it
) {
56 if (it
->primary_pattern
.Matches(primary_url
) &&
57 it
->secondary_pattern
.Matches(secondary_url
)) {
62 return CONTENT_SETTING_DEFAULT
;
67 ContentSettingsObserver::ContentSettingsObserver(
68 content::RenderView
* render_view
)
69 : content::RenderViewObserver(render_view
),
70 content::RenderViewObserverTracker
<ContentSettingsObserver
>(render_view
),
71 content_setting_rules_(NULL
),
72 is_interstitial_page_(false) {
73 ClearBlockedContentSettings();
76 ContentSettingsObserver::~ContentSettingsObserver() {
79 void ContentSettingsObserver::SetContentSettingRules(
80 const RendererContentSettingRules
* content_setting_rules
) {
81 content_setting_rules_
= content_setting_rules
;
84 bool ContentSettingsObserver::IsPluginTemporarilyAllowed(
85 const std::string
& identifier
) {
86 // If the empty string is in here, it means all plug-ins are allowed.
87 // TODO(bauerb): Remove this once we only pass in explicit identifiers.
88 return (temporarily_allowed_plugins_
.find(identifier
) !=
89 temporarily_allowed_plugins_
.end()) ||
90 (temporarily_allowed_plugins_
.find(std::string()) !=
91 temporarily_allowed_plugins_
.end());
94 void ContentSettingsObserver::DidBlockContentType(
95 ContentSettingsType settings_type
,
96 const std::string
& resource_identifier
) {
97 // Always send a message when |resource_identifier| is not empty, to tell the
98 // browser which resource was blocked (otherwise the browser will only show
99 // the first resource to be blocked, and none that are blocked at a later
101 if (!content_blocked_
[settings_type
] || !resource_identifier
.empty()) {
102 content_blocked_
[settings_type
] = true;
103 Send(new ChromeViewHostMsg_ContentBlocked(routing_id(), settings_type
,
104 resource_identifier
));
108 bool ContentSettingsObserver::OnMessageReceived(const IPC::Message
& message
) {
110 IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver
, message
)
111 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAsInterstitial
, OnSetAsInterstitial
)
112 IPC_MESSAGE_UNHANDLED(handled
= false)
113 IPC_END_MESSAGE_MAP()
117 // Don't swallow LoadBlockedPlugins messages, as they're sent to every
119 IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver
, message
)
120 IPC_MESSAGE_HANDLER(ChromeViewMsg_LoadBlockedPlugins
, OnLoadBlockedPlugins
)
121 IPC_END_MESSAGE_MAP()
126 void ContentSettingsObserver::DidCommitProvisionalLoad(
127 WebFrame
* frame
, bool is_new_navigation
) {
129 return; // Not a top-level navigation.
131 DocumentState
* document_state
= DocumentState::FromDataSource(
132 frame
->dataSource());
133 NavigationState
* navigation_state
= document_state
->navigation_state();
134 if (!navigation_state
->was_within_same_page()) {
135 // Clear "block" flags for the new page. This needs to happen before any of
136 // |AllowScript()|, |AllowScriptFromSource()|, |AllowImage()|, or
137 // |AllowPlugins()| is called for the new page so that these functions can
138 // correctly detect that a piece of content flipped from "not blocked" to
140 ClearBlockedContentSettings();
141 temporarily_allowed_plugins_
.clear();
144 GURL url
= frame
->document().url();
145 // If we start failing this DCHECK, please makes sure we don't regress
146 // this bug: http://code.google.com/p/chromium/issues/detail?id=79304
147 DCHECK(frame
->document().securityOrigin().toString() == "null" ||
148 !url
.SchemeIs(chrome::kDataScheme
));
151 bool ContentSettingsObserver::AllowDatabase(WebFrame
* frame
,
152 const WebString
& name
,
153 const WebString
& display_name
,
154 unsigned long estimated_size
) {
155 if (frame
->document().securityOrigin().isUnique() ||
156 frame
->top()->document().securityOrigin().isUnique())
160 Send(new ChromeViewHostMsg_AllowDatabase(
161 routing_id(), GURL(frame
->document().securityOrigin().toString()),
162 GURL(frame
->top()->document().securityOrigin().toString()),
163 name
, display_name
, &result
));
167 bool ContentSettingsObserver::AllowFileSystem(WebFrame
* frame
) {
168 if (frame
->document().securityOrigin().isUnique() ||
169 frame
->top()->document().securityOrigin().isUnique())
173 Send(new ChromeViewHostMsg_AllowFileSystem(
174 routing_id(), GURL(frame
->document().securityOrigin().toString()),
175 GURL(frame
->top()->document().securityOrigin().toString()), &result
));
179 bool ContentSettingsObserver::AllowImage(WebFrame
* frame
,
180 bool enabled_per_settings
,
181 const WebURL
& image_url
) {
182 bool allow
= enabled_per_settings
;
183 if (enabled_per_settings
) {
184 if (is_interstitial_page_
)
186 if (IsWhitelistedForContentSettings(frame
))
189 if (content_setting_rules_
) {
190 GURL
secondary_url(image_url
);
191 allow
= GetContentSettingFromRules(
192 content_setting_rules_
->image_rules
,
193 frame
, secondary_url
) != CONTENT_SETTING_BLOCK
;
197 DidBlockContentType(CONTENT_SETTINGS_TYPE_IMAGES
, std::string());
201 bool ContentSettingsObserver::AllowIndexedDB(WebFrame
* frame
,
202 const WebString
& name
,
203 const WebSecurityOrigin
& origin
) {
204 if (frame
->document().securityOrigin().isUnique() ||
205 frame
->top()->document().securityOrigin().isUnique())
209 Send(new ChromeViewHostMsg_AllowIndexedDB(
210 routing_id(), GURL(frame
->document().securityOrigin().toString()),
211 GURL(frame
->top()->document().securityOrigin().toString()),
216 bool ContentSettingsObserver::AllowPlugins(WebFrame
* frame
,
217 bool enabled_per_settings
) {
218 return enabled_per_settings
;
221 bool ContentSettingsObserver::AllowScript(WebFrame
* frame
,
222 bool enabled_per_settings
) {
223 if (!enabled_per_settings
)
225 if (is_interstitial_page_
)
228 std::map
<WebFrame
*, bool>::const_iterator it
=
229 cached_script_permissions_
.find(frame
);
230 if (it
!= cached_script_permissions_
.end())
233 // Evaluate the content setting rules before
234 // |IsWhitelistedForContentSettings|; if there is only the default rule
235 // allowing all scripts, it's quicker this way.
237 if (content_setting_rules_
) {
238 ContentSetting setting
= GetContentSettingFromRules(
239 content_setting_rules_
->script_rules
,
241 GURL(frame
->document().securityOrigin().toString()));
242 allow
= setting
!= CONTENT_SETTING_BLOCK
;
244 allow
= allow
|| IsWhitelistedForContentSettings(frame
);
246 cached_script_permissions_
[frame
] = allow
;
250 bool ContentSettingsObserver::AllowScriptFromSource(
252 bool enabled_per_settings
,
253 const WebKit::WebURL
& script_url
) {
254 if (!enabled_per_settings
)
256 if (is_interstitial_page_
)
260 if (content_setting_rules_
) {
261 ContentSetting setting
= GetContentSettingFromRules(
262 content_setting_rules_
->script_rules
,
265 allow
= setting
!= CONTENT_SETTING_BLOCK
;
267 return allow
|| IsWhitelistedForContentSettings(frame
);
270 bool ContentSettingsObserver::AllowStorage(WebFrame
* frame
, bool local
) {
271 if (frame
->document().securityOrigin().isUnique() ||
272 frame
->top()->document().securityOrigin().isUnique())
276 StoragePermissionsKey
key(
277 GURL(frame
->document().securityOrigin().toString()), local
);
278 std::map
<StoragePermissionsKey
, bool>::const_iterator permissions
=
279 cached_storage_permissions_
.find(key
);
280 if (permissions
!= cached_storage_permissions_
.end())
281 return permissions
->second
;
283 Send(new ChromeViewHostMsg_AllowDOMStorage(
284 routing_id(), GURL(frame
->document().securityOrigin().toString()),
285 GURL(frame
->top()->document().securityOrigin().toString()),
287 cached_storage_permissions_
[key
] = result
;
291 void ContentSettingsObserver::DidNotAllowPlugins(WebFrame
* frame
) {
292 DidBlockContentType(CONTENT_SETTINGS_TYPE_PLUGINS
, std::string());
295 void ContentSettingsObserver::DidNotAllowScript(WebFrame
* frame
) {
296 DidBlockContentType(CONTENT_SETTINGS_TYPE_JAVASCRIPT
, std::string());
299 void ContentSettingsObserver::OnLoadBlockedPlugins(
300 const std::string
& identifier
) {
301 temporarily_allowed_plugins_
.insert(identifier
);
304 void ContentSettingsObserver::OnSetAsInterstitial() {
305 is_interstitial_page_
= true;
308 void ContentSettingsObserver::ClearBlockedContentSettings() {
309 for (size_t i
= 0; i
< arraysize(content_blocked_
); ++i
)
310 content_blocked_
[i
] = false;
311 cached_storage_permissions_
.clear();
312 cached_script_permissions_
.clear();
315 bool ContentSettingsObserver::IsWhitelistedForContentSettings(WebFrame
* frame
) {
316 // Whitelist ftp directory listings, as they require JavaScript to function
318 webkit_glue::WebURLResponseExtraDataImpl
* extra_data
=
319 static_cast<webkit_glue::WebURLResponseExtraDataImpl
*>(
320 frame
->dataSource()->response().extraData());
321 if (extra_data
&& extra_data
->is_ftp_directory_listing())
323 return IsWhitelistedForContentSettings(frame
->document().securityOrigin(),
324 frame
->document().url());
327 bool ContentSettingsObserver::IsWhitelistedForContentSettings(
328 const WebSecurityOrigin
& origin
,
329 const GURL
& document_url
) {
330 if (document_url
== GURL(content::kUnreachableWebDataURL
))
333 if (origin
.isUnique())
334 return false; // Uninitialized document?
336 if (EqualsASCII(origin
.protocol(), chrome::kChromeUIScheme
))
337 return true; // Browser UI elements should still work.
339 if (EqualsASCII(origin
.protocol(), chrome::kChromeDevToolsScheme
))
340 return true; // DevTools UI elements should still work.
342 if (EqualsASCII(origin
.protocol(), chrome::kExtensionScheme
))
345 if (EqualsASCII(origin
.protocol(), chrome::kChromeInternalScheme
))
348 // If the scheme is file:, an empty file name indicates a directory listing,
349 // which requires JavaScript to function properly.
350 if (EqualsASCII(origin
.protocol(), chrome::kFileScheme
)) {
351 return document_url
.SchemeIs(chrome::kFileScheme
) &&
352 document_url
.ExtractFileName().empty();