// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


#include "chrome/browser/content_settings/content_settings_base_provider.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "chrome/common/chrome_switches.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"

namespace {

// True if a given content settings type requires additional resource
// identifiers.
const bool kRequiresResourceIdentifier[CONTENT_SETTINGS_NUM_TYPES] = {
  false,  // CONTENT_SETTINGS_TYPE_COOKIES
  false,  // CONTENT_SETTINGS_TYPE_IMAGES
  false,  // CONTENT_SETTINGS_TYPE_JAVASCRIPT
  true,   // CONTENT_SETTINGS_TYPE_PLUGINS
  false,  // CONTENT_SETTINGS_TYPE_POPUPS
  false,  // Not used for Geolocation
  false,  // Not used for Notifications
};

}  // namespace

namespace content_settings {

ExtendedContentSettings::ExtendedContentSettings() {}

ExtendedContentSettings::ExtendedContentSettings(
    const ExtendedContentSettings& rhs)
    : content_settings(rhs.content_settings),
      content_settings_for_resources(rhs.content_settings_for_resources) {
}

ExtendedContentSettings::~ExtendedContentSettings() {}

BaseProvider::BaseProvider(bool is_incognito)
    : is_incognito_(is_incognito) {
}

BaseProvider::~BaseProvider() {}

bool BaseProvider::RequiresResourceIdentifier(
    ContentSettingsType content_type) const {
  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kEnableResourceContentSettings)) {
    return kRequiresResourceIdentifier[content_type];
  } else {
    return false;
  }
}

bool BaseProvider::AllDefault(
    const ExtendedContentSettings& settings) const {
  for (size_t i = 0; i < arraysize(settings.content_settings.settings); ++i) {
    if (settings.content_settings.settings[i] != CONTENT_SETTING_DEFAULT)
      return false;
  }
  return settings.content_settings_for_resources.empty();
}

ContentSetting BaseProvider::GetContentSetting(
    const GURL& requesting_url,
    const GURL& embedding_url,
    ContentSettingsType content_type,
    const ResourceIdentifier& resource_identifier) const {
  // Support for embedding_patterns is not implemented yet.
  DCHECK(requesting_url == embedding_url);

  if (!RequiresResourceIdentifier(content_type) ||
      (RequiresResourceIdentifier(content_type) && resource_identifier.empty()))
    return GetNonDefaultContentSettings(requesting_url).settings[content_type];

  // Resolve content settings with resource identifier.
  // 1. Check for pattern that exactly match the url/host
  //     1.1 In the content-settings-map
  //     1.2 In the incognito content-settings-map
  // 3. Shorten the url subdomain by subdomain and try to find a pattern in
  //     3.1 OTR content-settings-map
  //     3.2 content-settings-map
  base::AutoLock auto_lock(lock_);
  const std::string host(net::GetHostOrSpecFromURL(requesting_url));
  ContentSettingsTypeResourceIdentifierPair
      requested_setting(content_type, resource_identifier);

  // Check for exact matches first.
  HostContentSettings::const_iterator i(host_content_settings_.find(host));
  if (i != host_content_settings_.end() &&
      i->second.content_settings_for_resources.find(requested_setting) !=
      i->second.content_settings_for_resources.end()) {
    return i->second.content_settings_for_resources.find(
        requested_setting)->second;
  }

  // If this map is not for an incognito profile, these searches will never
  // match. The additional incognito exceptions always overwrite the
  // regular ones.
  i = incognito_settings_.find(host);
  if (i != incognito_settings_.end() &&
      i->second.content_settings_for_resources.find(requested_setting) !=
      i->second.content_settings_for_resources.end()) {
    return i->second.content_settings_for_resources.find(
        requested_setting)->second;
  }

  // Match patterns starting with the most concrete pattern match.
  for (std::string key =
       std::string(ContentSettingsPattern::kDomainWildcard) + host; ; ) {
    HostContentSettings::const_iterator i(incognito_settings_.find(key));
    if (i != incognito_settings_.end() &&
        i->second.content_settings_for_resources.find(requested_setting) !=
        i->second.content_settings_for_resources.end()) {
      return i->second.content_settings_for_resources.find(
          requested_setting)->second;
    }

    i = host_content_settings_.find(key);
    if (i != host_content_settings_.end() &&
        i->second.content_settings_for_resources.find(requested_setting) !=
        i->second.content_settings_for_resources.end()) {
      return i->second.content_settings_for_resources.find(
          requested_setting)->second;
    }

    const size_t next_dot =
        key.find('.', ContentSettingsPattern::kDomainWildcardLength);
    if (next_dot == std::string::npos)
      break;
    key.erase(ContentSettingsPattern::kDomainWildcardLength,
              next_dot - ContentSettingsPattern::kDomainWildcardLength + 1);
  }

  return CONTENT_SETTING_DEFAULT;
}

void BaseProvider::GetAllContentSettingsRules(
    ContentSettingsType content_type,
    const ResourceIdentifier& resource_identifier,
    Rules* content_setting_rules) const {
  DCHECK(content_setting_rules);
  content_setting_rules->clear();

  const HostContentSettings* map_to_return =
      is_incognito_ ? &incognito_settings_ : &host_content_settings_;
  ContentSettingsTypeResourceIdentifierPair requested_setting(
      content_type, resource_identifier);

  base::AutoLock auto_lock(lock_);
  for (HostContentSettings::const_iterator i(map_to_return->begin());
       i != map_to_return->end(); ++i) {
    ContentSetting setting;
    if (RequiresResourceIdentifier(content_type)) {
      if (i->second.content_settings_for_resources.find(requested_setting) !=
          i->second.content_settings_for_resources.end()) {
        setting = i->second.content_settings_for_resources.find(
            requested_setting)->second;
      } else {
        setting = CONTENT_SETTING_DEFAULT;
      }
    } else {
     setting = i->second.content_settings.settings[content_type];
    }
    if (setting != CONTENT_SETTING_DEFAULT) {
      // Use of push_back() relies on the map iterator traversing in order of
      // ascending keys.
      content_setting_rules->push_back(Rule(ContentSettingsPattern(i->first),
                                            ContentSettingsPattern(i->first),
                                            setting));
    }
  }
}

ContentSettings BaseProvider::GetNonDefaultContentSettings(
    const GURL& url) const {
  base::AutoLock auto_lock(lock_);

  const std::string host(net::GetHostOrSpecFromURL(url));
  ContentSettings output;
  for (int j = 0; j < CONTENT_SETTINGS_NUM_TYPES; ++j)
    output.settings[j] = CONTENT_SETTING_DEFAULT;

  // Check for exact matches first.
  HostContentSettings::const_iterator i(host_content_settings_.find(host));
  if (i != host_content_settings_.end())
    output = i->second.content_settings;

  // If this map is not for an incognito profile, these searches will never
  // match. The additional incognito exceptions always overwrite the
  // regular ones.
  i = incognito_settings_.find(host);
  if (i != incognito_settings_.end()) {
    for (int j = 0; j < CONTENT_SETTINGS_NUM_TYPES; ++j)
      if (i->second.content_settings.settings[j] != CONTENT_SETTING_DEFAULT)
        output.settings[j] = i->second.content_settings.settings[j];
  }

  // Match patterns starting with the most concrete pattern match.
  for (std::string key =
       std::string(ContentSettingsPattern::kDomainWildcard) + host; ; ) {
    HostContentSettings::const_iterator i(incognito_settings_.find(key));
    if (i != incognito_settings_.end()) {
      for (int j = 0; j < CONTENT_SETTINGS_NUM_TYPES; ++j) {
        if (output.settings[j] == CONTENT_SETTING_DEFAULT)
          output.settings[j] = i->second.content_settings.settings[j];
      }
    }
    i = host_content_settings_.find(key);
    if (i != host_content_settings_.end()) {
      for (int j = 0; j < CONTENT_SETTINGS_NUM_TYPES; ++j) {
        if (output.settings[j] == CONTENT_SETTING_DEFAULT)
          output.settings[j] = i->second.content_settings.settings[j];
      }
    }
    const size_t next_dot =
        key.find('.', ContentSettingsPattern::kDomainWildcardLength);
    if (next_dot == std::string::npos)
      break;
    key.erase(ContentSettingsPattern::kDomainWildcardLength,
              next_dot - ContentSettingsPattern::kDomainWildcardLength + 1);
  }

  return output;
}

void BaseProvider::UpdateContentSettingsMap(
    const ContentSettingsPattern& requesting_pattern,
    const ContentSettingsPattern& embedding_pattern,
    ContentSettingsType content_type,
    const ResourceIdentifier& resource_identifier,
    ContentSetting content_setting) {
  std::string pattern_str(requesting_pattern.CanonicalizePattern());
  HostContentSettings* content_settings_map = host_content_settings();
  ExtendedContentSettings& extended_settings =
      (*content_settings_map)[pattern_str];
  extended_settings.content_settings.settings[content_type] = content_setting;
}

// static
ContentSetting BaseProvider::ClickToPlayFixup(ContentSettingsType content_type,
                                              ContentSetting setting) {
  if (setting == CONTENT_SETTING_ASK &&
      content_type == CONTENT_SETTINGS_TYPE_PLUGINS &&
      !CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableClickToPlay)) {
    return CONTENT_SETTING_BLOCK;
  }
  return setting;
}

}  // namespace content_settings