• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CreateTestExtension(const std::string & name,bool incognito_split_mode)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 
CreateWebStoreExtension()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 
CreateTestResponseHeaderExtension()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:
ExtensionProtocolTest()99   ExtensionProtocolTest()
100       : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
101         old_factory_(NULL),
102         resource_context_(&test_url_request_context_) {}
103 
SetUp()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 
TearDown()112   virtual void TearDown() {
113     net::URLRequestContext* request_context =
114         resource_context_.GetRequestContext();
115     request_context->set_job_factory(old_factory_);
116   }
117 
SetProtocolHandler(bool is_incognito)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 
StartRequest(net::URLRequest * request,ResourceType resource_type)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).
TEST_F(ExtensionProtocolTest,IncognitoRequest)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 
CheckForContentLengthHeader(net::URLRequest * request)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.
TEST_F(ExtensionProtocolTest,ComponentResourceRequest)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.
TEST_F(ExtensionProtocolTest,ResourceRequestResponseHeaders)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.
TEST_F(ExtensionProtocolTest,AllowFrameRequests)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