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