Home | History | Annotate | Download | only in renderer
      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