1 // Copyright 2013 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.
5 #include "webkit/browser/fileapi/file_system_url_request_job.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/scoped_vector.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/rand_util.h"
19 #include "base/run_loop.h"
20 #include "base/strings/string_piece.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "content/public/test/async_file_test_helper.h"
24 #include "content/public/test/test_file_system_backend.h"
25 #include "content/public/test/test_file_system_context.h"
26 #include "net/base/load_flags.h"
27 #include "net/base/mime_util.h"
28 #include "net/base/net_errors.h"
29 #include "net/base/net_util.h"
30 #include "net/base/request_priority.h"
31 #include "net/http/http_byte_range.h"
32 #include "net/http/http_request_headers.h"
33 #include "net/url_request/url_request.h"
34 #include "net/url_request/url_request_context.h"
35 #include "net/url_request/url_request_test_util.h"
36 #include "testing/gtest/include/gtest/gtest.h"
37 #include "webkit/browser/fileapi/external_mount_points.h"
38 #include "webkit/browser/fileapi/file_system_context.h"
39 #include "webkit/browser/fileapi/file_system_file_util.h"
41 using content::AsyncFileTestHelper
;
42 using storage::FileSystemContext
;
43 using storage::FileSystemURL
;
44 using storage::FileSystemURLRequestJob
;
49 // We always use the TEMPORARY FileSystem in this test.
50 const char kFileSystemURLPrefix
[] = "filesystem:http://remote/temporary/";
51 const char kTestFileData
[] = "0123456789";
53 void FillBuffer(char* buffer
, size_t len
) {
54 base::RandBytes(buffer
, len
);
57 const char kValidExternalMountPoint
[] = "mnt_name";
59 // An auto mounter that will try to mount anything for |storage_domain| =
60 // "automount", but will only succeed for the mount point "mnt_name".
61 bool TestAutoMountForURLRequest(
62 const net::URLRequest
* /*url_request*/,
63 const storage::FileSystemURL
& filesystem_url
,
64 const std::string
& storage_domain
,
65 const base::Callback
<void(base::File::Error result
)>& callback
) {
66 if (storage_domain
!= "automount")
68 std::vector
<base::FilePath::StringType
> components
;
69 filesystem_url
.path().GetComponents(&components
);
70 std::string mount_point
= base::FilePath(components
[0]).AsUTF8Unsafe();
72 if (mount_point
== kValidExternalMountPoint
) {
73 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
74 kValidExternalMountPoint
,
75 storage::kFileSystemTypeTest
,
76 storage::FileSystemMountOption(),
78 callback
.Run(base::File::FILE_OK
);
80 callback
.Run(base::File::FILE_ERROR_NOT_FOUND
);
85 class FileSystemURLRequestJobFactory
: public net::URLRequestJobFactory
{
87 FileSystemURLRequestJobFactory(const std::string
& storage_domain
,
88 FileSystemContext
* context
)
89 : storage_domain_(storage_domain
), file_system_context_(context
) {
92 virtual net::URLRequestJob
* MaybeCreateJobWithProtocolHandler(
93 const std::string
& scheme
,
94 net::URLRequest
* request
,
95 net::NetworkDelegate
* network_delegate
) const OVERRIDE
{
96 return new storage::FileSystemURLRequestJob(
97 request
, network_delegate
, storage_domain_
, file_system_context_
);
100 virtual bool IsHandledProtocol(const std::string
& scheme
) const OVERRIDE
{
104 virtual bool IsHandledURL(const GURL
& url
) const OVERRIDE
{
108 virtual bool IsSafeRedirectTarget(const GURL
& location
) const OVERRIDE
{
113 std::string storage_domain_
;
114 FileSystemContext
* file_system_context_
;
119 class FileSystemURLRequestJobTest
: public testing::Test
{
121 FileSystemURLRequestJobTest() : weak_factory_(this) {
124 virtual void SetUp() OVERRIDE
{
125 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
127 // We use the main thread so that we can get the root path synchronously.
128 // TODO(adamk): Run this on the FILE thread we've created as well.
129 file_system_context_
=
130 CreateFileSystemContextForTesting(NULL
, temp_dir_
.path());
132 file_system_context_
->OpenFileSystem(
133 GURL("http://remote/"),
134 storage::kFileSystemTypeTemporary
,
135 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT
,
136 base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem
,
137 weak_factory_
.GetWeakPtr()));
138 base::RunLoop().RunUntilIdle();
141 virtual void TearDown() OVERRIDE
{
142 // FileReader posts a task to close the file in destructor.
143 base::RunLoop().RunUntilIdle();
146 void SetUpAutoMountContext() {
147 base::FilePath mnt_point
= temp_dir_
.path().AppendASCII("auto_mount_dir");
148 ASSERT_TRUE(base::CreateDirectory(mnt_point
));
150 ScopedVector
<storage::FileSystemBackend
> additional_providers
;
151 additional_providers
.push_back(new TestFileSystemBackend(
152 base::MessageLoopProxy::current().get(), mnt_point
));
154 std::vector
<storage::URLRequestAutoMountHandler
> handlers
;
155 handlers
.push_back(base::Bind(&TestAutoMountForURLRequest
));
157 file_system_context_
= CreateFileSystemContextWithAutoMountersForTesting(
158 NULL
, additional_providers
.Pass(), handlers
, temp_dir_
.path());
160 ASSERT_EQ(static_cast<int>(sizeof(kTestFileData
)) - 1,
161 base::WriteFile(mnt_point
.AppendASCII("foo"), kTestFileData
,
162 sizeof(kTestFileData
) - 1));
165 void OnOpenFileSystem(const GURL
& root_url
,
166 const std::string
& name
,
167 base::File::Error result
) {
168 ASSERT_EQ(base::File::FILE_OK
, result
);
171 void TestRequestHelper(const GURL
& url
,
172 const net::HttpRequestHeaders
* headers
,
173 bool run_to_completion
,
174 FileSystemContext
* file_system_context
) {
175 delegate_
.reset(new net::TestDelegate());
176 // Make delegate_ exit the MessageLoop when the request is done.
177 delegate_
->set_quit_on_complete(true);
178 delegate_
->set_quit_on_redirect(true);
180 job_factory_
.reset(new FileSystemURLRequestJobFactory(
181 url
.GetOrigin().host(), file_system_context
));
182 empty_context_
.set_job_factory(job_factory_
.get());
184 request_
= empty_context_
.CreateRequest(
185 url
, net::DEFAULT_PRIORITY
, delegate_
.get(), NULL
);
187 request_
->SetExtraRequestHeaders(*headers
);
190 ASSERT_TRUE(request_
->is_pending()); // verify that we're starting async
191 if (run_to_completion
)
192 base::MessageLoop::current()->Run();
195 void TestRequest(const GURL
& url
) {
196 TestRequestHelper(url
, NULL
, true, file_system_context_
.get());
199 void TestRequestWithContext(const GURL
& url
,
200 FileSystemContext
* file_system_context
) {
201 TestRequestHelper(url
, NULL
, true, file_system_context
);
204 void TestRequestWithHeaders(const GURL
& url
,
205 const net::HttpRequestHeaders
* headers
) {
206 TestRequestHelper(url
, headers
, true, file_system_context_
.get());
209 void TestRequestNoRun(const GURL
& url
) {
210 TestRequestHelper(url
, NULL
, false, file_system_context_
.get());
213 void CreateDirectory(const base::StringPiece
& dir_name
) {
214 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
215 GURL("http://remote"),
216 storage::kFileSystemTypeTemporary
,
217 base::FilePath().AppendASCII(dir_name
));
220 AsyncFileTestHelper::CreateDirectory(file_system_context_
.get(), url
));
223 void WriteFile(const base::StringPiece
& file_name
,
224 const char* buf
, int buf_size
) {
225 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
226 GURL("http://remote"),
227 storage::kFileSystemTypeTemporary
,
228 base::FilePath().AppendASCII(file_name
));
229 ASSERT_EQ(base::File::FILE_OK
,
230 AsyncFileTestHelper::CreateFileWithData(
231 file_system_context_
.get(), url
, buf
, buf_size
));
234 GURL
CreateFileSystemURL(const std::string
& path
) {
235 return GURL(kFileSystemURLPrefix
+ path
);
238 // Put the message loop at the top, so that it's the last thing deleted.
239 base::MessageLoopForIO message_loop_
;
241 base::ScopedTempDir temp_dir_
;
242 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
243 base::WeakPtrFactory
<FileSystemURLRequestJobTest
> weak_factory_
;
245 net::URLRequestContext empty_context_
;
246 scoped_ptr
<FileSystemURLRequestJobFactory
> job_factory_
;
248 // NOTE: order matters, request must die before delegate
249 scoped_ptr
<net::TestDelegate
> delegate_
;
250 scoped_ptr
<net::URLRequest
> request_
;
255 TEST_F(FileSystemURLRequestJobTest
, FileTest
) {
256 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
257 TestRequest(CreateFileSystemURL("file1.dat"));
259 ASSERT_FALSE(request_
->is_pending());
260 EXPECT_EQ(1, delegate_
->response_started_count());
261 EXPECT_FALSE(delegate_
->received_data_before_response());
262 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
263 EXPECT_EQ(200, request_
->GetResponseCode());
264 std::string cache_control
;
265 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
266 EXPECT_EQ("no-cache", cache_control
);
269 TEST_F(FileSystemURLRequestJobTest
, FileTestFullSpecifiedRange
) {
270 const size_t buffer_size
= 4000;
271 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
272 FillBuffer(buffer
.get(), buffer_size
);
273 WriteFile("bigfile", buffer
.get(), buffer_size
);
275 const size_t first_byte_position
= 500;
276 const size_t last_byte_position
= buffer_size
- first_byte_position
;
277 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
278 buffer
.get() + last_byte_position
+ 1);
280 net::HttpRequestHeaders headers
;
282 net::HttpRequestHeaders::kRange
,
283 net::HttpByteRange::Bounded(
284 first_byte_position
, last_byte_position
).GetHeaderValue());
285 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
287 ASSERT_FALSE(request_
->is_pending());
288 EXPECT_EQ(1, delegate_
->response_started_count());
289 EXPECT_FALSE(delegate_
->received_data_before_response());
290 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
293 TEST_F(FileSystemURLRequestJobTest
, FileTestHalfSpecifiedRange
) {
294 const size_t buffer_size
= 4000;
295 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
296 FillBuffer(buffer
.get(), buffer_size
);
297 WriteFile("bigfile", buffer
.get(), buffer_size
);
299 const size_t first_byte_position
= 500;
300 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
301 buffer
.get() + buffer_size
);
303 net::HttpRequestHeaders headers
;
305 net::HttpRequestHeaders::kRange
,
306 net::HttpByteRange::RightUnbounded(first_byte_position
).GetHeaderValue());
307 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
308 ASSERT_FALSE(request_
->is_pending());
309 EXPECT_EQ(1, delegate_
->response_started_count());
310 EXPECT_FALSE(delegate_
->received_data_before_response());
311 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
312 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
315 TEST_F(FileSystemURLRequestJobTest
, FileTestMultipleRangesNotSupported
) {
316 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
317 net::HttpRequestHeaders headers
;
318 headers
.SetHeader(net::HttpRequestHeaders::kRange
,
319 "bytes=0-5,10-200,200-300");
320 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
321 EXPECT_TRUE(delegate_
->request_failed());
322 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
323 request_
->status().error());
326 TEST_F(FileSystemURLRequestJobTest
, RangeOutOfBounds
) {
327 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
328 net::HttpRequestHeaders headers
;
330 net::HttpRequestHeaders::kRange
,
331 net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
332 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
334 ASSERT_FALSE(request_
->is_pending());
335 EXPECT_TRUE(delegate_
->request_failed());
336 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
337 request_
->status().error());
340 TEST_F(FileSystemURLRequestJobTest
, FileDirRedirect
) {
341 CreateDirectory("dir");
342 TestRequest(CreateFileSystemURL("dir"));
344 EXPECT_EQ(1, delegate_
->received_redirect_count());
345 EXPECT_TRUE(request_
->status().is_success());
346 EXPECT_FALSE(delegate_
->request_failed());
348 // We've deferred the redirect; now cancel the request to avoid following it.
350 base::MessageLoop::current()->Run();
353 TEST_F(FileSystemURLRequestJobTest
, InvalidURL
) {
354 TestRequest(GURL("filesystem:/foo/bar/baz"));
355 ASSERT_FALSE(request_
->is_pending());
356 EXPECT_TRUE(delegate_
->request_failed());
357 EXPECT_EQ(net::ERR_INVALID_URL
, request_
->status().error());
360 TEST_F(FileSystemURLRequestJobTest
, NoSuchRoot
) {
361 TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
362 ASSERT_FALSE(request_
->is_pending());
363 EXPECT_TRUE(delegate_
->request_failed());
364 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
367 TEST_F(FileSystemURLRequestJobTest
, NoSuchFile
) {
368 TestRequest(CreateFileSystemURL("somefile"));
369 ASSERT_FALSE(request_
->is_pending());
370 EXPECT_TRUE(delegate_
->request_failed());
371 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
374 TEST_F(FileSystemURLRequestJobTest
, Cancel
) {
375 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
376 TestRequestNoRun(CreateFileSystemURL("file1.dat"));
378 // Run StartAsync() and only StartAsync().
379 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, request_
.release());
380 base::RunLoop().RunUntilIdle();
381 // If we get here, success! we didn't crash!
384 TEST_F(FileSystemURLRequestJobTest
, GetMimeType
) {
385 const char kFilename
[] = "hoge.html";
387 std::string mime_type_direct
;
388 base::FilePath::StringType extension
=
389 base::FilePath().AppendASCII(kFilename
).Extension();
390 if (!extension
.empty())
391 extension
= extension
.substr(1);
392 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
393 extension
, &mime_type_direct
));
395 TestRequest(CreateFileSystemURL(kFilename
));
397 std::string mime_type_from_job
;
398 request_
->GetMimeType(&mime_type_from_job
);
399 EXPECT_EQ(mime_type_direct
, mime_type_from_job
);
402 TEST_F(FileSystemURLRequestJobTest
, Incognito
) {
403 WriteFile("file", kTestFileData
, arraysize(kTestFileData
) - 1);
405 // Creates a new filesystem context for incognito mode.
406 scoped_refptr
<FileSystemContext
> file_system_context
=
407 CreateIncognitoFileSystemContextForTesting(NULL
, temp_dir_
.path());
409 // The request should return NOT_FOUND error if it's in incognito mode.
410 TestRequestWithContext(CreateFileSystemURL("file"),
411 file_system_context
.get());
412 ASSERT_FALSE(request_
->is_pending());
413 EXPECT_TRUE(delegate_
->request_failed());
414 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
416 // Make sure it returns success with regular (non-incognito) context.
417 TestRequest(CreateFileSystemURL("file"));
418 ASSERT_FALSE(request_
->is_pending());
419 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
420 EXPECT_EQ(200, request_
->GetResponseCode());
423 TEST_F(FileSystemURLRequestJobTest
, AutoMountFileTest
) {
424 SetUpAutoMountContext();
425 TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
427 ASSERT_FALSE(request_
->is_pending());
428 EXPECT_EQ(1, delegate_
->response_started_count());
429 EXPECT_FALSE(delegate_
->received_data_before_response());
430 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
431 EXPECT_EQ(200, request_
->GetResponseCode());
432 std::string cache_control
;
433 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
434 EXPECT_EQ("no-cache", cache_control
);
437 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
438 kValidExternalMountPoint
));
441 TEST_F(FileSystemURLRequestJobTest
, AutoMountInvalidRoot
) {
442 SetUpAutoMountContext();
443 TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
445 ASSERT_FALSE(request_
->is_pending());
446 EXPECT_TRUE(delegate_
->request_failed());
447 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
450 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
454 TEST_F(FileSystemURLRequestJobTest
, AutoMountNoHandler
) {
455 SetUpAutoMountContext();
456 TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo"));
458 ASSERT_FALSE(request_
->is_pending());
459 EXPECT_TRUE(delegate_
->request_failed());
460 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
463 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
464 kValidExternalMountPoint
));
468 } // namespace content