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. 4 5 #include "chrome/renderer/content_settings_observer.h" 6 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" 23 24 using WebKit::WebDataSource; 25 using WebKit::WebFrame; 26 using WebKit::WebFrameClient; 27 using WebKit::WebSecurityOrigin; 28 using WebKit::WebString; 29 using WebKit::WebURL; 30 using WebKit::WebView; 31 using content::DocumentState; 32 using content::NavigationState; 33 34 namespace { 35 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); 43 } 44 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 51 // the patterns. 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; 56 } 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)) { 61 return it->setting; 62 } 63 } 64 NOTREACHED(); 65 return CONTENT_SETTING_DEFAULT; 66 } 67 68 } // namespace 69 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(); 78 } 79 80 ContentSettingsObserver::~ContentSettingsObserver() { 81 } 82 83 void ContentSettingsObserver::SetContentSettingRules( 84 const RendererContentSettingRules* content_setting_rules) { 85 content_setting_rules_ = content_setting_rules; 86 } 87 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()); 96 } 97 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 104 // time). 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)); 109 } 110 } 111 112 bool ContentSettingsObserver::OnMessageReceived(const IPC::Message& message) { 113 bool handled = true; 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() 118 if (handled) 119 return true; 120 121 // Don't swallow LoadBlockedPlugins messages, as they're sent to every 122 // blocked plugin. 123 IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserver, message) 124 IPC_MESSAGE_HANDLER(ChromeViewMsg_LoadBlockedPlugins, OnLoadBlockedPlugins) 125 IPC_END_MESSAGE_MAP() 126 127 return false; 128 } 129 130 void ContentSettingsObserver::DidCommitProvisionalLoad( 131 WebFrame* frame, bool is_new_navigation) { 132 if (frame->parent()) 133 return; // Not a top-level navigation. 134 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 143 // "blocked". 144 ClearBlockedContentSettings(); 145 temporarily_allowed_plugins_.clear(); 146 } 147 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)); 153 } 154 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()) 161 return false; 162 163 bool result = false; 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)); 168 return result; 169 } 170 171 bool ContentSettingsObserver::AllowFileSystem(WebFrame* frame) { 172 if (frame->document().securityOrigin().isUnique() || 173 frame->top()->document().securityOrigin().isUnique()) 174 return false; 175 176 bool result = false; 177 Send(new ChromeViewHostMsg_AllowFileSystem( 178 routing_id(), GURL(frame->document().securityOrigin().toString()), 179 GURL(frame->top()->document().securityOrigin().toString()), &result)); 180 return result; 181 } 182 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_) 189 return true; 190 if (IsWhitelistedForContentSettings(frame)) 191 return true; 192 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; 198 } 199 } 200 if (!allow) 201 DidBlockContentType(CONTENT_SETTINGS_TYPE_IMAGES, std::string()); 202 return allow; 203 } 204 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()) 210 return false; 211 212 bool result = false; 213 Send(new ChromeViewHostMsg_AllowIndexedDB( 214 routing_id(), GURL(frame->document().securityOrigin().toString()), 215 GURL(frame->top()->document().securityOrigin().toString()), 216 name, &result)); 217 return result; 218 } 219 220 bool ContentSettingsObserver::AllowPlugins(WebFrame* frame, 221 bool enabled_per_settings) { 222 return enabled_per_settings; 223 } 224 225 bool ContentSettingsObserver::AllowScript(WebFrame* frame, 226 bool enabled_per_settings) { 227 if (!enabled_per_settings) 228 return false; 229 if (is_interstitial_page_) 230 return true; 231 232 std::map<WebFrame*, bool>::const_iterator it = 233 cached_script_permissions_.find(frame); 234 if (it != cached_script_permissions_.end()) 235 return it->second; 236 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. 240 bool allow = true; 241 if (content_setting_rules_) { 242 ContentSetting setting = GetContentSettingFromRules( 243 content_setting_rules_->script_rules, 244 frame, 245 GURL(frame->document().securityOrigin().toString())); 246 allow = setting != CONTENT_SETTING_BLOCK; 247 } 248 allow = allow || IsWhitelistedForContentSettings(frame); 249 250 cached_script_permissions_[frame] = allow; 251 return allow; 252 } 253 254 bool ContentSettingsObserver::AllowScriptFromSource( 255 WebFrame* frame, 256 bool enabled_per_settings, 257 const WebKit::WebURL& script_url) { 258 if (!enabled_per_settings) 259 return false; 260 if (is_interstitial_page_) 261 return true; 262 263 bool allow = true; 264 if (content_setting_rules_) { 265 ContentSetting setting = GetContentSettingFromRules( 266 content_setting_rules_->script_rules, 267 frame, 268 GURL(script_url)); 269 allow = setting != CONTENT_SETTING_BLOCK; 270 } 271 return allow || IsWhitelistedForContentSettings(frame); 272 } 273 274 bool ContentSettingsObserver::AllowStorage(WebFrame* frame, bool local) { 275 if (frame->document().securityOrigin().isUnique() || 276 frame->top()->document().securityOrigin().isUnique()) 277 return false; 278 bool result = false; 279 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; 286 287 Send(new ChromeViewHostMsg_AllowDOMStorage( 288 routing_id(), GURL(frame->document().securityOrigin().toString()), 289 GURL(frame->top()->document().securityOrigin().toString()), 290 local, &result)); 291 cached_storage_permissions_[key] = result; 292 return result; 293 } 294 295 void ContentSettingsObserver::DidNotAllowPlugins() { 296 DidBlockContentType(CONTENT_SETTINGS_TYPE_PLUGINS, std::string()); 297 } 298 299 void ContentSettingsObserver::DidNotAllowScript() { 300 DidBlockContentType(CONTENT_SETTINGS_TYPE_JAVASCRIPT, std::string()); 301 } 302 303 void ContentSettingsObserver::DidNotAllowMixedScript() { 304 DidBlockContentType(CONTENT_SETTINGS_TYPE_MIXEDSCRIPT, std::string()); 305 } 306 307 void ContentSettingsObserver::BlockNPAPIPlugins() { 308 npapi_plugins_blocked_ = true; 309 } 310 311 bool ContentSettingsObserver::AreNPAPIPluginsBlocked() const { 312 return npapi_plugins_blocked_; 313 } 314 315 void ContentSettingsObserver::OnLoadBlockedPlugins( 316 const std::string& identifier) { 317 temporarily_allowed_plugins_.insert(identifier); 318 } 319 320 void ContentSettingsObserver::OnSetAsInterstitial() { 321 is_interstitial_page_ = true; 322 } 323 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(); 329 } 330 331 bool ContentSettingsObserver::IsWhitelistedForContentSettings(WebFrame* frame) { 332 // Whitelist Instant processes. 333 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kInstantProcess)) 334 return true; 335 336 // Whitelist ftp directory listings, as they require JavaScript to function 337 // properly. 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()) 342 return true; 343 return IsWhitelistedForContentSettings(frame->document().securityOrigin(), 344 frame->document().url()); 345 } 346 347 bool ContentSettingsObserver::IsWhitelistedForContentSettings( 348 const WebSecurityOrigin& origin, 349 const GURL& document_url) { 350 if (document_url == GURL(content::kUnreachableWebDataURL)) 351 return true; 352 353 if (origin.isUnique()) 354 return false; // Uninitialized document? 355 356 if (EqualsASCII(origin.protocol(), chrome::kChromeUIScheme)) 357 return true; // Browser UI elements should still work. 358 359 if (EqualsASCII(origin.protocol(), chrome::kChromeDevToolsScheme)) 360 return true; // DevTools UI elements should still work. 361 362 if (EqualsASCII(origin.protocol(), extensions::kExtensionScheme)) 363 return true; 364 365 if (EqualsASCII(origin.protocol(), chrome::kChromeInternalScheme)) 366 return true; 367 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(); 373 } 374 375 return false; 376 } 377