blob: 0c29683cfcf23ea5ba7870a5bbbeadf2290aa241 [file] [log] [blame]
// 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/renderer/chrome_content_renderer_client.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/metrics/histogram_samples.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/renderer/searchbox/search_bouncer.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/webplugininfo.h"
#include "extensions/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_constants.h"
#endif
#if BUILDFLAG(ENABLE_NACL)
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_vector.h"
#include "third_party/blink/public/web/web_plugin_params.h"
#endif
#if BUILDFLAG(ENABLE_NACL)
using blink::WebPluginParams;
using blink::WebString;
using blink::WebVector;
#endif
using content::WebPluginInfo;
using content::WebPluginMimeType;
namespace {
#if BUILDFLAG(ENABLE_NACL)
const bool kNaClRestricted = false;
const bool kNaClUnrestricted = true;
const bool kExtensionNotFromWebStore = false;
const bool kExtensionFromWebStore = true;
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
const bool kNotHostedApp = false;
const bool kHostedApp = true;
#endif
#if BUILDFLAG(ENABLE_NACL)
const char kExtensionUrl[] = "chrome-extension://extension_id/background.html";
const char kChatManifestFS[] = "filesystem:https://talkgadget.google.com/foo";
const char kChatAppURL[] = "https://talkgadget.google.com/hangouts/foo";
#endif
void AddContentTypeHandler(content::WebPluginInfo* info,
const char* mime_type,
const char* manifest_url) {
content::WebPluginMimeType mime_type_info;
mime_type_info.mime_type = mime_type;
mime_type_info.additional_params.emplace_back(
base::UTF8ToUTF16("nacl"), base::UTF8ToUTF16(manifest_url));
info->mime_types.push_back(mime_type_info);
}
} // namespace
class ChromeContentRendererClientTest : public testing::Test {
public:
void SetUp() override {
// Ensure that this looks like the renderer process based on the command
// line.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kProcessType, switches::kRendererProcess);
}
};
#if BUILDFLAG(ENABLE_EXTENSIONS)
scoped_refptr<const extensions::Extension> CreateTestExtension(
extensions::Manifest::Location location, bool is_from_webstore,
bool is_hosted_app, const std::string& app_url) {
int flags = is_from_webstore ?
extensions::Extension::FROM_WEBSTORE:
extensions::Extension::NO_FLAGS;
base::DictionaryValue manifest;
manifest.SetString("name", "NaCl Extension");
manifest.SetString("version", "1");
manifest.SetInteger("manifest_version", 2);
if (is_hosted_app) {
auto url_list = std::make_unique<base::ListValue>();
url_list->AppendString(app_url);
manifest.Set(extensions::manifest_keys::kWebURLs, std::move(url_list));
manifest.SetString(extensions::manifest_keys::kLaunchWebURL, app_url);
}
std::string error;
return extensions::Extension::Create(base::FilePath(), location, manifest,
flags, &error);
}
scoped_refptr<const extensions::Extension> CreateExtension(
bool is_from_webstore) {
return CreateTestExtension(
extensions::Manifest::INTERNAL, is_from_webstore, kNotHostedApp,
std::string());
}
scoped_refptr<const extensions::Extension> CreateExtensionWithLocation(
extensions::Manifest::Location location, bool is_from_webstore) {
return CreateTestExtension(
location, is_from_webstore, kNotHostedApp, std::string());
}
scoped_refptr<const extensions::Extension> CreateHostedApp(
bool is_from_webstore, const std::string& app_url) {
return CreateTestExtension(extensions::Manifest::INTERNAL,
is_from_webstore,
kHostedApp,
app_url);
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(ChromeContentRendererClientTest, NaClRestriction) {
// Unknown content types have no NaCl module.
{
WebPluginInfo info;
EXPECT_EQ(GURL(),
ChromeContentRendererClient::GetNaClContentHandlerURL(
"application/x-foo", info));
}
// Known content types have a NaCl module.
{
WebPluginInfo info;
AddContentTypeHandler(&info, "application/x-foo", "www.foo.com");
EXPECT_EQ(GURL("www.foo.com"),
ChromeContentRendererClient::GetNaClContentHandlerURL(
"application/x-foo", info));
}
#if BUILDFLAG(ENABLE_NACL)
// --enable-nacl allows all NaCl apps.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(),
kNaClUnrestricted,
CreateExtension(kExtensionNotFromWebStore).get(),
&params));
}
// Unpacked extensions are allowed without --enable-nacl.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtensionWithLocation(extensions::Manifest::UNPACKED,
kExtensionNotFromWebStore).get(),
&params));
}
// Component extensions are allowed without --enable-nacl.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtensionWithLocation(extensions::Manifest::COMPONENT,
kExtensionNotFromWebStore).get(),
&params));
}
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtensionWithLocation(extensions::Manifest::EXTERNAL_COMPONENT,
kExtensionNotFromWebStore).get(),
&params));
}
// Extensions that are force installed by policy are allowed without
// --enable-nacl.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtensionWithLocation(extensions::Manifest::EXTERNAL_POLICY,
kExtensionNotFromWebStore).get(),
&params));
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtensionWithLocation(
extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD,
kExtensionNotFromWebStore).get(),
&params));
}
// CWS extensions are allowed without --enable-nacl if called from an
// extension url.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL(kExtensionUrl),
kNaClRestricted,
CreateExtension(kExtensionFromWebStore).get(),
&params));
}
// Whitelisted URLs are allowed without --enable-nacl. There is a whitelist
// for the app URL and the manifest URL.
{
WebPluginParams params;
// Whitelisted Chat app is allowed.
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(kChatManifestFS),
GURL(kChatAppURL),
kNaClRestricted,
nullptr,
&params));
// Whitelisted manifest URL, bad app URLs, NOT allowed.
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(kChatManifestFS),
GURL("http://plus.google.com/foo"), // http scheme
kNaClRestricted, nullptr, &params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(kChatManifestFS),
GURL("http://plus.sandbox.google.com/foo"), // http scheme
kNaClRestricted, nullptr, &params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(kChatManifestFS),
GURL("https://plus.google.evil.com/foo"), // bad host
kNaClRestricted, nullptr, &params));
// Whitelisted app URL, bad manifest URL, NOT allowed.
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL("http://ssl.gstatic.com/s2/oz/nacl/foo"), // http scheme
GURL(kChatAppURL), kNaClRestricted, nullptr, &params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL("https://ssl.gstatic.evil.com/s2/oz/nacl/foo"), // bad host
GURL(kChatAppURL), kNaClRestricted, nullptr, &params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL("https://ssl.gstatic.com/wrong/s2/oz/nacl/foo"), // bad path
GURL(kChatAppURL), kNaClRestricted, nullptr, &params));
}
// Non-whitelisted URLs are blocked without --enable-nacl.
{
WebPluginParams params;
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL("https://plus.google.com.evil.com/foo1"),
kNaClRestricted,
nullptr,
&params));
}
// Non chrome-extension:// URLs belonging to hosted apps are allowed for
// webstore installed hosted apps.
{
WebPluginParams params;
EXPECT_TRUE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL("http://example.com/test.html"),
kNaClRestricted,
CreateHostedApp(kExtensionFromWebStore,
"http://example.com/").get(),
&params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL("http://example.com/test.html"),
kNaClRestricted,
CreateHostedApp(kExtensionNotFromWebStore,
"http://example.com/").get(),
&params));
EXPECT_FALSE(ChromeContentRendererClient::IsNaClAllowed(
GURL(),
GURL("http://example.evil.com/test.html"),
kNaClRestricted,
CreateHostedApp(kExtensionNotFromWebStore,
"http://example.com/").get(),
&params));
}
#endif // BUILDFLAG(ENABLE_NACL)
}
// SearchBouncer doesn't exist on Android.
#if !defined(OS_ANDROID)
TEST_F(ChromeContentRendererClientTest, ShouldSuppressErrorPage) {
ChromeContentRendererClient client;
SearchBouncer::GetInstance()->SetNewTabPageURL(GURL("http://example.com/n"));
EXPECT_FALSE(client.ShouldSuppressErrorPage(nullptr,
GURL("http://example.com")));
EXPECT_TRUE(client.ShouldSuppressErrorPage(nullptr,
GURL("http://example.com/n")));
SearchBouncer::GetInstance()->SetNewTabPageURL(GURL::EmptyGURL());
}
TEST_F(ChromeContentRendererClientTest, ShouldTrackUseCounter) {
ChromeContentRendererClient client;
SearchBouncer::GetInstance()->SetNewTabPageURL(GURL("http://example.com/n"));
EXPECT_TRUE(client.ShouldTrackUseCounter(GURL("http://example.com")));
EXPECT_FALSE(client.ShouldTrackUseCounter(GURL("http://example.com/n")));
SearchBouncer::GetInstance()->SetNewTabPageURL(GURL::EmptyGURL());
}
#endif
TEST_F(ChromeContentRendererClientTest, AddImageContextMenuPropertiesForLoFi) {
ChromeContentRendererClient client;
blink::WebURLResponse web_url_response;
web_url_response.AddHTTPHeaderField(
blink::WebString::FromUTF8(
data_reduction_proxy::chrome_proxy_content_transform_header()),
blink::WebString::FromUTF8(
data_reduction_proxy::empty_image_directive()));
std::map<std::string, std::string> properties;
client.AddImageContextMenuProperties(
web_url_response, /*is_image_in_context_a_placeholder_image=*/false,
&properties);
EXPECT_EQ(
data_reduction_proxy::empty_image_directive(),
properties
[data_reduction_proxy::chrome_proxy_content_transform_header()]);
}
TEST_F(ChromeContentRendererClientTest,
AddImageContextMenuPropertiesForPlaceholder) {
ChromeContentRendererClient client;
std::map<std::string, std::string> properties;
client.AddImageContextMenuProperties(
blink::WebURLResponse(), /*is_image_in_context_a_placeholder_image=*/true,
&properties);
EXPECT_EQ(
data_reduction_proxy::empty_image_directive(),
properties
[data_reduction_proxy::chrome_proxy_content_transform_header()]);
}
TEST_F(ChromeContentRendererClientTest, RewriteEmbed) {
struct TestData {
std::string original;
std::string expected;
} test_data[] = {
// { original, expected }
{"youtube.com", ""},
{"www.youtube.com", ""},
{"http://www.youtube.com", ""},
{"https://www.youtube.com", ""},
{"http://www.foo.youtube.com", ""},
{"https://www.foo.youtube.com", ""},
// Non-YouTube domains shouldn't be modified
{"http://www.plus.google.com", ""},
// URL isn't using Flash
{"http://www.youtube.com/embed/deadbeef", ""},
// URL isn't using Flash, no www
{"http://youtube.com/embed/deadbeef", ""},
// URL isn't using Flash, invalid parameter construct
{"http://www.youtube.com/embed/deadbeef&start=4", ""},
// URL is using Flash, no www
{"http://youtube.com/v/deadbeef", "http://youtube.com/embed/deadbeef"},
// URL is using Flash, is valid, https
{"https://www.youtube.com/v/deadbeef",
"https://www.youtube.com/embed/deadbeef"},
// URL is using Flash, is valid, http
{"http://www.youtube.com/v/deadbeef",
"http://www.youtube.com/embed/deadbeef"},
// URL is using Flash, is valid, not a complete URL, no www or protocol
{"youtube.com/v/deadbeef", ""},
// URL is using Flash, is valid, not a complete URL,or protocol
{"www.youtube.com/v/deadbeef", ""},
// URL is using Flash, valid
{"https://www.foo.youtube.com/v/deadbeef",
"https://www.foo.youtube.com/embed/deadbeef"},
// URL is using Flash, is valid, has one parameter
{"http://www.youtube.com/v/deadbeef?start=4",
"http://www.youtube.com/embed/deadbeef?start=4"},
// URL is using Flash, is valid, has multiple parameters
{"http://www.youtube.com/v/deadbeef?start=4&fs=1",
"http://www.youtube.com/embed/deadbeef?start=4&fs=1"},
// URL is using Flash, invalid parameter construct, has one parameter
{"http://www.youtube.com/v/deadbeef&start=4",
"http://www.youtube.com/embed/deadbeef?start=4"},
// URL is using Flash, invalid parameter construct, has multiple
// parameters
{"http://www.youtube.com/v/deadbeef&start=4&fs=1?foo=bar",
"http://www.youtube.com/embed/deadbeef?start=4&fs=1&foo=bar"},
// URL is using Flash, invalid parameter construct, has multiple
// parameters
{"http://www.youtube.com/v/deadbeef&start=4&fs=1",
"http://www.youtube.com/embed/deadbeef?start=4&fs=1"},
// Invalid parameter construct
{"http://www.youtube.com/abcd/v/deadbeef", ""},
// Invalid parameter construct
{"http://www.youtube.com/v/abcd/", "http://www.youtube.com/embed/abcd/"},
// Invalid parameter construct
{"http://www.youtube.com/v/123/", "http://www.youtube.com/embed/123/"},
// youtube-nocookie.com
{"http://www.youtube-nocookie.com/v/123/",
"http://www.youtube-nocookie.com/embed/123/"},
// youtube-nocookie.com, isn't using flash
{"http://www.youtube-nocookie.com/embed/123/", ""},
// youtube-nocookie.com, has one parameter
{"http://www.youtube-nocookie.com/v/123?start=foo",
"http://www.youtube-nocookie.com/embed/123?start=foo"},
// youtube-nocookie.com, has multiple parameters
{"http://www.youtube-nocookie.com/v/123?start=foo&bar=baz",
"http://www.youtube-nocookie.com/embed/123?start=foo&bar=baz"},
// youtube-nocookie.com, invalid parameter construct, has one parameter
{"http://www.youtube-nocookie.com/v/123&start=foo",
"http://www.youtube-nocookie.com/embed/123?start=foo"},
// youtube-nocookie.com, invalid parameter construct, has multiple
// parameters
{"http://www.youtube-nocookie.com/v/123&start=foo&bar=baz",
"http://www.youtube-nocookie.com/embed/123?start=foo&bar=baz"},
// youtube-nocookie.com, https
{"https://www.youtube-nocookie.com/v/123/",
"https://www.youtube-nocookie.com/embed/123/"},
// URL isn't using Flash, has JS API enabled
{"http://www.youtube.com/embed/deadbeef?enablejsapi=1", ""},
// URL is using Flash, has JS API enabled
{"http://www.youtube.com/v/deadbeef?enablejsapi=1",
"http://www.youtube.com/embed/deadbeef?enablejsapi=1"},
// youtube-nocookie.com, has JS API enabled
{"http://www.youtube-nocookie.com/v/123?enablejsapi=1",
"http://www.youtube-nocookie.com/embed/123?enablejsapi=1"},
// URL is using Flash, has JS API enabled, invalid parameter construct
{"http://www.youtube.com/v/deadbeef&enablejsapi=1",
"http://www.youtube.com/embed/deadbeef?enablejsapi=1"},
// URL is using Flash, has JS API enabled, invalid parameter construct,
// has multiple parameters
{"http://www.youtube.com/v/deadbeef&start=4&enablejsapi=1",
"http://www.youtube.com/embed/deadbeef?start=4&enablejsapi=1"},
};
ChromeContentRendererClient client;
for (const auto& data : test_data)
EXPECT_EQ(GURL(data.expected),
client.OverrideFlashEmbedWithHTML(GURL(data.original)));
}
class ChromeContentRendererClientMetricsTest : public testing::Test {
public:
ChromeContentRendererClientMetricsTest() = default;
void SetUp() override {
// Ensure that this looks like the renderer process based on the command
// line.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kProcessType, switches::kRendererProcess);
client_ = std::make_unique<ChromeContentRendererClient>();
}
std::unique_ptr<base::HistogramSamples> GetHistogramSamples() {
return histogram_tester_.GetHistogramSamplesSinceCreation(
internal::kFlashYouTubeRewriteUMA);
}
void OverrideFlashEmbed(const GURL& gurl) {
client_->OverrideFlashEmbedWithHTML(gurl);
}
private:
std::unique_ptr<ChromeContentRendererClient> client_;
base::HistogramTester histogram_tester_;
DISALLOW_COPY_AND_ASSIGN(ChromeContentRendererClientMetricsTest);
};
TEST_F(ChromeContentRendererClientMetricsTest, RewriteEmbedIneligibleURL) {
std::unique_ptr<base::HistogramSamples> samples = GetHistogramSamples();
EXPECT_EQ(0, samples->TotalCount());
const std::string test_data[] = {
// HTTP, www, no flash
"http://www.youtube.com",
// No flash, subdomain
"http://www.foo.youtube.com",
// No flash
"youtube.com",
// Not youtube
"http://www.plus.google.com",
// Already using HTML5
"http://youtube.com/embed/deadbeef",
// Already using HTML5, enablejsapi=1
"http://www.youtube.com/embed/deadbeef?enablejsapi=1"};
for (const auto& data : test_data) {
GURL gurl = GURL(data);
OverrideFlashEmbed(gurl);
samples = GetHistogramSamples();
EXPECT_EQ(0, samples->GetCount(internal::SUCCESS));
EXPECT_EQ(0, samples->TotalCount());
}
}
TEST_F(ChromeContentRendererClientMetricsTest, RewriteEmbedSuccess) {
ChromeContentRendererClient client;
std::unique_ptr<base::HistogramSamples> samples = GetHistogramSamples();
auto total_count = 0;
EXPECT_EQ(total_count, samples->TotalCount());
const std::string test_data[] = {
// HTTP, www, flash
"http://www.youtube.com/v/deadbeef",
// HTTP, no www, flash
"http://youtube.com/v/deadbeef",
// HTTPS, www, flash
"https://www.youtube.com/v/deadbeef",
// HTTPS, no www, flash
"https://youtube.com/v/deadbeef",
// Invalid parameter construct
"http://www.youtube.com/v/abcd/",
// Invalid parameter construct
"http://www.youtube.com/v/1234/",
};
for (const auto& data : test_data) {
++total_count;
GURL gurl = GURL(data);
OverrideFlashEmbed(gurl);
samples = GetHistogramSamples();
EXPECT_EQ(total_count, samples->GetCount(internal::SUCCESS));
EXPECT_EQ(total_count, samples->TotalCount());
}
// Invalid parameter construct
GURL gurl = GURL("http://www.youtube.com/abcd/v/deadbeef");
samples = GetHistogramSamples();
EXPECT_EQ(total_count, samples->GetCount(internal::SUCCESS));
EXPECT_EQ(total_count, samples->TotalCount());
}
// Crashes on Mac/Win only. http://crbug.com/879644
#if defined(OS_WIN) || defined(OS_MACOSX)
#define MAYBE_RewriteEmbedSuccessRewrite DISABLED_RewriteEmbedSuccessRewrite
#else
#define MAYBE_RewriteEmbedSuccessRewrite RewriteEmbedSuccessRewrite
#endif
TEST_F(ChromeContentRendererClientMetricsTest, MAYBE_RewriteEmbedSuccessRewrite) {
ChromeContentRendererClient client;
std::unique_ptr<base::HistogramSamples> samples = GetHistogramSamples();
auto total_count = 0;
EXPECT_EQ(total_count, samples->TotalCount());
const std::string test_data[] = {
// Invalid parameter construct, one parameter
"http://www.youtube.com/v/deadbeef&start=4",
// Invalid parameter construct, has multiple parameters
"http://www.youtube.com/v/deadbeef&start=4&fs=1?foo=bar",
};
for (const auto& data : test_data) {
++total_count;
GURL gurl = GURL(data);
client.OverrideFlashEmbedWithHTML(gurl);
samples = GetHistogramSamples();
EXPECT_EQ(total_count, samples->GetCount(internal::SUCCESS_PARAMS_REWRITE));
EXPECT_EQ(total_count, samples->TotalCount());
}
// Invalid parameter construct, not flash
GURL gurl = GURL("http://www.youtube.com/embed/deadbeef&start=4");
OverrideFlashEmbed(gurl);
samples = GetHistogramSamples();
EXPECT_EQ(total_count, samples->GetCount(internal::SUCCESS_PARAMS_REWRITE));
EXPECT_EQ(total_count, samples->TotalCount());
}
TEST_F(ChromeContentRendererClientMetricsTest, RewriteEmbedJSAPI) {
ChromeContentRendererClient client;
std::unique_ptr<base::HistogramSamples> samples = GetHistogramSamples();
auto total_count = 0;
EXPECT_EQ(total_count, samples->TotalCount());
const std::string test_data[] = {
// Valid parameter construct, one parameter
"http://www.youtube.com/v/deadbeef?enablejsapi=1",
// Valid parameter construct, has multiple parameters
"http://www.youtube.com/v/deadbeef?enablejsapi=1&foo=2",
// Invalid parameter construct, one parameter
"http://www.youtube.com/v/deadbeef&enablejsapi=1",
// Invalid parameter construct, has multiple parameters
"http://www.youtube.com/v/deadbeef&enablejsapi=1&foo=2"};
for (const auto& data : test_data) {
++total_count;
GURL gurl = GURL(data);
OverrideFlashEmbed(gurl);
samples = GetHistogramSamples();
EXPECT_EQ(total_count, samples->GetCount(internal::SUCCESS_ENABLEJSAPI));
EXPECT_EQ(total_count, samples->TotalCount());
}
}