// Copyright (c) 2012 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/common/content_settings.h"
#include "chrome/common/render_messages.h"
#include "chrome/renderer/content_settings_observer.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "content/public/renderer/render_view.h"
#include "ipc/ipc_message_macros.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/web/WebView.h"

using testing::_;
using testing::DeleteArg;

namespace {

class MockContentSettingsObserver : public ContentSettingsObserver {
 public:
  explicit MockContentSettingsObserver(content::RenderFrame* render_frame);

  virtual bool Send(IPC::Message* message);

  MOCK_METHOD1(OnContentBlocked,
               void(ContentSettingsType));

  MOCK_METHOD5(OnAllowDOMStorage,
               void(int, const GURL&, const GURL&, bool, IPC::Message*));
  GURL image_url_;
  std::string image_origin_;
};

MockContentSettingsObserver::MockContentSettingsObserver(
    content::RenderFrame* render_frame)
    : ContentSettingsObserver(render_frame, NULL),
      image_url_("http://www.foo.com/image.jpg"),
      image_origin_("http://www.foo.com") {
}

bool MockContentSettingsObserver::Send(IPC::Message* message) {
  IPC_BEGIN_MESSAGE_MAP(MockContentSettingsObserver, *message)
    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_ContentBlocked, OnContentBlocked)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_AllowDOMStorage,
                                    OnAllowDOMStorage)
    IPC_MESSAGE_UNHANDLED(ADD_FAILURE())
  IPC_END_MESSAGE_MAP()

  // Our super class deletes the message.
  return RenderFrameObserver::Send(message);
}

}  // namespace

TEST_F(ChromeRenderViewTest, DidBlockContentType) {
  MockContentSettingsObserver observer(view_->GetMainRenderFrame());
  EXPECT_CALL(observer,
              OnContentBlocked(CONTENT_SETTINGS_TYPE_COOKIES));
  observer.DidBlockContentType(CONTENT_SETTINGS_TYPE_COOKIES);

  // Blocking the same content type a second time shouldn't send a notification.
  observer.DidBlockContentType(CONTENT_SETTINGS_TYPE_COOKIES);
  ::testing::Mock::VerifyAndClearExpectations(&observer);
}

// Tests that multiple invokations of AllowDOMStorage result in a single IPC.
// Fails due to http://crbug.com/104300
TEST_F(ChromeRenderViewTest, DISABLED_AllowDOMStorage) {
  // Load some HTML, so we have a valid security origin.
  LoadHTML("<html></html>");
  MockContentSettingsObserver observer(view_->GetMainRenderFrame());
  ON_CALL(observer,
          OnAllowDOMStorage(_, _, _, _, _)).WillByDefault(DeleteArg<4>());
  EXPECT_CALL(observer,
              OnAllowDOMStorage(_, _, _, _, _));
  observer.allowStorage(true);

  // Accessing localStorage from the same origin again shouldn't result in a
  // new IPC.
  observer.allowStorage(true);
  ::testing::Mock::VerifyAndClearExpectations(&observer);
}

// Regression test for http://crbug.com/35011
TEST_F(ChromeRenderViewTest, JSBlockSentAfterPageLoad) {
  // 1. Load page with JS.
  std::string html = "<html>"
                     "<head>"
                     "<script>document.createElement('div');</script>"
                     "</head>"
                     "<body>"
                     "</body>"
                     "</html>";
  render_thread_->sink().ClearMessages();
  LoadHTML(html.c_str());

  // 2. Block JavaScript.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& script_setting_rules =
      content_setting_rules.script_rules;
  script_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_BLOCK,
                                  std::string(),
                                  false));
  ContentSettingsObserver* observer = ContentSettingsObserver::Get(
      view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);

  // Make sure no pending messages are in the queue.
  ProcessPendingMessages();
  render_thread_->sink().ClearMessages();

  // 3. Reload page.
  std::string url_str = "data:text/html;charset=utf-8,";
  url_str.append(html);
  GURL url(url_str);
  Reload(url);
  ProcessPendingMessages();

  // 4. Verify that the notification that javascript was blocked is sent after
  //    the navigation notification is sent.
  int navigation_index = -1;
  int block_index = -1;
  for (size_t i = 0; i < render_thread_->sink().message_count(); ++i) {
    const IPC::Message* msg = render_thread_->sink().GetMessageAt(i);
    if (msg->type() == GetNavigationIPCType())
      navigation_index = i;
    if (msg->type() == ChromeViewHostMsg_ContentBlocked::ID)
      block_index = i;
  }
  EXPECT_NE(-1, navigation_index);
  EXPECT_NE(-1, block_index);
  EXPECT_LT(navigation_index, block_index);
}

TEST_F(ChromeRenderViewTest, PluginsTemporarilyAllowed) {
  // Load some HTML.
  LoadHTML("<html>Foo</html>");

  std::string foo_plugin = "foo";
  std::string bar_plugin = "bar";

  ContentSettingsObserver* observer =
      ContentSettingsObserver::Get(view_->GetMainRenderFrame());
  EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(foo_plugin));

  // Temporarily allow the "foo" plugin.
  observer->OnLoadBlockedPlugins(foo_plugin);
  EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
  EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));

  // Simulate a navigation within the page.
  DidNavigateWithinPage(GetMainFrame(), true);
  EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
  EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));

  // Navigate to a different page.
  LoadHTML("<html>Bar</html>");
  EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(foo_plugin));
  EXPECT_FALSE(observer->IsPluginTemporarilyAllowed(bar_plugin));

  // Temporarily allow all plugins.
  observer->OnLoadBlockedPlugins(std::string());
  EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(foo_plugin));
  EXPECT_TRUE(observer->IsPluginTemporarilyAllowed(bar_plugin));
}

TEST_F(ChromeRenderViewTest, ImagesBlockedByDefault) {
  MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame());

  // Load some HTML.
  LoadHTML("<html>Foo</html>");

  // Set the default image blocking setting.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& image_setting_rules =
      content_setting_rules.image_rules;
  image_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_BLOCK,
                                  std::string(),
                                  false));

  ContentSettingsObserver* observer = ContentSettingsObserver::Get(
      view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);
  EXPECT_CALL(mock_observer,
              OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES));
  EXPECT_FALSE(observer->allowImage(true, mock_observer.image_url_));
  ::testing::Mock::VerifyAndClearExpectations(&observer);

  // Create an exception which allows the image.
  image_setting_rules.insert(
      image_setting_rules.begin(),
      ContentSettingPatternSource(
          ContentSettingsPattern::Wildcard(),
          ContentSettingsPattern::FromString(mock_observer.image_origin_),
          CONTENT_SETTING_ALLOW,
          std::string(),
          false));

  EXPECT_CALL(
      mock_observer,
      OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES)).Times(0);
  EXPECT_TRUE(observer->allowImage(true, mock_observer.image_url_));
  ::testing::Mock::VerifyAndClearExpectations(&observer);
}

TEST_F(ChromeRenderViewTest, ImagesAllowedByDefault) {
  MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame());

  // Load some HTML.
  LoadHTML("<html>Foo</html>");

  // Set the default image blocking setting.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& image_setting_rules =
      content_setting_rules.image_rules;
  image_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_ALLOW,
                                  std::string(),
                                  false));

  ContentSettingsObserver* observer =
      ContentSettingsObserver::Get(view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);
  EXPECT_CALL(
      mock_observer,
      OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES)).Times(0);
  EXPECT_TRUE(observer->allowImage(true, mock_observer.image_url_));
  ::testing::Mock::VerifyAndClearExpectations(&observer);

  // Create an exception which blocks the image.
  image_setting_rules.insert(
      image_setting_rules.begin(),
      ContentSettingPatternSource(
          ContentSettingsPattern::Wildcard(),
          ContentSettingsPattern::FromString(mock_observer.image_origin_),
          CONTENT_SETTING_BLOCK,
          std::string(),
          false));
  EXPECT_CALL(mock_observer,
              OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES));
  EXPECT_FALSE(observer->allowImage(true, mock_observer.image_url_));
  ::testing::Mock::VerifyAndClearExpectations(&observer);
}

TEST_F(ChromeRenderViewTest, ContentSettingsBlockScripts) {
  // Set the content settings for scripts.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& script_setting_rules =
      content_setting_rules.script_rules;
  script_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_BLOCK,
                                  std::string(),
                                  false));

  ContentSettingsObserver* observer =
      ContentSettingsObserver::Get(view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);

  // Load a page which contains a script.
  std::string html = "<html>"
                     "<head>"
                     "<script src='data:foo'></script>"
                     "</head>"
                     "<body>"
                     "</body>"
                     "</html>";
  LoadHTML(html.c_str());

  // Verify that the script was blocked.
  bool was_blocked = false;
  for (size_t i = 0; i < render_thread_->sink().message_count(); ++i) {
    const IPC::Message* msg = render_thread_->sink().GetMessageAt(i);
    if (msg->type() == ChromeViewHostMsg_ContentBlocked::ID)
      was_blocked = true;
  }
  EXPECT_TRUE(was_blocked);
}

TEST_F(ChromeRenderViewTest, ContentSettingsAllowScripts) {
  // Set the content settings for scripts.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& script_setting_rules =
      content_setting_rules.script_rules;
  script_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_ALLOW,
                                  std::string(),
                                  false));

  ContentSettingsObserver* observer =
      ContentSettingsObserver::Get(view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);

  // Load a page which contains a script.
  std::string html = "<html>"
                     "<head>"
                     "<script src='data:foo'></script>"
                     "</head>"
                     "<body>"
                     "</body>"
                     "</html>";
  LoadHTML(html.c_str());

  // Verify that the script was not blocked.
  bool was_blocked = false;
  for (size_t i = 0; i < render_thread_->sink().message_count(); ++i) {
    const IPC::Message* msg = render_thread_->sink().GetMessageAt(i);
    if (msg->type() == ChromeViewHostMsg_ContentBlocked::ID)
      was_blocked = true;
  }
  EXPECT_FALSE(was_blocked);
}

TEST_F(ChromeRenderViewTest, ContentSettingsInterstitialPages) {
  MockContentSettingsObserver mock_observer(view_->GetMainRenderFrame());
  // Block scripts.
  RendererContentSettingRules content_setting_rules;
  ContentSettingsForOneType& script_setting_rules =
      content_setting_rules.script_rules;
  script_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_BLOCK,
                                  std::string(),
                                  false));
  // Block images.
  ContentSettingsForOneType& image_setting_rules =
      content_setting_rules.image_rules;
  image_setting_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_BLOCK,
                                  std::string(),
                                  false));

  ContentSettingsObserver* observer =
      ContentSettingsObserver::Get(view_->GetMainRenderFrame());
  observer->SetContentSettingRules(&content_setting_rules);
  observer->OnSetAsInterstitial();

  // Load a page which contains a script.
  std::string html = "<html>"
                     "<head>"
                     "<script src='data:foo'></script>"
                     "</head>"
                     "<body>"
                     "</body>"
                     "</html>";
  LoadHTML(html.c_str());

  // Verify that the script was allowed.
  bool was_blocked = false;
  for (size_t i = 0; i < render_thread_->sink().message_count(); ++i) {
    const IPC::Message* msg = render_thread_->sink().GetMessageAt(i);
    if (msg->type() == ChromeViewHostMsg_ContentBlocked::ID)
      was_blocked = true;
  }
  EXPECT_FALSE(was_blocked);

  // Verify that images are allowed.
  EXPECT_CALL(
      mock_observer,
      OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES)).Times(0);
  EXPECT_TRUE(observer->allowImage(true, mock_observer.image_url_));
  ::testing::Mock::VerifyAndClearExpectations(&observer);
}