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