Fix bug where we'd allow chrome-extension URLs to be loaded in incognito mode
as subresources, even though the extension is not allowed in incognito.

BUG=118721
TEST=see bug for repro; try the proof of concept in incognito mode with adblock installed but not allowed in incognito. adblock should not be detected.


Review URL: http://codereview.chromium.org/9726025

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128097 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_protocols.cc b/chrome/browser/extensions/extension_protocols.cc
index 55f435470..4b55153 100644
--- a/chrome/browser/extensions/extension_protocols.cc
+++ b/chrome/browser/extensions/extension_protocols.cc
@@ -165,12 +165,22 @@
   net::HttpResponseInfo response_info_;
 };
 
-bool ExtensionCanLoadInIncognito(const std::string& extension_id,
+bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
+                                 const std::string& extension_id,
                                  ExtensionInfoMap* extension_info_map) {
-  const Extension* extension =
-      extension_info_map->extensions().GetByID(extension_id);
-  // Only split-mode extensions can load in incognito profiles.
-  return extension && extension->incognito_split_mode();
+  if (!extension_info_map->IsIncognitoEnabled(extension_id))
+    return false;
+
+  // Only allow incognito toplevel navigations to extension resources in
+  // split mode. In spanning mode, the extension must run in a single process,
+  // and an incognito tab prevents that.
+  if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
+    const Extension* extension =
+        extension_info_map->extensions().GetByID(extension_id);
+    return extension && extension->incognito_split_mode();
+  }
+
+  return true;
 }
 
 // Returns true if an chrome-extension:// resource should be allowed to load.
@@ -189,14 +199,8 @@
     return true;
   }
 
-  // Don't allow toplevel navigations to extension resources in incognito mode.
-  // This is because an extension must run in a single process, and an
-  // incognito tab prevents that.
-  if (is_incognito &&
-      info->GetResourceType() == ResourceType::MAIN_FRAME &&
-      !ExtensionCanLoadInIncognito(request->url().host(), extension_info_map)) {
-    LOG(ERROR) << "Denying load of " << request->url().spec() << " from "
-               << "incognito tab.";
+  if (is_incognito && !ExtensionCanLoadInIncognito(info, request->url().host(),
+                                                   extension_info_map)) {
     return false;
   }
 
@@ -242,7 +246,6 @@
   // TODO(mpcomplete): better error code.
   if (!AllowExtensionResourceLoad(
            request, is_incognito_, extension_info_map_)) {
-    LOG(ERROR) << "disallowed in extension protocols";
     return new net::URLRequestErrorJob(request, net::ERR_ADDRESS_UNREACHABLE);
   }
 
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
new file mode 100644
index 0000000..2437db4
--- /dev/null
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -0,0 +1,153 @@
+// 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 <string>
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "chrome/browser/extensions/extension_info_map.h"
+#include "chrome/browser/extensions/extension_protocols.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/test/mock_resource_context.h"
+#include "content/test/test_browser_thread.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+namespace {
+
+scoped_refptr<Extension> CreateTestExtension(const std::string& name,
+                                             bool incognito_split_mode) {
+  DictionaryValue manifest;
+  manifest.SetString("name", name);
+  manifest.SetString("version", "1");
+  manifest.SetString("incognito", incognito_split_mode ? "split" : "spanning");
+
+  FilePath path;
+  EXPECT_TRUE(file_util::GetCurrentDirectory(&path));
+
+  std::string error;
+  scoped_refptr<Extension> extension(
+      Extension::Create(path, Extension::INTERNAL, manifest,
+                        Extension::STRICT_ERROR_CHECKS, &error));
+  EXPECT_TRUE(extension.get()) << error;
+  return extension;
+}
+
+class ExtensionProtocolTest : public testing::Test {
+ public:
+  ExtensionProtocolTest()
+      : ui_thread_(BrowserThread::UI, &message_loop_),
+        file_thread_(BrowserThread::FILE, &message_loop_),
+        io_thread_(BrowserThread::IO, &message_loop_) {}
+
+  virtual void SetUp() {
+    extension_info_map_ = new ExtensionInfoMap();
+    net::URLRequestContext* request_context =
+        resource_context_.GetRequestContext();
+    old_factory_ = request_context->job_factory();
+    // Register an incognito extension protocol handler.
+    job_factory_.SetProtocolHandler(
+        chrome::kExtensionScheme,
+        CreateExtensionProtocolHandler(true, extension_info_map_));
+    request_context->set_job_factory(&job_factory_);
+  }
+
+  virtual void TearDown() {
+    net::URLRequestContext* request_context =
+        resource_context_.GetRequestContext();
+    request_context->set_job_factory(old_factory_);
+  }
+
+  void StartRequest(net::URLRequest* request,
+                    ResourceType::Type resource_type) {
+    content::ResourceRequestInfo::AllocateForTesting(request,
+                                                     resource_type,
+                                                     &resource_context_);
+    request->set_context(resource_context_.GetRequestContext());
+    request->Start();
+    MessageLoop::current()->Run();
+  }
+
+ protected:
+  MessageLoopForIO message_loop_;
+  content::TestBrowserThread ui_thread_;
+  content::TestBrowserThread file_thread_;
+  content::TestBrowserThread io_thread_;
+  scoped_refptr<ExtensionInfoMap> extension_info_map_;
+  net::URLRequestJobFactory job_factory_;
+  const net::URLRequestJobFactory* old_factory_;
+  TestDelegate test_delegate_;
+  content::MockResourceContext resource_context_;
+};
+
+// Tests that making a chrome-extension request in an incognito context is
+// only allowed under the right circumstances (if the extension is allowed
+// in incognito, and it's either a non-main-frame request or a split-mode
+// extension).
+TEST_F(ExtensionProtocolTest, IncognitoRequest) {
+  struct TestCase {
+    // Inputs.
+    std::string name;
+    bool incognito_split_mode;
+    bool incognito_enabled;
+
+    // Expected results.
+    bool should_allow_main_frame_load;
+    bool should_allow_sub_frame_load;
+  } cases[] = {
+    {"spanning disabled", false, false, false, false},
+    {"split disabled", true, false, false, false},
+    {"spanning enabled", false, true, false, true},
+    {"split enabled", true, true, true, true},
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+    scoped_refptr<Extension> extension =
+        CreateTestExtension(cases[i].name, cases[i].incognito_split_mode);
+    extension_info_map_->AddExtension(
+        extension, base::Time::Now(), cases[i].incognito_enabled);
+
+    // First test a main frame request.
+    {
+      // It doesn't matter that the resource doesn't exist. If the resource
+      // is blocked, we should see ADDRESS_UNREACHABLE. Otherwise, the request
+      // should just fail because the file doesn't exist.
+      net::URLRequest request(extension->GetResourceURL("404.html"),
+                              &test_delegate_);
+      StartRequest(&request, ResourceType::MAIN_FRAME);
+      EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
+
+      if (cases[i].should_allow_main_frame_load) {
+        EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
+            cases[i].name;
+      } else {
+        EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
+            cases[i].name;
+      }
+    }
+
+    // Now do a subframe request.
+    {
+      net::URLRequest request(extension->GetResourceURL("404.html"),
+                              &test_delegate_);
+      StartRequest(&request, ResourceType::SUB_FRAME);
+      EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
+
+      if (cases[i].should_allow_sub_frame_load) {
+        EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
+            cases[i].name;
+      } else {
+        EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
+            cases[i].name;
+      }
+    }
+  }
+}
+
+}  // namespace