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 "base/command_line.h"
8 #include "chrome/common/chrome_switches.h"
9 #include "chrome/common/render_messages.h"
10 #include "chrome/common/url_constants.h"
11 #include "content/public/renderer/document_state.h"
12 #include "content/public/renderer/navigation_state.h"
13 #include "content/public/renderer/render_view.h"
14 #include "extensions/common/constants.h"
15 #include "third_party/WebKit/public/platform/WebURL.h"
16 #include "third_party/WebKit/public/web/WebDataSource.h"
17 #include "third_party/WebKit/public/web/WebDocument.h"
18 #include "third_party/WebKit/public/web/WebFrame.h"
19 #include "third_party/WebKit/public/web/WebFrameClient.h"
20 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
21 #include "third_party/WebKit/public/web/WebView.h"
22 #include "webkit/child/weburlresponse_extradata_impl.h"
24 using WebKit::WebDataSource
;
25 using WebKit::WebFrame
;
26 using WebKit::WebFrameClient
;
27 using WebKit::WebSecurityOrigin
;
28 using WebKit::WebString
;
30 using WebKit::WebView
;
31 using content::DocumentState
;
32 using content::NavigationState
;
36 GURL
GetOriginOrURL(const WebFrame
* frame
) {
37 WebString top_origin
= frame
->top()->document().securityOrigin().toString();
38 // The the |top_origin| is unique ("null") e.g., for file:// URLs. Use the
39 // document URL as the primary URL in those cases.
40 if (top_origin
== "null")
41 return frame
->top()->document().url();
42 return GURL(top_origin
);
45 ContentSetting
GetContentSettingFromRules(
46 const ContentSettingsForOneType
& rules
,
47 const WebFrame
* frame
,
48 const GURL
& secondary_url
) {
49 ContentSettingsForOneType::const_iterator it
;
50 // If there is only one rule, it's the default rule and we don't need to match
52 if (rules
.size() == 1) {
53 DCHECK(rules
[0].primary_pattern
== ContentSettingsPattern::Wildcard());
54 DCHECK(rules
[0].secondary_pattern
== ContentSettingsPattern::Wildcard());
55 return rules
[0].setting
;
57 const GURL
& primary_url
= GetOriginOrURL(frame
);
58 for (it
= rules
.begin(); it
!= rules
.end(); ++it
) {
59 if (it
->primary_pattern
.Matches(primary_url
) &&
60 it
->secondary_pattern
.Matches(secondary_url
)) {
65 return CONTENT_SETTING_DEFAULT
;
70 ContentSettingsObserver::ContentSettingsObserver(
71 content::RenderView
* render_view
)
72 : content::RenderViewObserver(render_view
),
73 content::RenderViewObserverTracker
<ContentSettingsObserver
>(render_view
),
74 content_setting_rules_(NULL
),
75 is_interstitial_page_(false),
76 npapi_plugins_blocked_(false) {
77 ClearBlockedContentSettings();
80 ContentSettingsObserver::~ContentSettingsObserver() {
83 void ContentSettingsObserver::SetContentSettingRules(
84 const RendererContentSettingRules
* content_setting_rules
) {
85 content_setting_rules_
= content_setting_rules
;
88 bool ContentSettingsObserver::IsPluginTemporarilyAllowed(
89 const std::string
& identifier
) {
90 // If the empty string is in here, it means all plug-ins are allowed.
91 // TODO(bauerb): Remove this once we only pass in explicit identifiers.
92 return (temporarily_allowed_plugins_
.find(identifier
) !=
93 temporarily_allowed_plugins_
.end()) ||
94 (temporarily_allowed_plugins_
.find(std::string()) !=
95 temporarily_allowed_plugins_
.end());
98 void ContentSettingsObserver::DidBlockContentType(
99 ContentSettingsType settings_type
,
100 const std::string
& resource_identifier
) {
101 // Always send a message when |resource_identifier| is not empty, to tell the
102 // browser which resource was blocked (otherwise the browser will only show
103 // the first resource to be blocked, and none that are blocked at a later
105 if (!content_blocked_
[settings_type
] || !resource_identifier
.empty()) {
106 content_blocked_
[settings_type
] = true;
107 Send(new ChromeViewHostMsg_ContentBlocked(routing_id(), settings_type
,
108 resource_identifier
));
112 bool ContentSettingsObserver::OnMessageReceived(const IPC::Message
& message
) {
114 IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver
, message
)
115 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAsInterstitial
, OnSetAsInterstitial
)
116 IPC_MESSAGE_UNHANDLED(handled
= false)
117 IPC_END_MESSAGE_MAP()
121 // Don't swallow LoadBlockedPlugins messages, as they're sent to every
123 IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver
, message
)
124 IPC_MESSAGE_HANDLER(ChromeViewMsg_LoadBlockedPlugins
, OnLoadBlockedPlugins
)
125 IPC_END_MESSAGE_MAP()
130 void ContentSettingsObserver::DidCommitProvisionalLoad(
131 WebFrame
* frame
, bool is_new_navigation
) {
133 return; // Not a top-level navigation.
135 DocumentState
* document_state
= DocumentState::FromDataSource(
136 frame
->dataSource());
137 NavigationState
* navigation_state
= document_state
->navigation_state();
138 if (!navigation_state
->was_within_same_page()) {
139 // Clear "block" flags for the new page. This needs to happen before any of
140 // |AllowScript()|, |AllowScriptFromSource()|, |AllowImage()|, or
141 // |AllowPlugins()| is called for the new page so that these functions can
142 // correctly detect that a piece of content flipped from "not blocked" to
144 ClearBlockedContentSettings();
145 temporarily_allowed_plugins_
.clear();
148 GURL url
= frame
->document().url();
149 // If we start failing this DCHECK, please makes sure we don't regress
150 // this bug: http://code.google.com/p/chromium/issues/detail?id=79304
151 DCHECK(frame
->document().securityOrigin().toString() == "null" ||
152 !url
.SchemeIs(chrome::kDataScheme
));
155 bool ContentSettingsObserver::AllowDatabase(WebFrame
* frame
,
156 const WebString
& name
,
157 const WebString
& display_name
,
158 unsigned long estimated_size
) {
159 if (frame
->document().securityOrigin().isUnique() ||
160 frame
->top()->document().securityOrigin().isUnique())
164 Send(new ChromeViewHostMsg_AllowDatabase(
165 routing_id(), GURL(frame
->document().securityOrigin().toString()),
166 GURL(frame
->top()->document().securityOrigin().toString()),
167 name
, display_name
, &result
));
171 bool ContentSettingsObserver::AllowFileSystem(WebFrame
* frame
) {
172 if (frame
->document().securityOrigin().isUnique() ||
173 frame
->top()->document().securityOrigin().isUnique())
177 Send(new ChromeViewHostMsg_AllowFileSystem(
178 routing_id(), GURL(frame
->document().securityOrigin().toString()),
179 GURL(frame
->top()->document().securityOrigin().toString()), &result
));
183 bool ContentSettingsObserver::AllowImage(WebFrame
* frame
,
184 bool enabled_per_settings
,
185 const WebURL
& image_url
) {
186 bool allow
= enabled_per_settings
;
187 if (enabled_per_settings
) {
188 if (is_interstitial_page_
)
190 if (IsWhitelistedForContentSettings(frame
))
193 if (content_setting_rules_
) {
194 GURL
secondary_url(image_url
);
195 allow
= GetContentSettingFromRules(
196 content_setting_rules_
->image_rules
,
197 frame
, secondary_url
) != CONTENT_SETTING_BLOCK
;
201 DidBlockContentType(CONTENT_SETTINGS_TYPE_IMAGES
, std::string());
205 bool ContentSettingsObserver::AllowIndexedDB(WebFrame
* frame
,
206 const WebString
& name
,
207 const WebSecurityOrigin
& origin
) {
208 if (frame
->document().securityOrigin().isUnique() ||
209 frame
->top()->document().securityOrigin().isUnique())
213 Send(new ChromeViewHostMsg_AllowIndexedDB(
214 routing_id(), GURL(frame
->document().securityOrigin().toString()),
215 GURL(frame
->top()->document().securityOrigin().toString()),
220 bool ContentSettingsObserver::AllowPlugins(WebFrame
* frame
,
221 bool enabled_per_settings
) {
222 return enabled_per_settings
;
225 bool ContentSettingsObserver::AllowScript(WebFrame
* frame
,
226 bool enabled_per_settings
) {
227 if (!enabled_per_settings
)
229 if (is_interstitial_page_
)
232 std::map
<WebFrame
*, bool>::const_iterator it
=
233 cached_script_permissions_
.find(frame
);
234 if (it
!= cached_script_permissions_
.end())
237 // Evaluate the content setting rules before
238 // |IsWhitelistedForContentSettings|; if there is only the default rule
239 // allowing all scripts, it's quicker this way.
241 if (content_setting_rules_
) {
242 ContentSetting setting
= GetContentSettingFromRules(
243 content_setting_rules_
->script_rules
,
245 GURL(frame
->document().securityOrigin().toString()));
246 allow
= setting
!= CONTENT_SETTING_BLOCK
;
248 allow
= allow
|| IsWhitelistedForContentSettings(frame
);
250 cached_script_permissions_
[frame
] = allow
;
254 bool ContentSettingsObserver::AllowScriptFromSource(
256 bool enabled_per_settings
,
257 const WebKit::WebURL
& script_url
) {
258 if (!enabled_per_settings
)
260 if (is_interstitial_page_
)
264 if (content_setting_rules_
) {
265 ContentSetting setting
= GetContentSettingFromRules(
266 content_setting_rules_
->script_rules
,
269 allow
= setting
!= CONTENT_SETTING_BLOCK
;
271 return allow
|| IsWhitelistedForContentSettings(frame
);
274 bool ContentSettingsObserver::AllowStorage(WebFrame
* frame
, bool local
) {
275 if (frame
->document().securityOrigin().isUnique() ||
276 frame
->top()->document().securityOrigin().isUnique())
280 StoragePermissionsKey
key(
281 GURL(frame
->document().securityOrigin().toString()), local
);
282 std::map
<StoragePermissionsKey
, bool>::const_iterator permissions
=
283 cached_storage_permissions_
.find(key
);
284 if (permissions
!= cached_storage_permissions_
.end())
285 return permissions
->second
;
287 Send(new ChromeViewHostMsg_AllowDOMStorage(
288 routing_id(), GURL(frame
->document().securityOrigin().toString()),
289 GURL(frame
->top()->document().securityOrigin().toString()),
291 cached_storage_permissions_
[key
] = result
;
295 void ContentSettingsObserver::DidNotAllowPlugins() {
296 DidBlockContentType(CONTENT_SETTINGS_TYPE_PLUGINS
, std::string());
299 void ContentSettingsObserver::DidNotAllowScript() {
300 DidBlockContentType(CONTENT_SETTINGS_TYPE_JAVASCRIPT
, std::string());
303 void ContentSettingsObserver::DidNotAllowMixedScript() {
304 DidBlockContentType(CONTENT_SETTINGS_TYPE_MIXEDSCRIPT
, std::string());
307 void ContentSettingsObserver::BlockNPAPIPlugins() {
308 npapi_plugins_blocked_
= true;
311 bool ContentSettingsObserver::AreNPAPIPluginsBlocked() const {
312 return npapi_plugins_blocked_
;
315 void ContentSettingsObserver::OnLoadBlockedPlugins(
316 const std::string
& identifier
) {
317 temporarily_allowed_plugins_
.insert(identifier
);
320 void ContentSettingsObserver::OnSetAsInterstitial() {
321 is_interstitial_page_
= true;
324 void ContentSettingsObserver::ClearBlockedContentSettings() {
325 for (size_t i
= 0; i
< arraysize(content_blocked_
); ++i
)
326 content_blocked_
[i
] = false;
327 cached_storage_permissions_
.clear();
328 cached_script_permissions_
.clear();
331 bool ContentSettingsObserver::IsWhitelistedForContentSettings(WebFrame
* frame
) {
332 // Whitelist Instant processes.
333 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kInstantProcess
))
336 // Whitelist ftp directory listings, as they require JavaScript to function
338 webkit_glue::WebURLResponseExtraDataImpl
* extra_data
=
339 static_cast<webkit_glue::WebURLResponseExtraDataImpl
*>(
340 frame
->dataSource()->response().extraData());
341 if (extra_data
&& extra_data
->is_ftp_directory_listing())
343 return IsWhitelistedForContentSettings(frame
->document().securityOrigin(),
344 frame
->document().url());
347 bool ContentSettingsObserver::IsWhitelistedForContentSettings(
348 const WebSecurityOrigin
& origin
,
349 const GURL
& document_url
) {
350 if (document_url
== GURL(content::kUnreachableWebDataURL
))
353 if (origin
.isUnique())
354 return false; // Uninitialized document?
356 if (EqualsASCII(origin
.protocol(), chrome::kChromeUIScheme
))
357 return true; // Browser UI elements should still work.
359 if (EqualsASCII(origin
.protocol(), chrome::kChromeDevToolsScheme
))
360 return true; // DevTools UI elements should still work.
362 if (EqualsASCII(origin
.protocol(), extensions::kExtensionScheme
))
365 if (EqualsASCII(origin
.protocol(), chrome::kChromeInternalScheme
))
368 // If the scheme is file:, an empty file name indicates a directory listing,
369 // which requires JavaScript to function properly.
370 if (EqualsASCII(origin
.protocol(), chrome::kFileScheme
)) {
371 return document_url
.SchemeIs(chrome::kFileScheme
) &&
372 document_url
.ExtractFileName().empty();