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 "storage/browser/fileapi/file_system_url_request_job.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.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 "storage/browser/fileapi/external_mount_points.h"
37 #include "storage/browser/fileapi/file_system_context.h"
38 #include "storage/browser/fileapi/file_system_file_util.h"
39 #include "testing/gtest/include/gtest/gtest.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 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 net::URLRequestJob
* MaybeInterceptRedirect(
101 net::URLRequest
* request
,
102 net::NetworkDelegate
* network_delegate
,
103 const GURL
& location
) const override
{
107 net::URLRequestJob
* MaybeInterceptResponse(
108 net::URLRequest
* request
,
109 net::NetworkDelegate
* network_delegate
) const override
{
113 bool IsHandledProtocol(const std::string
& scheme
) const override
{
117 bool IsHandledURL(const GURL
& url
) const override
{ return true; }
119 bool IsSafeRedirectTarget(const GURL
& location
) const override
{
124 std::string storage_domain_
;
125 FileSystemContext
* file_system_context_
;
130 class FileSystemURLRequestJobTest
: public testing::Test
{
132 FileSystemURLRequestJobTest() : weak_factory_(this) {
135 void SetUp() override
{
136 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
138 // We use the main thread so that we can get the root path synchronously.
139 // TODO(adamk): Run this on the FILE thread we've created as well.
140 file_system_context_
=
141 CreateFileSystemContextForTesting(NULL
, temp_dir_
.path());
143 file_system_context_
->OpenFileSystem(
144 GURL("http://remote/"),
145 storage::kFileSystemTypeTemporary
,
146 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT
,
147 base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem
,
148 weak_factory_
.GetWeakPtr()));
149 base::RunLoop().RunUntilIdle();
152 void TearDown() override
{
153 // FileReader posts a task to close the file in destructor.
154 base::RunLoop().RunUntilIdle();
157 void SetUpAutoMountContext() {
158 base::FilePath mnt_point
= temp_dir_
.path().AppendASCII("auto_mount_dir");
159 ASSERT_TRUE(base::CreateDirectory(mnt_point
));
161 ScopedVector
<storage::FileSystemBackend
> additional_providers
;
162 additional_providers
.push_back(new TestFileSystemBackend(
163 base::MessageLoopProxy::current().get(), mnt_point
));
165 std::vector
<storage::URLRequestAutoMountHandler
> handlers
;
166 handlers
.push_back(base::Bind(&TestAutoMountForURLRequest
));
168 file_system_context_
= CreateFileSystemContextWithAutoMountersForTesting(
169 NULL
, additional_providers
.Pass(), handlers
, temp_dir_
.path());
171 ASSERT_EQ(static_cast<int>(sizeof(kTestFileData
)) - 1,
172 base::WriteFile(mnt_point
.AppendASCII("foo"), kTestFileData
,
173 sizeof(kTestFileData
) - 1));
176 void OnOpenFileSystem(const GURL
& root_url
,
177 const std::string
& name
,
178 base::File::Error result
) {
179 ASSERT_EQ(base::File::FILE_OK
, result
);
182 void TestRequestHelper(const GURL
& url
,
183 const net::HttpRequestHeaders
* headers
,
184 bool run_to_completion
,
185 FileSystemContext
* file_system_context
) {
186 delegate_
.reset(new net::TestDelegate());
187 // Make delegate_ exit the MessageLoop when the request is done.
188 delegate_
->set_quit_on_complete(true);
189 delegate_
->set_quit_on_redirect(true);
191 job_factory_
.reset(new FileSystemURLRequestJobFactory(
192 url
.GetOrigin().host(), file_system_context
));
193 empty_context_
.set_job_factory(job_factory_
.get());
195 request_
= empty_context_
.CreateRequest(
196 url
, net::DEFAULT_PRIORITY
, delegate_
.get(), NULL
);
198 request_
->SetExtraRequestHeaders(*headers
);
201 ASSERT_TRUE(request_
->is_pending()); // verify that we're starting async
202 if (run_to_completion
)
203 base::MessageLoop::current()->Run();
206 void TestRequest(const GURL
& url
) {
207 TestRequestHelper(url
, NULL
, true, file_system_context_
.get());
210 void TestRequestWithContext(const GURL
& url
,
211 FileSystemContext
* file_system_context
) {
212 TestRequestHelper(url
, NULL
, true, file_system_context
);
215 void TestRequestWithHeaders(const GURL
& url
,
216 const net::HttpRequestHeaders
* headers
) {
217 TestRequestHelper(url
, headers
, true, file_system_context_
.get());
220 void TestRequestNoRun(const GURL
& url
) {
221 TestRequestHelper(url
, NULL
, false, file_system_context_
.get());
224 void CreateDirectory(const base::StringPiece
& dir_name
) {
225 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
226 GURL("http://remote"),
227 storage::kFileSystemTypeTemporary
,
228 base::FilePath().AppendASCII(dir_name
));
231 AsyncFileTestHelper::CreateDirectory(file_system_context_
.get(), url
));
234 void WriteFile(const base::StringPiece
& file_name
,
235 const char* buf
, int buf_size
) {
236 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
237 GURL("http://remote"),
238 storage::kFileSystemTypeTemporary
,
239 base::FilePath().AppendASCII(file_name
));
240 ASSERT_EQ(base::File::FILE_OK
,
241 AsyncFileTestHelper::CreateFileWithData(
242 file_system_context_
.get(), url
, buf
, buf_size
));
245 GURL
CreateFileSystemURL(const std::string
& path
) {
246 return GURL(kFileSystemURLPrefix
+ path
);
249 // Put the message loop at the top, so that it's the last thing deleted.
250 base::MessageLoopForIO message_loop_
;
252 base::ScopedTempDir temp_dir_
;
253 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
255 net::URLRequestContext empty_context_
;
256 scoped_ptr
<FileSystemURLRequestJobFactory
> job_factory_
;
258 // NOTE: order matters, request must die before delegate
259 scoped_ptr
<net::TestDelegate
> delegate_
;
260 scoped_ptr
<net::URLRequest
> request_
;
262 base::WeakPtrFactory
<FileSystemURLRequestJobTest
> weak_factory_
;
267 TEST_F(FileSystemURLRequestJobTest
, FileTest
) {
268 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
269 TestRequest(CreateFileSystemURL("file1.dat"));
271 ASSERT_FALSE(request_
->is_pending());
272 EXPECT_EQ(1, delegate_
->response_started_count());
273 EXPECT_FALSE(delegate_
->received_data_before_response());
274 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
275 EXPECT_EQ(200, request_
->GetResponseCode());
276 std::string cache_control
;
277 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
278 EXPECT_EQ("no-cache", cache_control
);
281 TEST_F(FileSystemURLRequestJobTest
, FileTestFullSpecifiedRange
) {
282 const size_t buffer_size
= 4000;
283 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
284 FillBuffer(buffer
.get(), buffer_size
);
285 WriteFile("bigfile", buffer
.get(), buffer_size
);
287 const size_t first_byte_position
= 500;
288 const size_t last_byte_position
= buffer_size
- first_byte_position
;
289 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
290 buffer
.get() + last_byte_position
+ 1);
292 net::HttpRequestHeaders headers
;
294 net::HttpRequestHeaders::kRange
,
295 net::HttpByteRange::Bounded(
296 first_byte_position
, last_byte_position
).GetHeaderValue());
297 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
299 ASSERT_FALSE(request_
->is_pending());
300 EXPECT_EQ(1, delegate_
->response_started_count());
301 EXPECT_FALSE(delegate_
->received_data_before_response());
302 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
305 TEST_F(FileSystemURLRequestJobTest
, FileTestHalfSpecifiedRange
) {
306 const size_t buffer_size
= 4000;
307 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
308 FillBuffer(buffer
.get(), buffer_size
);
309 WriteFile("bigfile", buffer
.get(), buffer_size
);
311 const size_t first_byte_position
= 500;
312 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
313 buffer
.get() + buffer_size
);
315 net::HttpRequestHeaders headers
;
317 net::HttpRequestHeaders::kRange
,
318 net::HttpByteRange::RightUnbounded(first_byte_position
).GetHeaderValue());
319 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
320 ASSERT_FALSE(request_
->is_pending());
321 EXPECT_EQ(1, delegate_
->response_started_count());
322 EXPECT_FALSE(delegate_
->received_data_before_response());
323 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
324 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
327 TEST_F(FileSystemURLRequestJobTest
, FileTestMultipleRangesNotSupported
) {
328 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
329 net::HttpRequestHeaders headers
;
330 headers
.SetHeader(net::HttpRequestHeaders::kRange
,
331 "bytes=0-5,10-200,200-300");
332 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
333 EXPECT_TRUE(delegate_
->request_failed());
334 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
335 request_
->status().error());
338 TEST_F(FileSystemURLRequestJobTest
, RangeOutOfBounds
) {
339 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
340 net::HttpRequestHeaders headers
;
342 net::HttpRequestHeaders::kRange
,
343 net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
344 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
346 ASSERT_FALSE(request_
->is_pending());
347 EXPECT_TRUE(delegate_
->request_failed());
348 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
349 request_
->status().error());
352 TEST_F(FileSystemURLRequestJobTest
, FileDirRedirect
) {
353 CreateDirectory("dir");
354 TestRequest(CreateFileSystemURL("dir"));
356 EXPECT_EQ(1, delegate_
->received_redirect_count());
357 EXPECT_TRUE(request_
->status().is_success());
358 EXPECT_FALSE(delegate_
->request_failed());
360 // We've deferred the redirect; now cancel the request to avoid following it.
362 base::MessageLoop::current()->Run();
365 TEST_F(FileSystemURLRequestJobTest
, InvalidURL
) {
366 TestRequest(GURL("filesystem:/foo/bar/baz"));
367 ASSERT_FALSE(request_
->is_pending());
368 EXPECT_TRUE(delegate_
->request_failed());
369 EXPECT_EQ(net::ERR_INVALID_URL
, request_
->status().error());
372 TEST_F(FileSystemURLRequestJobTest
, NoSuchRoot
) {
373 TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
374 ASSERT_FALSE(request_
->is_pending());
375 EXPECT_TRUE(delegate_
->request_failed());
376 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
379 TEST_F(FileSystemURLRequestJobTest
, NoSuchFile
) {
380 TestRequest(CreateFileSystemURL("somefile"));
381 ASSERT_FALSE(request_
->is_pending());
382 EXPECT_TRUE(delegate_
->request_failed());
383 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
386 TEST_F(FileSystemURLRequestJobTest
, Cancel
) {
387 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
388 TestRequestNoRun(CreateFileSystemURL("file1.dat"));
390 // Run StartAsync() and only StartAsync().
391 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, request_
.release());
392 base::RunLoop().RunUntilIdle();
393 // If we get here, success! we didn't crash!
396 TEST_F(FileSystemURLRequestJobTest
, GetMimeType
) {
397 const char kFilename
[] = "hoge.html";
399 std::string mime_type_direct
;
400 base::FilePath::StringType extension
=
401 base::FilePath().AppendASCII(kFilename
).Extension();
402 if (!extension
.empty())
403 extension
= extension
.substr(1);
404 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
405 extension
, &mime_type_direct
));
407 TestRequest(CreateFileSystemURL(kFilename
));
409 std::string mime_type_from_job
;
410 request_
->GetMimeType(&mime_type_from_job
);
411 EXPECT_EQ(mime_type_direct
, mime_type_from_job
);
414 TEST_F(FileSystemURLRequestJobTest
, Incognito
) {
415 WriteFile("file", kTestFileData
, arraysize(kTestFileData
) - 1);
417 // Creates a new filesystem context for incognito mode.
418 scoped_refptr
<FileSystemContext
> file_system_context
=
419 CreateIncognitoFileSystemContextForTesting(NULL
, temp_dir_
.path());
421 // The request should return NOT_FOUND error if it's in incognito mode.
422 TestRequestWithContext(CreateFileSystemURL("file"),
423 file_system_context
.get());
424 ASSERT_FALSE(request_
->is_pending());
425 EXPECT_TRUE(delegate_
->request_failed());
426 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
428 // Make sure it returns success with regular (non-incognito) context.
429 TestRequest(CreateFileSystemURL("file"));
430 ASSERT_FALSE(request_
->is_pending());
431 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
432 EXPECT_EQ(200, request_
->GetResponseCode());
435 TEST_F(FileSystemURLRequestJobTest
, AutoMountFileTest
) {
436 SetUpAutoMountContext();
437 TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
439 ASSERT_FALSE(request_
->is_pending());
440 EXPECT_EQ(1, delegate_
->response_started_count());
441 EXPECT_FALSE(delegate_
->received_data_before_response());
442 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
443 EXPECT_EQ(200, request_
->GetResponseCode());
444 std::string cache_control
;
445 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
446 EXPECT_EQ("no-cache", cache_control
);
449 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
450 kValidExternalMountPoint
));
453 TEST_F(FileSystemURLRequestJobTest
, AutoMountInvalidRoot
) {
454 SetUpAutoMountContext();
455 TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
457 ASSERT_FALSE(request_
->is_pending());
458 EXPECT_TRUE(delegate_
->request_failed());
459 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
462 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
466 TEST_F(FileSystemURLRequestJobTest
, AutoMountNoHandler
) {
467 SetUpAutoMountContext();
468 TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo"));
470 ASSERT_FALSE(request_
->is_pending());
471 EXPECT_TRUE(delegate_
->request_failed());
472 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
475 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
476 kValidExternalMountPoint
));
480 } // namespace content