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 "webkit/browser/fileapi/file_system_url_request_job.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/file_util.h" 11 #include "base/files/file_path.h" 12 #include "base/files/scoped_temp_dir.h" 13 #include "base/format_macros.h" 14 #include "base/memory/weak_ptr.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/message_loop/message_loop_proxy.h" 17 #include "base/platform_file.h" 18 #include "base/rand_util.h" 19 #include "base/strings/string_piece.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "net/base/load_flags.h" 23 #include "net/base/mime_util.h" 24 #include "net/base/net_errors.h" 25 #include "net/base/net_util.h" 26 #include "net/http/http_request_headers.h" 27 #include "net/url_request/url_request.h" 28 #include "net/url_request/url_request_context.h" 29 #include "net/url_request/url_request_test_util.h" 30 #include "testing/gtest/include/gtest/gtest.h" 31 #include "webkit/browser/fileapi/file_system_context.h" 32 #include "webkit/browser/fileapi/file_system_file_util.h" 33 #include "webkit/browser/fileapi/file_system_operation_context.h" 34 #include "webkit/browser/fileapi/mock_file_system_context.h" 35 36 namespace fileapi { 37 namespace { 38 39 // We always use the TEMPORARY FileSystem in this test. 40 const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; 41 const char kTestFileData[] = "0123456789"; 42 43 void FillBuffer(char* buffer, size_t len) { 44 base::RandBytes(buffer, len); 45 } 46 47 } // namespace 48 49 class FileSystemURLRequestJobTest : public testing::Test { 50 protected: 51 FileSystemURLRequestJobTest() 52 : message_loop_(base::MessageLoop::TYPE_IO), // simulate an IO thread 53 weak_factory_(this) { 54 } 55 56 virtual void SetUp() OVERRIDE { 57 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 58 59 // We use the main thread so that we can get the root path synchronously. 60 // TODO(adamk): Run this on the FILE thread we've created as well. 61 file_system_context_ = 62 CreateFileSystemContextForTesting(NULL, temp_dir_.path()); 63 64 file_system_context_->OpenFileSystem( 65 GURL("http://remote/"), kFileSystemTypeTemporary, 66 OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, 67 base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem, 68 weak_factory_.GetWeakPtr())); 69 base::MessageLoop::current()->RunUntilIdle(); 70 71 net::URLRequest::Deprecated::RegisterProtocolFactory( 72 "filesystem", &FileSystemURLRequestJobFactory); 73 } 74 75 virtual void TearDown() OVERRIDE { 76 net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL); 77 ClearUnusedJob(); 78 if (pending_job_.get()) { 79 pending_job_->Kill(); 80 pending_job_ = NULL; 81 } 82 // FileReader posts a task to close the file in destructor. 83 base::MessageLoop::current()->RunUntilIdle(); 84 } 85 86 void OnOpenFileSystem(base::PlatformFileError result, 87 const std::string& name, 88 const GURL& root_url) { 89 ASSERT_EQ(base::PLATFORM_FILE_OK, result); 90 } 91 92 void TestRequestHelper(const GURL& url, 93 const net::HttpRequestHeaders* headers, 94 bool run_to_completion) { 95 delegate_.reset(new net::TestDelegate()); 96 // Make delegate_ exit the MessageLoop when the request is done. 97 delegate_->set_quit_on_complete(true); 98 delegate_->set_quit_on_redirect(true); 99 request_.reset(empty_context_.CreateRequest(url, delegate_.get())); 100 if (headers) 101 request_->SetExtraRequestHeaders(*headers); 102 ASSERT_TRUE(!job_); 103 job_ = new FileSystemURLRequestJob( 104 request_.get(), NULL, file_system_context_.get()); 105 pending_job_ = job_; 106 107 request_->Start(); 108 ASSERT_TRUE(request_->is_pending()); // verify that we're starting async 109 if (run_to_completion) 110 base::MessageLoop::current()->Run(); 111 } 112 113 void TestRequest(const GURL& url) { 114 TestRequestHelper(url, NULL, true); 115 } 116 117 void TestRequestWithHeaders(const GURL& url, 118 const net::HttpRequestHeaders* headers) { 119 TestRequestHelper(url, headers, true); 120 } 121 122 void TestRequestNoRun(const GURL& url) { 123 TestRequestHelper(url, NULL, false); 124 } 125 126 void CreateDirectory(const base::StringPiece& dir_name) { 127 FileSystemFileUtil* file_util = file_system_context_->GetFileUtil( 128 kFileSystemTypeTemporary); 129 FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 130 GURL("http://remote"), 131 kFileSystemTypeTemporary, 132 base::FilePath().AppendASCII(dir_name)); 133 134 FileSystemOperationContext context(file_system_context_.get()); 135 context.set_allowed_bytes_growth(1024); 136 137 ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateDirectory( 138 &context, 139 url, 140 false /* exclusive */, 141 false /* recursive */)); 142 } 143 144 void WriteFile(const base::StringPiece& file_name, 145 const char* buf, int buf_size) { 146 FileSystemFileUtil* file_util = file_system_context_->GetFileUtil( 147 kFileSystemTypeTemporary); 148 FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 149 GURL("http://remote"), 150 kFileSystemTypeTemporary, 151 base::FilePath().AppendASCII(file_name)); 152 153 FileSystemOperationContext context(file_system_context_.get()); 154 context.set_allowed_bytes_growth(1024); 155 156 base::PlatformFile handle = base::kInvalidPlatformFileValue; 157 bool created = false; 158 ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen( 159 &context, 160 url, 161 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, 162 &handle, 163 &created)); 164 EXPECT_TRUE(created); 165 ASSERT_NE(base::kInvalidPlatformFileValue, handle); 166 ASSERT_EQ(buf_size, 167 base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size)); 168 base::ClosePlatformFile(handle); 169 } 170 171 GURL CreateFileSystemURL(const std::string& path) { 172 return GURL(kFileSystemURLPrefix + path); 173 } 174 175 static net::URLRequestJob* FileSystemURLRequestJobFactory( 176 net::URLRequest* request, 177 net::NetworkDelegate* network_delegate, 178 const std::string& scheme) { 179 DCHECK(job_); 180 net::URLRequestJob* temp = job_; 181 job_ = NULL; 182 return temp; 183 } 184 185 static void ClearUnusedJob() { 186 if (job_) { 187 scoped_refptr<net::URLRequestJob> deleter = job_; 188 job_ = NULL; 189 } 190 } 191 192 // Put the message loop at the top, so that it's the last thing deleted. 193 base::MessageLoop message_loop_; 194 195 base::ScopedTempDir temp_dir_; 196 scoped_refptr<FileSystemContext> file_system_context_; 197 base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_; 198 199 net::URLRequestContext empty_context_; 200 201 // NOTE: order matters, request must die before delegate 202 scoped_ptr<net::TestDelegate> delegate_; 203 scoped_ptr<net::URLRequest> request_; 204 205 scoped_refptr<net::URLRequestJob> pending_job_; 206 static net::URLRequestJob* job_; 207 }; 208 209 // static 210 net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL; 211 212 namespace { 213 214 TEST_F(FileSystemURLRequestJobTest, FileTest) { 215 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 216 TestRequest(CreateFileSystemURL("file1.dat")); 217 218 ASSERT_FALSE(request_->is_pending()); 219 EXPECT_EQ(1, delegate_->response_started_count()); 220 EXPECT_FALSE(delegate_->received_data_before_response()); 221 EXPECT_EQ(kTestFileData, delegate_->data_received()); 222 EXPECT_EQ(200, request_->GetResponseCode()); 223 std::string cache_control; 224 request_->GetResponseHeaderByName("cache-control", &cache_control); 225 EXPECT_EQ("no-cache", cache_control); 226 } 227 228 TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) { 229 const size_t buffer_size = 4000; 230 scoped_ptr<char[]> buffer(new char[buffer_size]); 231 FillBuffer(buffer.get(), buffer_size); 232 WriteFile("bigfile", buffer.get(), buffer_size); 233 234 const size_t first_byte_position = 500; 235 const size_t last_byte_position = buffer_size - first_byte_position; 236 std::string partial_buffer_string(buffer.get() + first_byte_position, 237 buffer.get() + last_byte_position + 1); 238 239 net::HttpRequestHeaders headers; 240 headers.SetHeader(net::HttpRequestHeaders::kRange, 241 base::StringPrintf( 242 "bytes=%" PRIuS "-%" PRIuS, 243 first_byte_position, last_byte_position)); 244 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); 245 246 ASSERT_FALSE(request_->is_pending()); 247 EXPECT_EQ(1, delegate_->response_started_count()); 248 EXPECT_FALSE(delegate_->received_data_before_response()); 249 EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); 250 } 251 252 TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) { 253 const size_t buffer_size = 4000; 254 scoped_ptr<char[]> buffer(new char[buffer_size]); 255 FillBuffer(buffer.get(), buffer_size); 256 WriteFile("bigfile", buffer.get(), buffer_size); 257 258 const size_t first_byte_position = 500; 259 std::string partial_buffer_string(buffer.get() + first_byte_position, 260 buffer.get() + buffer_size); 261 262 net::HttpRequestHeaders headers; 263 headers.SetHeader(net::HttpRequestHeaders::kRange, 264 base::StringPrintf("bytes=%" PRIuS "-", 265 first_byte_position)); 266 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); 267 ASSERT_FALSE(request_->is_pending()); 268 EXPECT_EQ(1, delegate_->response_started_count()); 269 EXPECT_FALSE(delegate_->received_data_before_response()); 270 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. 271 EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); 272 } 273 274 275 TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) { 276 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 277 net::HttpRequestHeaders headers; 278 headers.SetHeader(net::HttpRequestHeaders::kRange, 279 "bytes=0-5,10-200,200-300"); 280 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); 281 EXPECT_TRUE(delegate_->request_failed()); 282 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, 283 request_->status().error()); 284 } 285 286 TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) { 287 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 288 net::HttpRequestHeaders headers; 289 headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=500-1000"); 290 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); 291 292 ASSERT_FALSE(request_->is_pending()); 293 EXPECT_TRUE(delegate_->request_failed()); 294 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, 295 request_->status().error()); 296 } 297 298 TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) { 299 CreateDirectory("dir"); 300 TestRequest(CreateFileSystemURL("dir")); 301 302 EXPECT_EQ(1, delegate_->received_redirect_count()); 303 EXPECT_TRUE(request_->status().is_success()); 304 EXPECT_FALSE(delegate_->request_failed()); 305 306 // We've deferred the redirect; now cancel the request to avoid following it. 307 request_->Cancel(); 308 base::MessageLoop::current()->Run(); 309 } 310 311 TEST_F(FileSystemURLRequestJobTest, InvalidURL) { 312 TestRequest(GURL("filesystem:/foo/bar/baz")); 313 ASSERT_FALSE(request_->is_pending()); 314 EXPECT_TRUE(delegate_->request_failed()); 315 EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error()); 316 } 317 318 TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) { 319 TestRequest(GURL("filesystem:http://remote/persistent/somefile")); 320 ASSERT_FALSE(request_->is_pending()); 321 EXPECT_TRUE(delegate_->request_failed()); 322 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 323 } 324 325 TEST_F(FileSystemURLRequestJobTest, NoSuchFile) { 326 TestRequest(CreateFileSystemURL("somefile")); 327 ASSERT_FALSE(request_->is_pending()); 328 EXPECT_TRUE(delegate_->request_failed()); 329 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 330 } 331 332 TEST_F(FileSystemURLRequestJobTest, Cancel) { 333 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 334 TestRequestNoRun(CreateFileSystemURL("file1.dat")); 335 336 // Run StartAsync() and only StartAsync(). 337 base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); 338 base::MessageLoop::current()->RunUntilIdle(); 339 // If we get here, success! we didn't crash! 340 } 341 342 TEST_F(FileSystemURLRequestJobTest, GetMimeType) { 343 const char kFilename[] = "hoge.html"; 344 345 std::string mime_type_direct; 346 base::FilePath::StringType extension = 347 base::FilePath().AppendASCII(kFilename).Extension(); 348 if (!extension.empty()) 349 extension = extension.substr(1); 350 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension( 351 extension, &mime_type_direct)); 352 353 TestRequest(CreateFileSystemURL(kFilename)); 354 355 std::string mime_type_from_job; 356 request_->GetMimeType(&mime_type_from_job); 357 EXPECT_EQ(mime_type_direct, mime_type_from_job); 358 } 359 360 } // namespace 361 } // namespace fileapi 362