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_
;
254 base::WeakPtrFactory
<FileSystemURLRequestJobTest
> weak_factory_
;
256 net::URLRequestContext empty_context_
;
257 scoped_ptr
<FileSystemURLRequestJobFactory
> job_factory_
;
259 // NOTE: order matters, request must die before delegate
260 scoped_ptr
<net::TestDelegate
> delegate_
;
261 scoped_ptr
<net::URLRequest
> request_
;
266 TEST_F(FileSystemURLRequestJobTest
, FileTest
) {
267 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
268 TestRequest(CreateFileSystemURL("file1.dat"));
270 ASSERT_FALSE(request_
->is_pending());
271 EXPECT_EQ(1, delegate_
->response_started_count());
272 EXPECT_FALSE(delegate_
->received_data_before_response());
273 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
274 EXPECT_EQ(200, request_
->GetResponseCode());
275 std::string cache_control
;
276 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
277 EXPECT_EQ("no-cache", cache_control
);
280 TEST_F(FileSystemURLRequestJobTest
, FileTestFullSpecifiedRange
) {
281 const size_t buffer_size
= 4000;
282 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
283 FillBuffer(buffer
.get(), buffer_size
);
284 WriteFile("bigfile", buffer
.get(), buffer_size
);
286 const size_t first_byte_position
= 500;
287 const size_t last_byte_position
= buffer_size
- first_byte_position
;
288 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
289 buffer
.get() + last_byte_position
+ 1);
291 net::HttpRequestHeaders headers
;
293 net::HttpRequestHeaders::kRange
,
294 net::HttpByteRange::Bounded(
295 first_byte_position
, last_byte_position
).GetHeaderValue());
296 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
298 ASSERT_FALSE(request_
->is_pending());
299 EXPECT_EQ(1, delegate_
->response_started_count());
300 EXPECT_FALSE(delegate_
->received_data_before_response());
301 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
304 TEST_F(FileSystemURLRequestJobTest
, FileTestHalfSpecifiedRange
) {
305 const size_t buffer_size
= 4000;
306 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
307 FillBuffer(buffer
.get(), buffer_size
);
308 WriteFile("bigfile", buffer
.get(), buffer_size
);
310 const size_t first_byte_position
= 500;
311 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
312 buffer
.get() + buffer_size
);
314 net::HttpRequestHeaders headers
;
316 net::HttpRequestHeaders::kRange
,
317 net::HttpByteRange::RightUnbounded(first_byte_position
).GetHeaderValue());
318 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
319 ASSERT_FALSE(request_
->is_pending());
320 EXPECT_EQ(1, delegate_
->response_started_count());
321 EXPECT_FALSE(delegate_
->received_data_before_response());
322 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
323 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
326 TEST_F(FileSystemURLRequestJobTest
, FileTestMultipleRangesNotSupported
) {
327 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
328 net::HttpRequestHeaders headers
;
329 headers
.SetHeader(net::HttpRequestHeaders::kRange
,
330 "bytes=0-5,10-200,200-300");
331 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
332 EXPECT_TRUE(delegate_
->request_failed());
333 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
334 request_
->status().error());
337 TEST_F(FileSystemURLRequestJobTest
, RangeOutOfBounds
) {
338 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
339 net::HttpRequestHeaders headers
;
341 net::HttpRequestHeaders::kRange
,
342 net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
343 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
345 ASSERT_FALSE(request_
->is_pending());
346 EXPECT_TRUE(delegate_
->request_failed());
347 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
348 request_
->status().error());
351 TEST_F(FileSystemURLRequestJobTest
, FileDirRedirect
) {
352 CreateDirectory("dir");
353 TestRequest(CreateFileSystemURL("dir"));
355 EXPECT_EQ(1, delegate_
->received_redirect_count());
356 EXPECT_TRUE(request_
->status().is_success());
357 EXPECT_FALSE(delegate_
->request_failed());
359 // We've deferred the redirect; now cancel the request to avoid following it.
361 base::MessageLoop::current()->Run();
364 TEST_F(FileSystemURLRequestJobTest
, InvalidURL
) {
365 TestRequest(GURL("filesystem:/foo/bar/baz"));
366 ASSERT_FALSE(request_
->is_pending());
367 EXPECT_TRUE(delegate_
->request_failed());
368 EXPECT_EQ(net::ERR_INVALID_URL
, request_
->status().error());
371 TEST_F(FileSystemURLRequestJobTest
, NoSuchRoot
) {
372 TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
373 ASSERT_FALSE(request_
->is_pending());
374 EXPECT_TRUE(delegate_
->request_failed());
375 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
378 TEST_F(FileSystemURLRequestJobTest
, NoSuchFile
) {
379 TestRequest(CreateFileSystemURL("somefile"));
380 ASSERT_FALSE(request_
->is_pending());
381 EXPECT_TRUE(delegate_
->request_failed());
382 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
385 TEST_F(FileSystemURLRequestJobTest
, Cancel
) {
386 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
387 TestRequestNoRun(CreateFileSystemURL("file1.dat"));
389 // Run StartAsync() and only StartAsync().
390 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, request_
.release());
391 base::RunLoop().RunUntilIdle();
392 // If we get here, success! we didn't crash!
395 TEST_F(FileSystemURLRequestJobTest
, GetMimeType
) {
396 const char kFilename
[] = "hoge.html";
398 std::string mime_type_direct
;
399 base::FilePath::StringType extension
=
400 base::FilePath().AppendASCII(kFilename
).Extension();
401 if (!extension
.empty())
402 extension
= extension
.substr(1);
403 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
404 extension
, &mime_type_direct
));
406 TestRequest(CreateFileSystemURL(kFilename
));
408 std::string mime_type_from_job
;
409 request_
->GetMimeType(&mime_type_from_job
);
410 EXPECT_EQ(mime_type_direct
, mime_type_from_job
);
413 TEST_F(FileSystemURLRequestJobTest
, Incognito
) {
414 WriteFile("file", kTestFileData
, arraysize(kTestFileData
) - 1);
416 // Creates a new filesystem context for incognito mode.
417 scoped_refptr
<FileSystemContext
> file_system_context
=
418 CreateIncognitoFileSystemContextForTesting(NULL
, temp_dir_
.path());
420 // The request should return NOT_FOUND error if it's in incognito mode.
421 TestRequestWithContext(CreateFileSystemURL("file"),
422 file_system_context
.get());
423 ASSERT_FALSE(request_
->is_pending());
424 EXPECT_TRUE(delegate_
->request_failed());
425 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
427 // Make sure it returns success with regular (non-incognito) context.
428 TestRequest(CreateFileSystemURL("file"));
429 ASSERT_FALSE(request_
->is_pending());
430 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
431 EXPECT_EQ(200, request_
->GetResponseCode());
434 TEST_F(FileSystemURLRequestJobTest
, AutoMountFileTest
) {
435 SetUpAutoMountContext();
436 TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
438 ASSERT_FALSE(request_
->is_pending());
439 EXPECT_EQ(1, delegate_
->response_started_count());
440 EXPECT_FALSE(delegate_
->received_data_before_response());
441 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
442 EXPECT_EQ(200, request_
->GetResponseCode());
443 std::string cache_control
;
444 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
445 EXPECT_EQ("no-cache", cache_control
);
448 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
449 kValidExternalMountPoint
));
452 TEST_F(FileSystemURLRequestJobTest
, AutoMountInvalidRoot
) {
453 SetUpAutoMountContext();
454 TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
456 ASSERT_FALSE(request_
->is_pending());
457 EXPECT_TRUE(delegate_
->request_failed());
458 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
461 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
465 TEST_F(FileSystemURLRequestJobTest
, AutoMountNoHandler
) {
466 SetUpAutoMountContext();
467 TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo"));
469 ASSERT_FALSE(request_
->is_pending());
470 EXPECT_TRUE(delegate_
->request_failed());
471 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
474 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
475 kValidExternalMountPoint
));
479 } // namespace content