NetworkService: Make requests with bad referrers fail.

This moves logic for this case from ChromeNetworkDelegate to
NetworkContext's NetworkDelegate, and adds a Mojo parameter to cause
requests with bad referrers to fail (enabled by default).

Also removes the action Net.URLRequest_StartJob_InvalidReferrer rather
than moving it, as it was unowned.

Bug: 852871
Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs;luci.chromium.try:linux_mojo;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: I9d6d55157ba28e2b12ca7136b78330100a8673aa
Reviewed-on: https://chromium-review.googlesource.com/1103119
Reviewed-by: Gayane Petrosyan <[email protected]>
Reviewed-by: David Roger <[email protected]>
Reviewed-by: Tom Sepez <[email protected]>
Reviewed-by: Jochen Eisinger <[email protected]>
Commit-Queue: Matt Menke <[email protected]>
Cr-Commit-Position: refs/heads/master@{#568807}
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index 45a38097..e489529 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -94,24 +94,6 @@
   std::move(callback).Run(rv);
 }
 
-void ReportInvalidReferrerSendOnUI() {
-  base::RecordAction(
-      base::UserMetricsAction("Net.URLRequest_StartJob_InvalidReferrer"));
-}
-
-void ReportInvalidReferrerSend(const GURL& target_url,
-                               const GURL& referrer_url) {
-  LOG(ERROR) << "Cancelling request to " << target_url
-             << " with invalid referrer " << referrer_url;
-  // Record information to help debug http://crbug.com/422871
-  if (!target_url.SchemeIsHTTPOrHTTPS())
-    return;
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                          base::BindOnce(&ReportInvalidReferrerSendOnUI));
-  base::debug::DumpWithoutCrashing();
-  NOTREACHED();
-}
-
 // Record network errors that HTTP requests complete with, including OK and
 // ABORTED.
 void RecordNetworkErrorHistograms(const net::URLRequest* request,
@@ -496,7 +478,9 @@
     const net::URLRequest& request,
     const GURL& target_url,
     const GURL& referrer_url) const {
-  ReportInvalidReferrerSend(target_url, referrer_url);
+  // These errors should be handled by the NetworkDelegate wrapper created by
+  // the owning NetworkContext.
+  NOTREACHED();
   return true;
 }
 
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index 11e3d07..fb7175a 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -56,6 +56,7 @@
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/resource_response_info.h"
@@ -945,6 +946,36 @@
   EXPECT_EQ("None", referrer);
 }
 
+// Make sure that sending referrers that violate the referrer policy results in
+// errors.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+                       PolicyViolatingReferrers) {
+  std::unique_ptr<network::ResourceRequest> request =
+      std::make_unique<network::ResourceRequest>();
+  request->url = embedded_test_server()->GetURL("/echoheader?Referer");
+  request->referrer = GURL("http://referrer/");
+  request->referrer_policy = net::URLRequest::NO_REFERRER;
+  content::SimpleURLLoaderTestHelper simple_loader_helper;
+  std::unique_ptr<network::SimpleURLLoader> simple_loader =
+      network::SimpleURLLoader::Create(std::move(request),
+                                       TRAFFIC_ANNOTATION_FOR_TESTS);
+
+  simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      loader_factory(), simple_loader_helper.GetCallback());
+  simple_loader_helper.WaitForCallback();
+  if (GetParam().network_context_type == NetworkContextType::kSafeBrowsing &&
+      base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    // Safebrowsing ignores referrers, when the network service is enabled, so
+    // the requests succeed.
+    EXPECT_EQ(net::OK, simple_loader->NetError());
+    ASSERT_TRUE(simple_loader_helper.response_body());
+    EXPECT_EQ("None", *simple_loader_helper.response_body());
+  } else {
+    // In all other cases, the invalid referrer causes the request to fail.
+    EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, simple_loader->NetError());
+  }
+}
+
 class NetworkContextConfigurationFixedPortBrowserTest
     : public NetworkContextConfigurationBrowserTest {
  public:
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.cc b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
index 7abc1d3..c0f0fb2f 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.cc
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
@@ -13,7 +13,6 @@
 #include "base/logging.h"
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/metrics/user_metrics.h"
 #include "base/path_service.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
@@ -29,11 +28,6 @@
 
 const char kDNTHeader[] = "DNT";
 
-void ReportInvalidReferrerSendOnUI() {
-  base::RecordAction(
-      base::UserMetricsAction("Net.URLRequest_StartJob_InvalidReferrer"));
-}
-
 void ReportInvalidReferrerSend(const GURL& target_url,
                                const GURL& referrer_url) {
   LOG(ERROR) << "Cancelling request to " << target_url
@@ -41,8 +35,6 @@
   // Record information to help debug http://crbug.com/422871
   if (!target_url.SchemeIsHTTPOrHTTPS())
     return;
-  web::WebThread::PostTask(web::WebThread::UI, FROM_HERE,
-                           base::Bind(&ReportInvalidReferrerSendOnUI));
   base::debug::DumpWithoutCrashing();
   NOTREACHED();
 }
diff --git a/net/base/layered_network_delegate.cc b/net/base/layered_network_delegate.cc
index 754ce8f..0299fcd 100644
--- a/net/base/layered_network_delegate.cc
+++ b/net/base/layered_network_delegate.cc
@@ -231,18 +231,19 @@
         const URLRequest& request,
         const GURL& target_url,
         const GURL& referrer_url) const {
-  OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
-      request, target_url, referrer_url);
-  return nested_network_delegate_
-      ->CancelURLRequestWithPolicyViolatingReferrerHeader(request, target_url,
-                                                          referrer_url);
+  return OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
+             request, target_url, referrer_url) ||
+         nested_network_delegate_
+             ->CancelURLRequestWithPolicyViolatingReferrerHeader(
+                 request, target_url, referrer_url);
 }
 
-void LayeredNetworkDelegate::
+bool LayeredNetworkDelegate::
     OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
         const URLRequest& request,
         const GURL& target_url,
         const GURL& referrer_url) const {
+  return false;
 }
 
 bool LayeredNetworkDelegate::OnCanQueueReportingReport(
diff --git a/net/base/layered_network_delegate.h b/net/base/layered_network_delegate.h
index 0e7d3a039..1623c3d 100644
--- a/net/base/layered_network_delegate.h
+++ b/net/base/layered_network_delegate.h
@@ -163,7 +163,9 @@
 
   virtual void OnAreExperimentalCookieFeaturesEnabledInternal() const;
 
-  virtual void OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
+  // If this returns false, it short circuits the corresponding call in any
+  // nested NetworkDelegates.
+  virtual bool OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
       const URLRequest& request,
       const GURL& target_url,
       const GURL& referrer_url) const;
diff --git a/net/base/layered_network_delegate_unittest.cc b/net/base/layered_network_delegate_unittest.cc
index 2de65e4..87acd0f1 100644
--- a/net/base/layered_network_delegate_unittest.cc
+++ b/net/base/layered_network_delegate_unittest.cc
@@ -322,7 +322,7 @@
     EXPECT_EQ(1, (*counters_)["on_can_enable_privacy_mode_count"]);
   }
 
-  void OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
+  bool OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
       const URLRequest& request,
       const GURL& target_url,
       const GURL& referrer_url) const override {
@@ -332,6 +332,7 @@
     EXPECT_EQ(1, (*counters_)
                      ["on_cancel_url_request_with_policy_"
                       "violating_referrer_header_count"]);
+    return false;
   }
 
   void OnCanQueueReportingReportInternal(
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 9884ead5..9368ef3 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/command_line.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop_current.h"
@@ -222,9 +223,12 @@
  public:
   ContextNetworkDelegate(
       std::unique_ptr<net::NetworkDelegate> nested_network_delegate,
-      bool enable_referrers)
+      bool enable_referrers,
+      bool validate_referrer_policy_on_initial_request)
       : LayeredNetworkDelegate(std::move(nested_network_delegate)),
-        enable_referrers_(enable_referrers) {}
+        enable_referrers_(enable_referrers),
+        validate_referrer_policy_on_initial_request_(
+            validate_referrer_policy_on_initial_request) {}
 
   ~ContextNetworkDelegate() override {}
 
@@ -234,12 +238,31 @@
       request->SetReferrer(std::string());
   }
 
+  bool OnCancelURLRequestWithPolicyViolatingReferrerHeaderInternal(
+      const net::URLRequest& request,
+      const GURL& target_url,
+      const GURL& referrer_url) const override {
+    // TODO(mmenke): Once the network service has shipped on all platforms,
+    // consider moving this logic into URLLoader, and removing this method from
+    // NetworkDelegate. Can just have a DCHECK in URLRequest instead.
+    if (!validate_referrer_policy_on_initial_request_)
+      return false;
+
+    LOG(ERROR) << "Cancelling request to " << target_url
+               << " with invalid referrer " << referrer_url;
+    // Record information to help debug issues like http://crbug.com/422871.
+    if (target_url.SchemeIsHTTPOrHTTPS())
+      base::debug::DumpWithoutCrashing();
+    return true;
+  }
+
   void set_enable_referrers(bool enable_referrers) {
     enable_referrers_ = enable_referrers;
   }
 
  private:
   bool enable_referrers_;
+  bool validate_referrer_policy_on_initial_request_;
 
   DISALLOW_COPY_AND_ASSIGN(ContextNetworkDelegate);
 };
@@ -854,7 +877,9 @@
         std::unique_ptr<ContextNetworkDelegate> context_network_delegate =
             std::make_unique<ContextNetworkDelegate>(
                 std::move(nested_network_delegate),
-                network_context_params->enable_referrers);
+                network_context_params->enable_referrers,
+                network_context_params
+                    ->validate_referrer_policy_on_initial_request);
         if (out_context_network_delegate)
           *out_context_network_delegate = context_network_delegate.get();
         return context_network_delegate;
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 8109cbf..5611116 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -35,6 +35,7 @@
 #include "components/network_session_configurator/browser/network_session_configurator.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "net/base/cache_type.h"
 #include "net/base/hash_value.h"
 #include "net/base/ip_endpoint.h"
@@ -815,6 +816,75 @@
   }
 }
 
+// Test that valid referrers are allowed, while invalid ones result in errors.
+TEST_F(NetworkContextTest, Referrers) {
+  const GURL kReferrer = GURL("http://referrer/");
+  net::test_server::EmbeddedTestServer test_server;
+  test_server.AddDefaultHandlers(
+      base::FilePath(FILE_PATH_LITERAL("services/test/data")));
+  ASSERT_TRUE(test_server.Start());
+
+  for (bool validate_referrer_policy_on_initial_request : {false, true}) {
+    for (net::URLRequest::ReferrerPolicy referrer_policy :
+         {net::URLRequest::NEVER_CLEAR_REFERRER,
+          net::URLRequest::NO_REFERRER}) {
+      mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+      context_params->validate_referrer_policy_on_initial_request =
+          validate_referrer_policy_on_initial_request;
+      std::unique_ptr<NetworkContext> network_context =
+          CreateContextWithParams(std::move(context_params));
+
+      mojom::URLLoaderFactoryPtr loader_factory;
+      mojom::URLLoaderFactoryParamsPtr params =
+          mojom::URLLoaderFactoryParams::New();
+      params->process_id = 0;
+      network_context->CreateURLLoaderFactory(
+          mojo::MakeRequest(&loader_factory), std::move(params));
+
+      ResourceRequest request;
+      request.url = test_server.GetURL("/echoheader?Referer");
+      request.referrer = kReferrer;
+      request.referrer_policy = referrer_policy;
+
+      mojom::URLLoaderPtr loader;
+      TestURLLoaderClient client;
+      loader_factory->CreateLoaderAndStart(
+          mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */,
+          0 /* options */, request, client.CreateInterfacePtr(),
+          net::MutableNetworkTrafficAnnotationTag(
+              TRAFFIC_ANNOTATION_FOR_TESTS));
+
+      client.RunUntilComplete();
+      EXPECT_TRUE(client.has_received_completion());
+
+      // If validating referrers, and the referrer policy is not to send
+      // referrers, the request should fail.
+      if (validate_referrer_policy_on_initial_request &&
+          referrer_policy == net::URLRequest::NO_REFERRER) {
+        EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT,
+                  client.completion_status().error_code);
+        EXPECT_FALSE(client.response_body().is_valid());
+        continue;
+      }
+
+      // Otherwise, the request should succeed.
+      EXPECT_EQ(net::OK, client.completion_status().error_code);
+      std::string response_body;
+      ASSERT_TRUE(client.response_body().is_valid());
+      EXPECT_TRUE(mojo::BlockingCopyToString(client.response_body_release(),
+                                             &response_body));
+      if (referrer_policy == net::URLRequest::NO_REFERRER) {
+        // If not validating referrers, and the referrer policy is not to send
+        // referrers, the referrer should be cleared.
+        EXPECT_EQ("None", response_body);
+      } else {
+        // Otherwise, the referrer should be send.
+        EXPECT_EQ(kReferrer.spec(), response_body);
+      }
+    }
+  }
+}
+
 // Validates that clearing the HTTP cache when no cache exists does complete.
 TEST_F(NetworkContextTest, ClearHttpCacheWithNoCache) {
   mojom::NetworkContextParamsPtr context_params = CreateContextParams();
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index b44618b..ef2c01d 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -50,6 +50,10 @@
   // If false, the referrer of requests is never populated.
   bool enable_referrers = true;
 
+  // If true, requests initiated with referrers that don't match their referrer
+  // policy will fail.
+  bool validate_referrer_policy_on_initial_request = true;
+
   // Handles PAC script execution. If not populated, will attempt to use
   // platform implementation to execute PAC scripts, if available (Only
   // available on Windows and Mac).
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index c7ffbdc..5ecd026 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -12988,6 +12988,7 @@
 </action>
 
 <action name="Net.URLRequest_StartJob_InvalidReferrer">
+  <obsolete>Deprecated 6/2018.</obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
 </action>