Home | History | Annotate | Download | only in extensions
      1 // Copyright 2014 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/files/file_util.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/values.h"
     13 #include "chrome/common/chrome_paths.h"
     14 #include "content/public/browser/resource_request_info.h"
     15 #include "content/public/test/mock_resource_context.h"
     16 #include "content/public/test/test_browser_thread_bundle.h"
     17 #include "extensions/browser/extension_protocols.h"
     18 #include "extensions/browser/info_map.h"
     19 #include "extensions/common/constants.h"
     20 #include "extensions/common/extension.h"
     21 #include "net/base/request_priority.h"
     22 #include "net/url_request/url_request.h"
     23 #include "net/url_request/url_request_job_factory_impl.h"
     24 #include "net/url_request/url_request_status.h"
     25 #include "net/url_request/url_request_test_util.h"
     26 #include "testing/gtest/include/gtest/gtest.h"
     27 
     28 using content::ResourceType;
     29 
     30 namespace extensions {
     31 namespace {
     32 
     33 scoped_refptr<Extension> CreateTestExtension(const std::string& name,
     34                                              bool incognito_split_mode) {
     35   base::DictionaryValue manifest;
     36   manifest.SetString("name", name);
     37   manifest.SetString("version", "1");
     38   manifest.SetInteger("manifest_version", 2);
     39   manifest.SetString("incognito", incognito_split_mode ? "split" : "spanning");
     40 
     41   base::FilePath path;
     42   EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
     43   path = path.AppendASCII("extensions").AppendASCII("response_headers");
     44 
     45   std::string error;
     46   scoped_refptr<Extension> extension(
     47       Extension::Create(path, Manifest::INTERNAL, manifest,
     48                         Extension::NO_FLAGS, &error));
     49   EXPECT_TRUE(extension.get()) << error;
     50   return extension;
     51 }
     52 
     53 scoped_refptr<Extension> CreateWebStoreExtension() {
     54   base::DictionaryValue manifest;
     55   manifest.SetString("name", "WebStore");
     56   manifest.SetString("version", "1");
     57   manifest.SetString("icons.16", "webstore_icon_16.png");
     58 
     59   base::FilePath path;
     60   EXPECT_TRUE(PathService::Get(chrome::DIR_RESOURCES, &path));
     61   path = path.AppendASCII("web_store");
     62 
     63   std::string error;
     64   scoped_refptr<Extension> extension(
     65       Extension::Create(path, Manifest::COMPONENT, manifest,
     66                         Extension::NO_FLAGS, &error));
     67   EXPECT_TRUE(extension.get()) << error;
     68   return extension;
     69 }
     70 
     71 scoped_refptr<Extension> CreateTestResponseHeaderExtension() {
     72   base::DictionaryValue manifest;
     73   manifest.SetString("name", "An extension with web-accessible resources");
     74   manifest.SetString("version", "2");
     75 
     76   base::ListValue* web_accessible_list = new base::ListValue();
     77   web_accessible_list->AppendString("test.dat");
     78   manifest.Set("web_accessible_resources", web_accessible_list);
     79 
     80   base::FilePath path;
     81   EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
     82   path = path.AppendASCII("extensions").AppendASCII("response_headers");
     83 
     84   std::string error;
     85   scoped_refptr<Extension> extension(
     86       Extension::Create(path, Manifest::UNPACKED, manifest,
     87                         Extension::NO_FLAGS, &error));
     88   EXPECT_TRUE(extension.get()) << error;
     89   return extension;
     90 }
     91 
     92 }  // namespace
     93 
     94 // This test lives in src/chrome instead of src/extensions because it tests
     95 // functionality delegated back to Chrome via ChromeExtensionsBrowserClient.
     96 // See chrome/browser/extensions/chrome_url_request_util.cc.
     97 class ExtensionProtocolTest : public testing::Test {
     98  public:
     99   ExtensionProtocolTest()
    100       : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
    101         old_factory_(NULL),
    102         resource_context_(&test_url_request_context_) {}
    103 
    104   virtual void SetUp() OVERRIDE {
    105     testing::Test::SetUp();
    106     extension_info_map_ = new InfoMap();
    107     net::URLRequestContext* request_context =
    108         resource_context_.GetRequestContext();
    109     old_factory_ = request_context->job_factory();
    110   }
    111 
    112   virtual void TearDown() {
    113     net::URLRequestContext* request_context =
    114         resource_context_.GetRequestContext();
    115     request_context->set_job_factory(old_factory_);
    116   }
    117 
    118   void SetProtocolHandler(bool is_incognito) {
    119     net::URLRequestContext* request_context =
    120         resource_context_.GetRequestContext();
    121     job_factory_.SetProtocolHandler(
    122         kExtensionScheme,
    123         CreateExtensionProtocolHandler(is_incognito,
    124                                        extension_info_map_.get()));
    125     request_context->set_job_factory(&job_factory_);
    126   }
    127 
    128   void StartRequest(net::URLRequest* request,
    129                     ResourceType resource_type) {
    130     content::ResourceRequestInfo::AllocateForTesting(request,
    131                                                      resource_type,
    132                                                      &resource_context_,
    133                                                      -1,
    134                                                      -1,
    135                                                      -1,
    136                                                      false);
    137     request->Start();
    138     base::MessageLoop::current()->Run();
    139   }
    140 
    141  protected:
    142   content::TestBrowserThreadBundle thread_bundle_;
    143   scoped_refptr<InfoMap> extension_info_map_;
    144   net::URLRequestJobFactoryImpl job_factory_;
    145   const net::URLRequestJobFactory* old_factory_;
    146   net::TestDelegate test_delegate_;
    147   net::TestURLRequestContext test_url_request_context_;
    148   content::MockResourceContext resource_context_;
    149 };
    150 
    151 // Tests that making a chrome-extension request in an incognito context is
    152 // only allowed under the right circumstances (if the extension is allowed
    153 // in incognito, and it's either a non-main-frame request or a split-mode
    154 // extension).
    155 TEST_F(ExtensionProtocolTest, IncognitoRequest) {
    156   // Register an incognito extension protocol handler.
    157   SetProtocolHandler(true);
    158 
    159   struct TestCase {
    160     // Inputs.
    161     std::string name;
    162     bool incognito_split_mode;
    163     bool incognito_enabled;
    164 
    165     // Expected results.
    166     bool should_allow_main_frame_load;
    167     bool should_allow_sub_frame_load;
    168   } cases[] = {
    169     {"spanning disabled", false, false, false, false},
    170     {"split disabled", true, false, false, false},
    171     {"spanning enabled", false, true, false, true},
    172     {"split enabled", true, true, true, true},
    173   };
    174 
    175   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
    176     scoped_refptr<Extension> extension =
    177         CreateTestExtension(cases[i].name, cases[i].incognito_split_mode);
    178     extension_info_map_->AddExtension(
    179         extension.get(), base::Time::Now(), cases[i].incognito_enabled, false);
    180 
    181     // First test a main frame request.
    182     {
    183       // It doesn't matter that the resource doesn't exist. If the resource
    184       // is blocked, we should see ADDRESS_UNREACHABLE. Otherwise, the request
    185       // should just fail because the file doesn't exist.
    186       scoped_ptr<net::URLRequest> request(
    187           resource_context_.GetRequestContext()->CreateRequest(
    188               extension->GetResourceURL("404.html"),
    189               net::DEFAULT_PRIORITY,
    190               &test_delegate_,
    191               NULL));
    192       StartRequest(request.get(), content::RESOURCE_TYPE_MAIN_FRAME);
    193       EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
    194 
    195       if (cases[i].should_allow_main_frame_load) {
    196         EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error()) <<
    197             cases[i].name;
    198       } else {
    199         EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request->status().error()) <<
    200             cases[i].name;
    201       }
    202     }
    203 
    204     // Now do a subframe request.
    205     {
    206       scoped_ptr<net::URLRequest> request(
    207           resource_context_.GetRequestContext()->CreateRequest(
    208               extension->GetResourceURL("404.html"),
    209               net::DEFAULT_PRIORITY,
    210               &test_delegate_,
    211               NULL));
    212       StartRequest(request.get(), content::RESOURCE_TYPE_SUB_FRAME);
    213       EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
    214 
    215       if (cases[i].should_allow_sub_frame_load) {
    216         EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error()) <<
    217             cases[i].name;
    218       } else {
    219         EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request->status().error()) <<
    220             cases[i].name;
    221       }
    222     }
    223   }
    224 }
    225 
    226 void CheckForContentLengthHeader(net::URLRequest* request) {
    227   std::string content_length;
    228   request->GetResponseHeaderByName(net::HttpRequestHeaders::kContentLength,
    229                                   &content_length);
    230   EXPECT_FALSE(content_length.empty());
    231   int length_value = 0;
    232   EXPECT_TRUE(base::StringToInt(content_length, &length_value));
    233   EXPECT_GT(length_value, 0);
    234 }
    235 
    236 // Tests getting a resource for a component extension works correctly, both when
    237 // the extension is enabled and when it is disabled.
    238 TEST_F(ExtensionProtocolTest, ComponentResourceRequest) {
    239   // Register a non-incognito extension protocol handler.
    240   SetProtocolHandler(false);
    241 
    242   scoped_refptr<Extension> extension = CreateWebStoreExtension();
    243   extension_info_map_->AddExtension(extension.get(),
    244                                     base::Time::Now(),
    245                                     false,
    246                                     false);
    247 
    248   // First test it with the extension enabled.
    249   {
    250     scoped_ptr<net::URLRequest> request(
    251         resource_context_.GetRequestContext()->CreateRequest(
    252             extension->GetResourceURL("webstore_icon_16.png"),
    253             net::DEFAULT_PRIORITY,
    254             &test_delegate_,
    255             NULL));
    256     StartRequest(request.get(), content::RESOURCE_TYPE_MEDIA);
    257     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    258     CheckForContentLengthHeader(request.get());
    259   }
    260 
    261   // And then test it with the extension disabled.
    262   extension_info_map_->RemoveExtension(extension->id(),
    263                                        UnloadedExtensionInfo::REASON_DISABLE);
    264   {
    265     scoped_ptr<net::URLRequest> request(
    266         resource_context_.GetRequestContext()->CreateRequest(
    267             extension->GetResourceURL("webstore_icon_16.png"),
    268             net::DEFAULT_PRIORITY,
    269             &test_delegate_,
    270             NULL));
    271     StartRequest(request.get(), content::RESOURCE_TYPE_MEDIA);
    272     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    273     CheckForContentLengthHeader(request.get());
    274   }
    275 }
    276 
    277 // Tests that a URL request for resource from an extension returns a few
    278 // expected response headers.
    279 TEST_F(ExtensionProtocolTest, ResourceRequestResponseHeaders) {
    280   // Register a non-incognito extension protocol handler.
    281   SetProtocolHandler(false);
    282 
    283   scoped_refptr<Extension> extension = CreateTestResponseHeaderExtension();
    284   extension_info_map_->AddExtension(extension.get(),
    285                                     base::Time::Now(),
    286                                     false,
    287                                     false);
    288 
    289   {
    290     scoped_ptr<net::URLRequest> request(
    291         resource_context_.GetRequestContext()->CreateRequest(
    292             extension->GetResourceURL("test.dat"),
    293             net::DEFAULT_PRIORITY,
    294             &test_delegate_,
    295             NULL));
    296     StartRequest(request.get(), content::RESOURCE_TYPE_MEDIA);
    297     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    298 
    299     // Check that cache-related headers are set.
    300     std::string etag;
    301     request->GetResponseHeaderByName("ETag", &etag);
    302     EXPECT_TRUE(StartsWithASCII(etag, "\"", false));
    303     EXPECT_TRUE(EndsWith(etag, "\"", false));
    304 
    305     std::string revalidation_header;
    306     request->GetResponseHeaderByName("cache-control", &revalidation_header);
    307     EXPECT_EQ("no-cache", revalidation_header);
    308 
    309     // We set test.dat as web-accessible, so it should have a CORS header.
    310     std::string access_control;
    311     request->GetResponseHeaderByName("Access-Control-Allow-Origin",
    312                                     &access_control);
    313     EXPECT_EQ("*", access_control);
    314   }
    315 }
    316 
    317 // Tests that a URL request for main frame or subframe from an extension
    318 // succeeds, but subresources fail. See http://crbug.com/312269.
    319 TEST_F(ExtensionProtocolTest, AllowFrameRequests) {
    320   // Register a non-incognito extension protocol handler.
    321   SetProtocolHandler(false);
    322 
    323   scoped_refptr<Extension> extension = CreateTestExtension("foo", false);
    324   extension_info_map_->AddExtension(extension.get(),
    325                                     base::Time::Now(),
    326                                     false,
    327                                     false);
    328 
    329   // All MAIN_FRAME and SUB_FRAME requests should succeed.
    330   {
    331     scoped_ptr<net::URLRequest> request(
    332         resource_context_.GetRequestContext()->CreateRequest(
    333             extension->GetResourceURL("test.dat"),
    334             net::DEFAULT_PRIORITY,
    335             &test_delegate_,
    336             NULL));
    337     StartRequest(request.get(), content::RESOURCE_TYPE_MAIN_FRAME);
    338     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    339   }
    340   {
    341     scoped_ptr<net::URLRequest> request(
    342         resource_context_.GetRequestContext()->CreateRequest(
    343             extension->GetResourceURL("test.dat"),
    344             net::DEFAULT_PRIORITY,
    345             &test_delegate_,
    346             NULL));
    347     StartRequest(request.get(), content::RESOURCE_TYPE_SUB_FRAME);
    348     EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
    349   }
    350 
    351   // And subresource types, such as media, should fail.
    352   {
    353     scoped_ptr<net::URLRequest> request(
    354         resource_context_.GetRequestContext()->CreateRequest(
    355             extension->GetResourceURL("test.dat"),
    356             net::DEFAULT_PRIORITY,
    357             &test_delegate_,
    358             NULL));
    359     StartRequest(request.get(), content::RESOURCE_TYPE_MEDIA);
    360     EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
    361   }
    362 }
    363 
    364 }  // namespace extensions
    365