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 bool IsHandledProtocol(const std::string
& scheme
) const override
{
104 bool IsHandledURL(const GURL
& url
) const override
{ return true; }
106 bool IsSafeRedirectTarget(const GURL
& location
) const override
{
111 std::string storage_domain_
;
112 FileSystemContext
* file_system_context_
;
117 class FileSystemURLRequestJobTest
: public testing::Test
{
119 FileSystemURLRequestJobTest() : weak_factory_(this) {
122 virtual void SetUp() override
{
123 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
125 // We use the main thread so that we can get the root path synchronously.
126 // TODO(adamk): Run this on the FILE thread we've created as well.
127 file_system_context_
=
128 CreateFileSystemContextForTesting(NULL
, temp_dir_
.path());
130 file_system_context_
->OpenFileSystem(
131 GURL("http://remote/"),
132 storage::kFileSystemTypeTemporary
,
133 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT
,
134 base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem
,
135 weak_factory_
.GetWeakPtr()));
136 base::RunLoop().RunUntilIdle();
139 virtual void TearDown() override
{
140 // FileReader posts a task to close the file in destructor.
141 base::RunLoop().RunUntilIdle();
144 void SetUpAutoMountContext() {
145 base::FilePath mnt_point
= temp_dir_
.path().AppendASCII("auto_mount_dir");
146 ASSERT_TRUE(base::CreateDirectory(mnt_point
));
148 ScopedVector
<storage::FileSystemBackend
> additional_providers
;
149 additional_providers
.push_back(new TestFileSystemBackend(
150 base::MessageLoopProxy::current().get(), mnt_point
));
152 std::vector
<storage::URLRequestAutoMountHandler
> handlers
;
153 handlers
.push_back(base::Bind(&TestAutoMountForURLRequest
));
155 file_system_context_
= CreateFileSystemContextWithAutoMountersForTesting(
156 NULL
, additional_providers
.Pass(), handlers
, temp_dir_
.path());
158 ASSERT_EQ(static_cast<int>(sizeof(kTestFileData
)) - 1,
159 base::WriteFile(mnt_point
.AppendASCII("foo"), kTestFileData
,
160 sizeof(kTestFileData
) - 1));
163 void OnOpenFileSystem(const GURL
& root_url
,
164 const std::string
& name
,
165 base::File::Error result
) {
166 ASSERT_EQ(base::File::FILE_OK
, result
);
169 void TestRequestHelper(const GURL
& url
,
170 const net::HttpRequestHeaders
* headers
,
171 bool run_to_completion
,
172 FileSystemContext
* file_system_context
) {
173 delegate_
.reset(new net::TestDelegate());
174 // Make delegate_ exit the MessageLoop when the request is done.
175 delegate_
->set_quit_on_complete(true);
176 delegate_
->set_quit_on_redirect(true);
178 job_factory_
.reset(new FileSystemURLRequestJobFactory(
179 url
.GetOrigin().host(), file_system_context
));
180 empty_context_
.set_job_factory(job_factory_
.get());
182 request_
= empty_context_
.CreateRequest(
183 url
, net::DEFAULT_PRIORITY
, delegate_
.get(), NULL
);
185 request_
->SetExtraRequestHeaders(*headers
);
188 ASSERT_TRUE(request_
->is_pending()); // verify that we're starting async
189 if (run_to_completion
)
190 base::MessageLoop::current()->Run();
193 void TestRequest(const GURL
& url
) {
194 TestRequestHelper(url
, NULL
, true, file_system_context_
.get());
197 void TestRequestWithContext(const GURL
& url
,
198 FileSystemContext
* file_system_context
) {
199 TestRequestHelper(url
, NULL
, true, file_system_context
);
202 void TestRequestWithHeaders(const GURL
& url
,
203 const net::HttpRequestHeaders
* headers
) {
204 TestRequestHelper(url
, headers
, true, file_system_context_
.get());
207 void TestRequestNoRun(const GURL
& url
) {
208 TestRequestHelper(url
, NULL
, false, file_system_context_
.get());
211 void CreateDirectory(const base::StringPiece
& dir_name
) {
212 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
213 GURL("http://remote"),
214 storage::kFileSystemTypeTemporary
,
215 base::FilePath().AppendASCII(dir_name
));
218 AsyncFileTestHelper::CreateDirectory(file_system_context_
.get(), url
));
221 void WriteFile(const base::StringPiece
& file_name
,
222 const char* buf
, int buf_size
) {
223 FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
224 GURL("http://remote"),
225 storage::kFileSystemTypeTemporary
,
226 base::FilePath().AppendASCII(file_name
));
227 ASSERT_EQ(base::File::FILE_OK
,
228 AsyncFileTestHelper::CreateFileWithData(
229 file_system_context_
.get(), url
, buf
, buf_size
));
232 GURL
CreateFileSystemURL(const std::string
& path
) {
233 return GURL(kFileSystemURLPrefix
+ path
);
236 // Put the message loop at the top, so that it's the last thing deleted.
237 base::MessageLoopForIO message_loop_
;
239 base::ScopedTempDir temp_dir_
;
240 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
241 base::WeakPtrFactory
<FileSystemURLRequestJobTest
> weak_factory_
;
243 net::URLRequestContext empty_context_
;
244 scoped_ptr
<FileSystemURLRequestJobFactory
> job_factory_
;
246 // NOTE: order matters, request must die before delegate
247 scoped_ptr
<net::TestDelegate
> delegate_
;
248 scoped_ptr
<net::URLRequest
> request_
;
253 TEST_F(FileSystemURLRequestJobTest
, FileTest
) {
254 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
255 TestRequest(CreateFileSystemURL("file1.dat"));
257 ASSERT_FALSE(request_
->is_pending());
258 EXPECT_EQ(1, delegate_
->response_started_count());
259 EXPECT_FALSE(delegate_
->received_data_before_response());
260 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
261 EXPECT_EQ(200, request_
->GetResponseCode());
262 std::string cache_control
;
263 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
264 EXPECT_EQ("no-cache", cache_control
);
267 TEST_F(FileSystemURLRequestJobTest
, FileTestFullSpecifiedRange
) {
268 const size_t buffer_size
= 4000;
269 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
270 FillBuffer(buffer
.get(), buffer_size
);
271 WriteFile("bigfile", buffer
.get(), buffer_size
);
273 const size_t first_byte_position
= 500;
274 const size_t last_byte_position
= buffer_size
- first_byte_position
;
275 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
276 buffer
.get() + last_byte_position
+ 1);
278 net::HttpRequestHeaders headers
;
280 net::HttpRequestHeaders::kRange
,
281 net::HttpByteRange::Bounded(
282 first_byte_position
, last_byte_position
).GetHeaderValue());
283 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
285 ASSERT_FALSE(request_
->is_pending());
286 EXPECT_EQ(1, delegate_
->response_started_count());
287 EXPECT_FALSE(delegate_
->received_data_before_response());
288 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
291 TEST_F(FileSystemURLRequestJobTest
, FileTestHalfSpecifiedRange
) {
292 const size_t buffer_size
= 4000;
293 scoped_ptr
<char[]> buffer(new char[buffer_size
]);
294 FillBuffer(buffer
.get(), buffer_size
);
295 WriteFile("bigfile", buffer
.get(), buffer_size
);
297 const size_t first_byte_position
= 500;
298 std::string
partial_buffer_string(buffer
.get() + first_byte_position
,
299 buffer
.get() + buffer_size
);
301 net::HttpRequestHeaders headers
;
303 net::HttpRequestHeaders::kRange
,
304 net::HttpByteRange::RightUnbounded(first_byte_position
).GetHeaderValue());
305 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers
);
306 ASSERT_FALSE(request_
->is_pending());
307 EXPECT_EQ(1, delegate_
->response_started_count());
308 EXPECT_FALSE(delegate_
->received_data_before_response());
309 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
310 EXPECT_TRUE(partial_buffer_string
== delegate_
->data_received());
313 TEST_F(FileSystemURLRequestJobTest
, FileTestMultipleRangesNotSupported
) {
314 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
315 net::HttpRequestHeaders headers
;
316 headers
.SetHeader(net::HttpRequestHeaders::kRange
,
317 "bytes=0-5,10-200,200-300");
318 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
319 EXPECT_TRUE(delegate_
->request_failed());
320 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
321 request_
->status().error());
324 TEST_F(FileSystemURLRequestJobTest
, RangeOutOfBounds
) {
325 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
326 net::HttpRequestHeaders headers
;
328 net::HttpRequestHeaders::kRange
,
329 net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
330 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers
);
332 ASSERT_FALSE(request_
->is_pending());
333 EXPECT_TRUE(delegate_
->request_failed());
334 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE
,
335 request_
->status().error());
338 TEST_F(FileSystemURLRequestJobTest
, FileDirRedirect
) {
339 CreateDirectory("dir");
340 TestRequest(CreateFileSystemURL("dir"));
342 EXPECT_EQ(1, delegate_
->received_redirect_count());
343 EXPECT_TRUE(request_
->status().is_success());
344 EXPECT_FALSE(delegate_
->request_failed());
346 // We've deferred the redirect; now cancel the request to avoid following it.
348 base::MessageLoop::current()->Run();
351 TEST_F(FileSystemURLRequestJobTest
, InvalidURL
) {
352 TestRequest(GURL("filesystem:/foo/bar/baz"));
353 ASSERT_FALSE(request_
->is_pending());
354 EXPECT_TRUE(delegate_
->request_failed());
355 EXPECT_EQ(net::ERR_INVALID_URL
, request_
->status().error());
358 TEST_F(FileSystemURLRequestJobTest
, NoSuchRoot
) {
359 TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
360 ASSERT_FALSE(request_
->is_pending());
361 EXPECT_TRUE(delegate_
->request_failed());
362 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
365 TEST_F(FileSystemURLRequestJobTest
, NoSuchFile
) {
366 TestRequest(CreateFileSystemURL("somefile"));
367 ASSERT_FALSE(request_
->is_pending());
368 EXPECT_TRUE(delegate_
->request_failed());
369 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
372 TEST_F(FileSystemURLRequestJobTest
, Cancel
) {
373 WriteFile("file1.dat", kTestFileData
, arraysize(kTestFileData
) - 1);
374 TestRequestNoRun(CreateFileSystemURL("file1.dat"));
376 // Run StartAsync() and only StartAsync().
377 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, request_
.release());
378 base::RunLoop().RunUntilIdle();
379 // If we get here, success! we didn't crash!
382 TEST_F(FileSystemURLRequestJobTest
, GetMimeType
) {
383 const char kFilename
[] = "hoge.html";
385 std::string mime_type_direct
;
386 base::FilePath::StringType extension
=
387 base::FilePath().AppendASCII(kFilename
).Extension();
388 if (!extension
.empty())
389 extension
= extension
.substr(1);
390 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
391 extension
, &mime_type_direct
));
393 TestRequest(CreateFileSystemURL(kFilename
));
395 std::string mime_type_from_job
;
396 request_
->GetMimeType(&mime_type_from_job
);
397 EXPECT_EQ(mime_type_direct
, mime_type_from_job
);
400 TEST_F(FileSystemURLRequestJobTest
, Incognito
) {
401 WriteFile("file", kTestFileData
, arraysize(kTestFileData
) - 1);
403 // Creates a new filesystem context for incognito mode.
404 scoped_refptr
<FileSystemContext
> file_system_context
=
405 CreateIncognitoFileSystemContextForTesting(NULL
, temp_dir_
.path());
407 // The request should return NOT_FOUND error if it's in incognito mode.
408 TestRequestWithContext(CreateFileSystemURL("file"),
409 file_system_context
.get());
410 ASSERT_FALSE(request_
->is_pending());
411 EXPECT_TRUE(delegate_
->request_failed());
412 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
414 // Make sure it returns success with regular (non-incognito) context.
415 TestRequest(CreateFileSystemURL("file"));
416 ASSERT_FALSE(request_
->is_pending());
417 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
418 EXPECT_EQ(200, request_
->GetResponseCode());
421 TEST_F(FileSystemURLRequestJobTest
, AutoMountFileTest
) {
422 SetUpAutoMountContext();
423 TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
425 ASSERT_FALSE(request_
->is_pending());
426 EXPECT_EQ(1, delegate_
->response_started_count());
427 EXPECT_FALSE(delegate_
->received_data_before_response());
428 EXPECT_EQ(kTestFileData
, delegate_
->data_received());
429 EXPECT_EQ(200, request_
->GetResponseCode());
430 std::string cache_control
;
431 request_
->GetResponseHeaderByName("cache-control", &cache_control
);
432 EXPECT_EQ("no-cache", cache_control
);
435 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
436 kValidExternalMountPoint
));
439 TEST_F(FileSystemURLRequestJobTest
, AutoMountInvalidRoot
) {
440 SetUpAutoMountContext();
441 TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
443 ASSERT_FALSE(request_
->is_pending());
444 EXPECT_TRUE(delegate_
->request_failed());
445 EXPECT_EQ(net::ERR_FILE_NOT_FOUND
, request_
->status().error());
448 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
452 TEST_F(FileSystemURLRequestJobTest
, AutoMountNoHandler
) {
453 SetUpAutoMountContext();
454 TestRequest(GURL("filesystem:http://noauto/external/mnt_name/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(
462 kValidExternalMountPoint
));
466 } // namespace content