// Copyright (c) 2009 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/child_process_security_policy.h"

#include "base/file_path.h"
#include "base/logging.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "chrome/common/bindings_policy.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/gurl.h"
#include "net/url_request/url_request.h"

// The SecurityState class is used to maintain per-renderer security state
// information.
class ChildProcessSecurityPolicy::SecurityState {
 public:
  SecurityState()
    : enabled_bindings_(0),
      can_read_raw_cookies_(false) { }
  ~SecurityState() {
    scheme_policy_.clear();
  }

  // Grant permission to request URLs with the specified scheme.
  void GrantScheme(const std::string& scheme) {
    scheme_policy_[scheme] = true;
  }

  // Revoke permission to request URLs with the specified scheme.
  void RevokeScheme(const std::string& scheme) {
    scheme_policy_[scheme] = false;
  }

  // Grant permission to upload the specified file to the web.
  void GrantUploadFile(const FilePath& file) {
    uploadable_files_.insert(file);
  }

  void GrantBindings(int bindings) {
    enabled_bindings_ |= bindings;
  }

  void GrantReadRawCookies() {
    can_read_raw_cookies_ = true;
  }

  void RevokeReadRawCookies() {
    can_read_raw_cookies_ = false;
  }

  // Determine whether permission has been granted to request url.
  // Schemes that have not been granted default to being denied.
  bool CanRequestURL(const GURL& url) {
    SchemeMap::const_iterator judgment(scheme_policy_.find(url.scheme()));

    if (judgment == scheme_policy_.end())
      return false;  // Unmentioned schemes are disallowed.

    return judgment->second;
  }

  // Determine whether permission has been granted to upload file.
  // Files that have not been granted default to being denied.
  bool CanUploadFile(const FilePath& file) {
    return uploadable_files_.find(file) != uploadable_files_.end();
  }

  bool has_dom_ui_bindings() const {
    return BindingsPolicy::is_dom_ui_enabled(enabled_bindings_);
  }

  bool has_extension_bindings() const {
    return BindingsPolicy::is_extension_enabled(enabled_bindings_);
  }

  bool can_read_raw_cookies() const {
    return can_read_raw_cookies_;
  }

 private:
  typedef std::map<std::string, bool> SchemeMap;
  typedef std::set<FilePath> FileSet;

  // Maps URL schemes to whether permission has been granted or revoked:
  //   |true| means the scheme has been granted.
  //   |false| means the scheme has been revoked.
  // If a scheme is not present in the map, then it has never been granted
  // or revoked.
  SchemeMap scheme_policy_;

  // The set of files the renderer is permited to upload to the web.
  FileSet uploadable_files_;

  int enabled_bindings_;

  bool can_read_raw_cookies_;

  DISALLOW_COPY_AND_ASSIGN(SecurityState);
};

ChildProcessSecurityPolicy::ChildProcessSecurityPolicy() {
  // We know about these schemes and believe them to be safe.
  RegisterWebSafeScheme(chrome::kHttpScheme);
  RegisterWebSafeScheme(chrome::kHttpsScheme);
  RegisterWebSafeScheme(chrome::kFtpScheme);
  RegisterWebSafeScheme(chrome::kDataScheme);
  RegisterWebSafeScheme("feed");
  RegisterWebSafeScheme(chrome::kExtensionScheme);

  // We know about the following psuedo schemes and treat them specially.
  RegisterPseudoScheme(chrome::kAboutScheme);
  RegisterPseudoScheme(chrome::kJavaScriptScheme);
  RegisterPseudoScheme(chrome::kViewSourceScheme);
}

ChildProcessSecurityPolicy::~ChildProcessSecurityPolicy() {
  web_safe_schemes_.clear();
  pseudo_schemes_.clear();
  STLDeleteContainerPairSecondPointers(security_state_.begin(),
                                       security_state_.end());
  security_state_.clear();
}

// static
ChildProcessSecurityPolicy* ChildProcessSecurityPolicy::GetInstance() {
  return Singleton<ChildProcessSecurityPolicy>::get();
}

void ChildProcessSecurityPolicy::Add(int renderer_id) {
  AutoLock lock(lock_);
  if (security_state_.count(renderer_id) != 0) {
    NOTREACHED() << "Add renderers at most once.";
    return;
  }

  security_state_[renderer_id] = new SecurityState();
}

void ChildProcessSecurityPolicy::Remove(int renderer_id) {
  AutoLock lock(lock_);
  if (!security_state_.count(renderer_id))
    return;  // May be called multiple times.

  delete security_state_[renderer_id];
  security_state_.erase(renderer_id);
}

void ChildProcessSecurityPolicy::RegisterWebSafeScheme(
    const std::string& scheme) {
  AutoLock lock(lock_);
  DCHECK(web_safe_schemes_.count(scheme) == 0) << "Add schemes at most once.";
  DCHECK(pseudo_schemes_.count(scheme) == 0) << "Web-safe implies not psuedo.";

  web_safe_schemes_.insert(scheme);
}

bool ChildProcessSecurityPolicy::IsWebSafeScheme(const std::string& scheme) {
  AutoLock lock(lock_);

  return (web_safe_schemes_.find(scheme) != web_safe_schemes_.end());
}

void ChildProcessSecurityPolicy::RegisterPseudoScheme(
    const std::string& scheme) {
  AutoLock lock(lock_);
  DCHECK(pseudo_schemes_.count(scheme) == 0) << "Add schemes at most once.";
  DCHECK(web_safe_schemes_.count(scheme) == 0) <<
      "Psuedo implies not web-safe.";

  pseudo_schemes_.insert(scheme);
}

bool ChildProcessSecurityPolicy::IsPseudoScheme(const std::string& scheme) {
  AutoLock lock(lock_);

  return (pseudo_schemes_.find(scheme) != pseudo_schemes_.end());
}

void ChildProcessSecurityPolicy::GrantRequestURL(
    int renderer_id, const GURL& url) {

  if (!url.is_valid())
    return;  // Can't grant the capability to request invalid URLs.

  if (IsWebSafeScheme(url.scheme()))
    return;  // The scheme has already been white-listed for every renderer.

  if (IsPseudoScheme(url.scheme())) {
    // The view-source scheme is a special case of a pseudo-URL that eventually
    // results in requesting its embedded URL.
    if (url.SchemeIs(chrome::kViewSourceScheme)) {
      // URLs with the view-source scheme typically look like:
      //   view-source:http://www.google.com/a
      // In order to request these URLs, the renderer needs to be able to
      // request the embedded URL.
      GrantRequestURL(renderer_id, GURL(url.path()));
    }

    return;  // Can't grant the capability to request pseudo schemes.
  }

  {
    AutoLock lock(lock_);
    SecurityStateMap::iterator state = security_state_.find(renderer_id);
    if (state == security_state_.end())
      return;

    // If the renderer has been commanded to request a scheme, then we grant
    // it the capability to request URLs of that scheme.
    state->second->GrantScheme(url.scheme());
  }
}

void ChildProcessSecurityPolicy::GrantUploadFile(int renderer_id,
                                             const FilePath& file) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->GrantUploadFile(file);
}

void ChildProcessSecurityPolicy::GrantScheme(int renderer_id,
                                             const std::string& scheme) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->GrantScheme(scheme);
}

void ChildProcessSecurityPolicy::GrantInspectElement(int renderer_id) {
  // The inspector is served from a chrome: URL.  In order to run the
  // inspector, the renderer needs to be able to load chrome: URLs.
  GrantScheme(renderer_id, chrome::kChromeUIScheme);
}

void ChildProcessSecurityPolicy::GrantDOMUIBindings(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->GrantBindings(BindingsPolicy::DOM_UI);

  // DOM UI bindings need the ability to request chrome: URLs.
  state->second->GrantScheme(chrome::kChromeUIScheme);

  // DOM UI pages can contain links to file:// URLs.
  state->second->GrantScheme(chrome::kFileScheme);
}

void ChildProcessSecurityPolicy::GrantExtensionBindings(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->GrantBindings(BindingsPolicy::EXTENSION);
}

void ChildProcessSecurityPolicy::GrantReadRawCookies(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->GrantReadRawCookies();
}

void ChildProcessSecurityPolicy::RevokeReadRawCookies(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return;

  state->second->RevokeReadRawCookies();
}

bool ChildProcessSecurityPolicy::CanRequestURL(
    int renderer_id, const GURL& url) {
  if (!url.is_valid())
    return false;  // Can't request invalid URLs.

  if (IsWebSafeScheme(url.scheme()))
    return true;  // The scheme has been white-listed for every renderer.

  if (IsPseudoScheme(url.scheme())) {
    // There are a number of special cases for pseudo schemes.

    if (url.SchemeIs(chrome::kViewSourceScheme)) {
      // A view-source URL is allowed if the renderer is permitted to request
      // the embedded URL. Careful to avoid pointless recursion.
      GURL child_url(url.path());
      if (child_url.SchemeIs(chrome::kViewSourceScheme) &&
          url.SchemeIs(chrome::kViewSourceScheme))
          return false;

      return CanRequestURL(renderer_id, child_url);
    }

    if (LowerCaseEqualsASCII(url.spec(), chrome::kAboutBlankURL))
      return true;  // Every renderer can request <about:blank>.

    // URLs like <about:memory> and <about:crash> shouldn't be requestable by
    // any renderer.  Also, this case covers <javascript:...>, which should be
    // handled internally by the renderer and not kicked up to the browser.
    return false;
  }

  if (!URLRequest::IsHandledURL(url))
    return true;  // This URL request is destined for ShellExecute.

  {
    AutoLock lock(lock_);

    SecurityStateMap::iterator state = security_state_.find(renderer_id);
    if (state == security_state_.end())
      return false;

    // Otherwise, we consult the renderer's security state to see if it is
    // allowed to request the URL.
    return state->second->CanRequestURL(url);
  }
}

bool ChildProcessSecurityPolicy::CanUploadFile(int renderer_id,
                                               const FilePath& file) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return false;

  return state->second->CanUploadFile(file);
}

bool ChildProcessSecurityPolicy::HasDOMUIBindings(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return false;

  return state->second->has_dom_ui_bindings();
}

bool ChildProcessSecurityPolicy::HasExtensionBindings(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return false;

  return state->second->has_extension_bindings();
}

bool ChildProcessSecurityPolicy::CanReadRawCookies(int renderer_id) {
  AutoLock lock(lock_);

  SecurityStateMap::iterator state = security_state_.find(renderer_id);
  if (state == security_state_.end())
    return false;

  return state->second->can_read_raw_cookies();
}