Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <string>
      6 
      7 #include "base/file_util.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/values.h"
     11 #include "chrome/browser/extensions/extension_info_map.h"
     12 #include "chrome/browser/extensions/extension_protocols.h"
     13 #include "chrome/common/chrome_paths.h"
     14 #include "chrome/common/extensions/extension.h"
     15 #include "chrome/common/extensions/extension_manifest_constants.h"
     16 #include "chrome/common/url_constants.h"
     17 #include "content/public/browser/resource_request_info.h"
     18 #include "content/public/test/mock_resource_context.h"
     19 #include "content/public/test/test_browser_thread_bundle.h"
     20 #include "extensions/common/constants.h"
     21 #include "net/url_request/url_request.h"
     22 #include "net/url_request/url_request_job_factory_impl.h"
     23 #include "net/url_request/url_request_status.h"
     24 #include "net/url_request/url_request_test_util.h"
     25 #include "testing/gtest/include/gtest/gtest.h"
     26 
     27 namespace extensions {
     28 
     29 scoped_refptr<Extension> CreateTestExtension(const std::string& name,
     30                                              bool incognito_split_mode) {
     31   DictionaryValue manifest;
     32   manifest.SetString("name", name);
     33   manifest.SetString("version", "1");
     34   manifest.SetString("incognito", incognito_split_mode ? "split" : "spanning");
     35 
     36   base::FilePath path;
     37   EXPECT_TRUE(file_util::GetCurrentDirectory(&path));
     38 
     39   std::string error;
     40   scoped_refptr<Extension> extension(
     41       Extension::Create(path, Manifest::INTERNAL, manifest,
     42                         Extension::NO_FLAGS, &error));
     43   EXPECT_TRUE(extension.get()) << error;
     44   return extension;
     45 }
     46 
     47 scoped_refptr<Extension> CreateWebStoreExtension() {
     48   DictionaryValue manifest;
     49   manifest.SetString("name", "WebStore");
     50   manifest.SetString("version", "1");
     51   manifest.SetString("icons.16", "webstore_icon_16.png");
     52 
     53   base::FilePath path;
     54   EXPECT_TRUE(PathService::Get(chrome::DIR_RESOURCES, &path));
     55   path = path.AppendASCII("web_store");
     56 
     57   std::string error;
     58   scoped_refptr<Extension> extension(
     59       Extension::Create(path, Manifest::COMPONENT, manifest,
     60                         Extension::NO_FLAGS, &error));
     61   EXPECT_TRUE(extension.get()) << error;
     62   return extension;
     63 }
     64 
     65 scoped_refptr<Extension> CreateTestResponseHeaderExtension() {
     66   DictionaryValue manifest;
     67   manifest.SetString("name", "An extension with web-accessible resources");
     68   manifest.SetString("version", "2");
     69 
     70   base::ListValue* web_accessible_list = new base::ListValue();
     71   web_accessible_list->AppendString("test.dat");
     72   manifest.Set("web_accessible_resources", web_accessible_list);
     73 
     74   base::FilePath path;
     75   EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
     76   path = path.AppendASCII("extensions").AppendASCII("response_headers");
     77 
     78   std::string error;
     79   scoped_refptr<Extension> extension(
     80       Extension::Create(path, Manifest::UNPACKED, manifest,
     81                         Extension::NO_FLAGS, &error));
     82   EXPECT_TRUE(extension.get()) << error;
     83   return extension;
     84 }
     85 
     86 class ExtensionProtocolTest : public testing::Test {
     87  public:
     88   ExtensionProtocolTest()
     89     : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
     90       resource_context_(&test_url_request_context_) {}
     91 
     92   virtual void SetUp() OVERRIDE {
     93     testing::Test::SetUp();
     94     extension_info_map_ = new ExtensionInfoMap();
     95     net::URLRequestContext* request_context =
     96         resource_context_.GetRequestContext();
     97     old_factory_ = request_context->job_factory();
     98   }
     99 
    100   virtual void TearDown() {
    101     net::URLRequestContext* request_context =
    102         resource_context_.GetRequestContext();
    103     request_context->set_job_factory(old_factory_);
    104   }
    105 
    106   void SetProtocolHandler(bool incognito) {
    107     net::URLRequestContext* request_context =
    108         resource_context_.GetRequestContext();
    109     job_factory_.SetProtocolHandler(
    110         kExtensionScheme,
    111         CreateExtensionProtocolHandler(incognito, extension_info_map_.get()));
    112     request_context->set_job_factory(&job_factory_);
    113   }
    114 
    115   void StartRequest(net::URLRequest* request,
    116                     ResourceType::Type resource_type) {
    117     content::ResourceRequestInfo::AllocateForTesting(request,
    118                                                      resource_type,
    119                                                      &resource_context_,
    120                                                      -1,
    121                                                      -1);
    122     request->Start();
    123     base::MessageLoop::current()->Run();
    124   }
    125 
    126  protected:
    127   content::TestBrowserThreadBundle thread_bundle_;
    128   scoped_refptr<ExtensionInfoMap> extension_info_map_;
    129   net::URLRequestJobFactoryImpl job_factory_;
    130   const net::URLRequestJobFactory* old_factory_;
    131   net::TestDelegate test_delegate_;
    132   net::TestURLRequestContext test_url_request_context_;
    133   content::MockResourceContext resource_context_;
    134 };
    135 
    136 // Tests that making a chrome-extension request in an incognito context is
    137 // only allowed under the right circumstances (if the extension is allowed
    138 // in incognito, and it's either a non-main-frame request or a split-mode
    139 // extension).
    140 TEST_F(ExtensionProtocolTest, IncognitoRequest) {
    141   // Register an incognito extension protocol handler.
    142   SetProtocolHandler(true);
    143 
    144   struct TestCase {
    145     // Inputs.
    146     std::string name;
    147     bool incognito_split_mode;
    148     bool incognito_enabled;
    149 
    150     // Expected results.
    151     bool should_allow_main_frame_load;
    152     bool should_allow_sub_frame_load;
    153   } cases[] = {
    154     {"spanning disabled", false, false, false, false},
    155     {"split disabled", true, false, false, false},
    156     {"spanning enabled", false, true, false, true},
    157     {"split enabled", true, true, true, true},
    158   };
    159 
    160   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
    161     scoped_refptr<Extension> extension =
    162         CreateTestExtension(cases[i].name, cases[i].incognito_split_mode);
    163     extension_info_map_->AddExtension(
    164         extension.get(), base::Time::Now(), cases[i].incognito_enabled);
    165 
    166     // First test a main frame request.
    167     {
    168       // It doesn't matter that the resource doesn't exist. If the resource
    169       // is blocked, we should see ADDRESS_UNREACHABLE. Otherwise, the request
    170       // should just fail because the file doesn't exist.
    171       net::URLRequest request(extension->GetResourceURL("404.html"),
    172                               &test_delegate_,
    173                               resource_context_.GetRequestContext());
    174       StartRequest(&request, ResourceType::MAIN_FRAME);
    175       EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
    176 
    177       if (cases[i].should_allow_main_frame_load) {
    178         EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
    179             cases[i].name;
    180       } else {
    181         EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
    182             cases[i].name;
    183       }
    184     }
    185 
    186     // Now do a subframe request.
    187     {
    188       net::URLRequest request(extension->GetResourceURL("404.html"),
    189                               &test_delegate_,
    190                               resource_context_.GetRequestContext());
    191       StartRequest(&request, ResourceType::SUB_FRAME);
    192       EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
    193 
    194       if (cases[i].should_allow_sub_frame_load) {
    195         EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
    196             cases[i].name;
    197       } else {
    198         EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
    199             cases[i].name;
    200       }
    201     }
    202   }
    203 }
    204 
    205 // Tests getting a resource for a component extension works correctly, both when
    206 // the extension is enabled and when it is disabled.
    207 TEST_F(ExtensionProtocolTest, ComponentResourceRequest) {
    208   // Register a non-incognito extension protocol handler.
    209   SetProtocolHandler(false);
    210 
    211   scoped_refptr<Extension> extension = CreateWebStoreExtension();
    212   extension_info_map_->AddExtension(extension.get(), base::Time::Now(), false);
    213 
    214   // First test it with the extension enabled.
    215   {
    216     net::URLRequest request(extension->GetResourceURL("webstore_icon_16.png"),
    217                             &test_delegate_,
    218                             resource_context_.GetRequestContext());
    219     StartRequest(&request, ResourceType::MEDIA);
    220     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
    221   }
    222 
    223   // And then test it with the extension disabled.
    224   extension_info_map_->RemoveExtension(extension->id(),
    225                                        extension_misc::UNLOAD_REASON_DISABLE);
    226   {
    227     net::URLRequest request(extension->GetResourceURL("webstore_icon_16.png"),
    228                             &test_delegate_,
    229                             resource_context_.GetRequestContext());
    230     StartRequest(&request, ResourceType::MEDIA);
    231     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
    232   }
    233 }
    234 
    235 // Tests that a URL request for resource from an extension returns a few
    236 // expected response headers.
    237 TEST_F(ExtensionProtocolTest, ResourceRequestResponseHeaders) {
    238   // Register a non-incognito extension protocol handler.
    239   SetProtocolHandler(false);
    240 
    241   scoped_refptr<Extension> extension = CreateTestResponseHeaderExtension();
    242   extension_info_map_->AddExtension(extension.get(), base::Time::Now(), false);
    243 
    244   {
    245     net::URLRequest request(extension->GetResourceURL("test.dat"),
    246                             &test_delegate_,
    247                             resource_context_.GetRequestContext());
    248     StartRequest(&request, ResourceType::MEDIA);
    249     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
    250 
    251     // Check that cache-related headers are set.
    252     std::string etag;
    253     request.GetResponseHeaderByName("ETag", &etag);
    254     EXPECT_TRUE(StartsWithASCII(etag, "\"", false));
    255     EXPECT_TRUE(EndsWith(etag, "\"", false));
    256 
    257     std::string revalidation_header;
    258     request.GetResponseHeaderByName("cache-control", &revalidation_header);
    259     EXPECT_EQ("no-cache", revalidation_header);
    260 
    261     // We set test.dat as web-accessible, so it should have a CORS header.
    262     std::string access_control;
    263     request.GetResponseHeaderByName("Access-Control-Allow-Origin",
    264                                     &access_control);
    265     EXPECT_EQ("*", access_control);
    266   }
    267 }
    268 
    269 }  // namespace extensions
    270