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.
4
5#include "storage/browser/fileapi/file_system_url_request_job.h"
6
7#include <string>
8
9#include "base/bind.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"
40
41using content::AsyncFileTestHelper;
42using storage::FileSystemContext;
43using storage::FileSystemURL;
44using storage::FileSystemURLRequestJob;
45
46namespace content {
47namespace {
48
49// We always use the TEMPORARY FileSystem in this test.
50const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
51const char kTestFileData[] = "0123456789";
52
53void FillBuffer(char* buffer, size_t len) {
54  base::RandBytes(buffer, len);
55}
56
57const char kValidExternalMountPoint[] = "mnt_name";
58
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".
61bool 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")
67    return false;
68  std::vector<base::FilePath::StringType> components;
69  filesystem_url.path().GetComponents(&components);
70  std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe();
71
72  if (mount_point == kValidExternalMountPoint) {
73    storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
74        kValidExternalMountPoint,
75        storage::kFileSystemTypeTest,
76        storage::FileSystemMountOption(),
77        base::FilePath());
78    callback.Run(base::File::FILE_OK);
79  } else {
80    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
81  }
82  return true;
83}
84
85class FileSystemURLRequestJobFactory : public net::URLRequestJobFactory {
86 public:
87  FileSystemURLRequestJobFactory(const std::string& storage_domain,
88                                 FileSystemContext* context)
89      : storage_domain_(storage_domain), file_system_context_(context) {
90  }
91
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_);
98  }
99
100  virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE {
101    return true;
102  }
103
104  virtual bool IsHandledURL(const GURL& url) const OVERRIDE {
105    return true;
106  }
107
108  virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE {
109    return false;
110  }
111
112 private:
113  std::string storage_domain_;
114  FileSystemContext* file_system_context_;
115};
116
117}  // namespace
118
119class FileSystemURLRequestJobTest : public testing::Test {
120 protected:
121  FileSystemURLRequestJobTest() : weak_factory_(this) {
122  }
123
124  virtual void SetUp() OVERRIDE {
125    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
126
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());
131
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();
139  }
140
141  virtual void TearDown() OVERRIDE {
142    // FileReader posts a task to close the file in destructor.
143    base::RunLoop().RunUntilIdle();
144  }
145
146  void SetUpAutoMountContext() {
147    base::FilePath mnt_point = temp_dir_.path().AppendASCII("auto_mount_dir");
148    ASSERT_TRUE(base::CreateDirectory(mnt_point));
149
150    ScopedVector<storage::FileSystemBackend> additional_providers;
151    additional_providers.push_back(new TestFileSystemBackend(
152        base::MessageLoopProxy::current().get(), mnt_point));
153
154    std::vector<storage::URLRequestAutoMountHandler> handlers;
155    handlers.push_back(base::Bind(&TestAutoMountForURLRequest));
156
157    file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting(
158        NULL, additional_providers.Pass(), handlers, temp_dir_.path());
159
160    ASSERT_EQ(static_cast<int>(sizeof(kTestFileData)) - 1,
161              base::WriteFile(mnt_point.AppendASCII("foo"), kTestFileData,
162                              sizeof(kTestFileData) - 1));
163  }
164
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);
169  }
170
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);
179
180    job_factory_.reset(new FileSystemURLRequestJobFactory(
181        url.GetOrigin().host(), file_system_context));
182    empty_context_.set_job_factory(job_factory_.get());
183
184    request_ = empty_context_.CreateRequest(
185        url, net::DEFAULT_PRIORITY, delegate_.get(), NULL);
186    if (headers)
187      request_->SetExtraRequestHeaders(*headers);
188
189    request_->Start();
190    ASSERT_TRUE(request_->is_pending());  // verify that we're starting async
191    if (run_to_completion)
192      base::MessageLoop::current()->Run();
193  }
194
195  void TestRequest(const GURL& url) {
196    TestRequestHelper(url, NULL, true, file_system_context_.get());
197  }
198
199  void TestRequestWithContext(const GURL& url,
200                              FileSystemContext* file_system_context) {
201    TestRequestHelper(url, NULL, true, file_system_context);
202  }
203
204  void TestRequestWithHeaders(const GURL& url,
205                              const net::HttpRequestHeaders* headers) {
206    TestRequestHelper(url, headers, true, file_system_context_.get());
207  }
208
209  void TestRequestNoRun(const GURL& url) {
210    TestRequestHelper(url, NULL, false, file_system_context_.get());
211  }
212
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));
218    ASSERT_EQ(
219        base::File::FILE_OK,
220        AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), url));
221  }
222
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));
232  }
233
234  GURL CreateFileSystemURL(const std::string& path) {
235    return GURL(kFileSystemURLPrefix + path);
236  }
237
238  // Put the message loop at the top, so that it's the last thing deleted.
239  base::MessageLoopForIO message_loop_;
240
241  base::ScopedTempDir temp_dir_;
242  scoped_refptr<storage::FileSystemContext> file_system_context_;
243  base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_;
244
245  net::URLRequestContext empty_context_;
246  scoped_ptr<FileSystemURLRequestJobFactory> job_factory_;
247
248  // NOTE: order matters, request must die before delegate
249  scoped_ptr<net::TestDelegate> delegate_;
250  scoped_ptr<net::URLRequest> request_;
251};
252
253namespace {
254
255TEST_F(FileSystemURLRequestJobTest, FileTest) {
256  WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
257  TestRequest(CreateFileSystemURL("file1.dat"));
258
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);
267}
268
269TEST_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);
274
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);
279
280  net::HttpRequestHeaders headers;
281  headers.SetHeader(
282      net::HttpRequestHeaders::kRange,
283      net::HttpByteRange::Bounded(
284          first_byte_position, last_byte_position).GetHeaderValue());
285  TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
286
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());
291}
292
293TEST_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);
298
299  const size_t first_byte_position = 500;
300  std::string partial_buffer_string(buffer.get() + first_byte_position,
301                                    buffer.get() + buffer_size);
302
303  net::HttpRequestHeaders headers;
304  headers.SetHeader(
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());
313}
314
315TEST_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());
324}
325
326TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) {
327  WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
328  net::HttpRequestHeaders headers;
329  headers.SetHeader(
330      net::HttpRequestHeaders::kRange,
331      net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
332  TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
333
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());
338}
339
340TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) {
341  CreateDirectory("dir");
342  TestRequest(CreateFileSystemURL("dir"));
343
344  EXPECT_EQ(1, delegate_->received_redirect_count());
345  EXPECT_TRUE(request_->status().is_success());
346  EXPECT_FALSE(delegate_->request_failed());
347
348  // We've deferred the redirect; now cancel the request to avoid following it.
349  request_->Cancel();
350  base::MessageLoop::current()->Run();
351}
352
353TEST_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());
358}
359
360TEST_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());
365}
366
367TEST_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());
372}
373
374TEST_F(FileSystemURLRequestJobTest, Cancel) {
375  WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
376  TestRequestNoRun(CreateFileSystemURL("file1.dat"));
377
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!
382}
383
384TEST_F(FileSystemURLRequestJobTest, GetMimeType) {
385  const char kFilename[] = "hoge.html";
386
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));
394
395  TestRequest(CreateFileSystemURL(kFilename));
396
397  std::string mime_type_from_job;
398  request_->GetMimeType(&mime_type_from_job);
399  EXPECT_EQ(mime_type_direct, mime_type_from_job);
400}
401
402TEST_F(FileSystemURLRequestJobTest, Incognito) {
403  WriteFile("file", kTestFileData, arraysize(kTestFileData) - 1);
404
405  // Creates a new filesystem context for incognito mode.
406  scoped_refptr<FileSystemContext> file_system_context =
407      CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path());
408
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());
415
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());
421}
422
423TEST_F(FileSystemURLRequestJobTest, AutoMountFileTest) {
424  SetUpAutoMountContext();
425  TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo"));
426
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);
435
436  ASSERT_TRUE(
437      storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
438          kValidExternalMountPoint));
439}
440
441TEST_F(FileSystemURLRequestJobTest, AutoMountInvalidRoot) {
442  SetUpAutoMountContext();
443  TestRequest(GURL("filesystem:http://automount/external/invalid/foo"));
444
445  ASSERT_FALSE(request_->is_pending());
446  EXPECT_TRUE(delegate_->request_failed());
447  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
448
449  ASSERT_FALSE(
450      storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
451          "invalid"));
452}
453
454TEST_F(FileSystemURLRequestJobTest, AutoMountNoHandler) {
455  SetUpAutoMountContext();
456  TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo"));
457
458  ASSERT_FALSE(request_->is_pending());
459  EXPECT_TRUE(delegate_->request_failed());
460  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
461
462  ASSERT_FALSE(
463      storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
464          kValidExternalMountPoint));
465}
466
467}  // namespace
468}  // namespace content
469