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 "webkit/browser/fileapi/file_system_dir_url_request_job.h"
6
7#include <string>
8
9#include "base/file_util.h"
10#include "base/files/file_path.h"
11#include "base/files/scoped_temp_dir.h"
12#include "base/format_macros.h"
13#include "base/memory/scoped_vector.h"
14#include "base/memory/weak_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "base/run_loop.h"
17#include "base/strings/string_piece.h"
18#include "base/strings/utf_string_conversions.h"
19#include "content/public/test/mock_special_storage_policy.h"
20#include "content/public/test/test_file_system_backend.h"
21#include "content/public/test/test_file_system_context.h"
22#include "net/base/net_errors.h"
23#include "net/base/net_util.h"
24#include "net/base/request_priority.h"
25#include "net/http/http_request_headers.h"
26#include "net/url_request/url_request.h"
27#include "net/url_request/url_request_context.h"
28#include "net/url_request/url_request_test_util.h"
29#include "testing/gtest/include/gtest/gtest.h"
30#include "third_party/icu/source/i18n/unicode/regex.h"
31#include "webkit/browser/fileapi/external_mount_points.h"
32#include "webkit/browser/fileapi/file_system_context.h"
33#include "webkit/browser/fileapi/file_system_file_util.h"
34#include "webkit/browser/fileapi/file_system_operation_context.h"
35#include "webkit/browser/fileapi/file_system_url.h"
36
37using fileapi::FileSystemContext;
38using fileapi::FileSystemOperationContext;
39using fileapi::FileSystemURL;
40
41namespace content {
42namespace {
43
44// We always use the TEMPORARY FileSystem in this test.
45const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
46
47const char kValidExternalMountPoint[] = "mnt_name";
48
49// An auto mounter that will try to mount anything for |storage_domain| =
50// "automount", but will only succeed for the mount point "mnt_name".
51bool TestAutoMountForURLRequest(
52    const net::URLRequest* /*url_request*/,
53    const fileapi::FileSystemURL& filesystem_url,
54    const std::string& storage_domain,
55    const base::Callback<void(base::File::Error result)>& callback) {
56  if (storage_domain != "automount")
57    return false;
58
59  std::vector<base::FilePath::StringType> components;
60  filesystem_url.path().GetComponents(&components);
61  std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe();
62
63  if (mount_point == kValidExternalMountPoint) {
64    fileapi::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
65        kValidExternalMountPoint, fileapi::kFileSystemTypeTest,
66        fileapi::FileSystemMountOption(), base::FilePath());
67    callback.Run(base::File::FILE_OK);
68  } else {
69    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
70  }
71  return true;
72}
73
74class FileSystemDirURLRequestJobFactory : public net::URLRequestJobFactory {
75 public:
76  FileSystemDirURLRequestJobFactory(const std::string& storage_domain,
77                                    FileSystemContext* context)
78      : storage_domain_(storage_domain), file_system_context_(context) {
79  }
80
81  virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
82      const std::string& scheme,
83      net::URLRequest* request,
84      net::NetworkDelegate* network_delegate) const OVERRIDE {
85    return new fileapi::FileSystemDirURLRequestJob(
86        request, network_delegate, storage_domain_, file_system_context_);
87  }
88
89  virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE {
90    return true;
91  }
92
93  virtual bool IsHandledURL(const GURL& url) const OVERRIDE {
94    return true;
95  }
96
97  virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE {
98    return false;
99  }
100
101 private:
102  std::string storage_domain_;
103  FileSystemContext* file_system_context_;
104};
105
106
107}  // namespace
108
109class FileSystemDirURLRequestJobTest : public testing::Test {
110 protected:
111  FileSystemDirURLRequestJobTest()
112    : weak_factory_(this) {
113  }
114
115  virtual void SetUp() OVERRIDE {
116    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
117
118    special_storage_policy_ = new MockSpecialStoragePolicy;
119    file_system_context_ = CreateFileSystemContextForTesting(
120        NULL, temp_dir_.path());
121
122    file_system_context_->OpenFileSystem(
123        GURL("http://remote/"), fileapi::kFileSystemTypeTemporary,
124        fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
125        base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem,
126                   weak_factory_.GetWeakPtr()));
127    base::RunLoop().RunUntilIdle();
128  }
129
130  virtual void TearDown() OVERRIDE {
131    // NOTE: order matters, request must die before delegate
132    request_.reset(NULL);
133    delegate_.reset(NULL);
134  }
135
136  void SetUpAutoMountContext(base::FilePath* mnt_point) {
137    *mnt_point = temp_dir_.path().AppendASCII("auto_mount_dir");
138    ASSERT_TRUE(base::CreateDirectory(*mnt_point));
139
140    ScopedVector<fileapi::FileSystemBackend> additional_providers;
141    additional_providers.push_back(new TestFileSystemBackend(
142        base::MessageLoopProxy::current().get(), *mnt_point));
143
144    std::vector<fileapi::URLRequestAutoMountHandler> handlers;
145    handlers.push_back(base::Bind(&TestAutoMountForURLRequest));
146
147    file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting(
148        NULL, additional_providers.Pass(), handlers, temp_dir_.path());
149  }
150
151  void OnOpenFileSystem(const GURL& root_url,
152                        const std::string& name,
153                        base::File::Error result) {
154    ASSERT_EQ(base::File::FILE_OK, result);
155  }
156
157  void TestRequestHelper(const GURL& url, bool run_to_completion,
158                         FileSystemContext* file_system_context) {
159    delegate_.reset(new net::TestDelegate());
160    delegate_->set_quit_on_redirect(true);
161    job_factory_.reset(new FileSystemDirURLRequestJobFactory(
162        url.GetOrigin().host(), file_system_context));
163    empty_context_.set_job_factory(job_factory_.get());
164
165    request_ = empty_context_.CreateRequest(
166        url, net::DEFAULT_PRIORITY, delegate_.get(), NULL);
167    request_->Start();
168    ASSERT_TRUE(request_->is_pending());  // verify that we're starting async
169    if (run_to_completion)
170      base::MessageLoop::current()->Run();
171  }
172
173  void TestRequest(const GURL& url) {
174    TestRequestHelper(url, true, file_system_context_.get());
175  }
176
177  void TestRequestWithContext(const GURL& url,
178                              FileSystemContext* file_system_context) {
179    TestRequestHelper(url, true, file_system_context);
180  }
181
182  void TestRequestNoRun(const GURL& url) {
183    TestRequestHelper(url, false, file_system_context_.get());
184  }
185
186  FileSystemURL CreateURL(const base::FilePath& file_path) {
187    return file_system_context_->CreateCrackedFileSystemURL(
188        GURL("http://remote"),
189        fileapi::kFileSystemTypeTemporary,
190        file_path);
191  }
192
193  FileSystemOperationContext* NewOperationContext() {
194    FileSystemOperationContext* context(
195        new FileSystemOperationContext(file_system_context_.get()));
196    context->set_allowed_bytes_growth(1024);
197    return context;
198  }
199
200  void CreateDirectory(const base::StringPiece& dir_name) {
201    base::FilePath path = base::FilePath().AppendASCII(dir_name);
202    scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
203    ASSERT_EQ(base::File::FILE_OK, file_util()->CreateDirectory(
204        context.get(),
205        CreateURL(path),
206        false /* exclusive */,
207        false /* recursive */));
208  }
209
210  void EnsureFileExists(const base::StringPiece file_name) {
211    base::FilePath path = base::FilePath().AppendASCII(file_name);
212    scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
213    ASSERT_EQ(base::File::FILE_OK, file_util()->EnsureFileExists(
214        context.get(), CreateURL(path), NULL));
215  }
216
217  void TruncateFile(const base::StringPiece file_name, int64 length) {
218    base::FilePath path = base::FilePath().AppendASCII(file_name);
219    scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
220    ASSERT_EQ(base::File::FILE_OK, file_util()->Truncate(
221        context.get(), CreateURL(path), length));
222  }
223
224  base::File::Error GetFileInfo(const base::FilePath& path,
225                                base::File::Info* file_info,
226                                base::FilePath* platform_file_path) {
227    scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
228    return file_util()->GetFileInfo(context.get(),
229                                    CreateURL(path),
230                                    file_info, platform_file_path);
231  }
232
233  // If |size| is negative, the reported size is ignored.
234  void VerifyListingEntry(const std::string& entry_line,
235                          const std::string& name,
236                          const std::string& url,
237                          bool is_directory,
238                          int64 size) {
239#define STR "([^\"]*)"
240    icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR
241                               "\",(0|1),\"" STR "\",\"" STR "\"\\);</script>");
242#undef STR
243    icu::UnicodeString input(entry_line.c_str());
244
245    UErrorCode status = U_ZERO_ERROR;
246    icu::RegexMatcher match(pattern, input, 0, status);
247
248    EXPECT_TRUE(match.find());
249    EXPECT_EQ(5, match.groupCount());
250    EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status));
251    EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status));
252    EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"),
253              match.group(3, status));
254    if (size >= 0) {
255      icu::UnicodeString size_string(FormatBytesUnlocalized(size).c_str());
256      EXPECT_EQ(size_string, match.group(4, status));
257    }
258
259    base::Time date;
260    icu::UnicodeString date_ustr(match.group(5, status));
261    std::string date_str;
262    base::UTF16ToUTF8(date_ustr.getBuffer(), date_ustr.length(), &date_str);
263    EXPECT_TRUE(base::Time::FromString(date_str.c_str(), &date));
264    EXPECT_FALSE(date.is_null());
265  }
266
267  GURL CreateFileSystemURL(const std::string path) {
268    return GURL(kFileSystemURLPrefix + path);
269  }
270
271  fileapi::FileSystemFileUtil* file_util() {
272    return file_system_context_->sandbox_delegate()->sync_file_util();
273  }
274
275  // Put the message loop at the top, so that it's the last thing deleted.
276  // Delete all MessageLoopProxy objects before the MessageLoop, to help prevent
277  // leaks caused by tasks posted during shutdown.
278  base::MessageLoopForIO message_loop_;
279
280  base::ScopedTempDir temp_dir_;
281  net::URLRequestContext empty_context_;
282  scoped_ptr<net::TestDelegate> delegate_;
283  scoped_ptr<net::URLRequest> request_;
284  scoped_ptr<FileSystemDirURLRequestJobFactory> job_factory_;
285  scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
286  scoped_refptr<FileSystemContext> file_system_context_;
287  base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_;
288};
289
290namespace {
291
292TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) {
293  CreateDirectory("foo");
294  CreateDirectory("foo/bar");
295  CreateDirectory("foo/bar/baz");
296
297  EnsureFileExists("foo/bar/hoge");
298  TruncateFile("foo/bar/hoge", 10);
299
300  TestRequest(CreateFileSystemURL("foo/bar/"));
301
302  ASSERT_FALSE(request_->is_pending());
303  EXPECT_EQ(1, delegate_->response_started_count());
304  EXPECT_FALSE(delegate_->received_data_before_response());
305  EXPECT_GT(delegate_->bytes_received(), 0);
306
307  std::istringstream in(delegate_->data_received());
308  std::string line;
309  EXPECT_TRUE(!!std::getline(in, line));
310
311#if defined(OS_WIN)
312  EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line);
313#elif defined(OS_POSIX)
314  EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line);
315#endif
316
317  EXPECT_TRUE(!!std::getline(in, line));
318  VerifyListingEntry(line, "hoge", "hoge", false, 10);
319
320  EXPECT_TRUE(!!std::getline(in, line));
321  VerifyListingEntry(line, "baz", "baz", true, 0);
322  EXPECT_FALSE(!!std::getline(in, line));
323}
324
325TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) {
326  TestRequest(GURL("filesystem:/foo/bar/baz"));
327  ASSERT_FALSE(request_->is_pending());
328  EXPECT_TRUE(delegate_->request_failed());
329  ASSERT_FALSE(request_->status().is_success());
330  EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error());
331}
332
333TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) {
334  TestRequest(GURL("filesystem:http://remote/persistent/somedir/"));
335  ASSERT_FALSE(request_->is_pending());
336  ASSERT_FALSE(request_->status().is_success());
337  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
338}
339
340TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) {
341  TestRequest(CreateFileSystemURL("somedir/"));
342  ASSERT_FALSE(request_->is_pending());
343  ASSERT_FALSE(request_->status().is_success());
344  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
345}
346
347TEST_F(FileSystemDirURLRequestJobTest, Cancel) {
348  CreateDirectory("foo");
349  TestRequestNoRun(CreateFileSystemURL("foo/"));
350  // Run StartAsync() and only StartAsync().
351  base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release());
352  base::RunLoop().RunUntilIdle();
353  // If we get here, success! we didn't crash!
354}
355
356TEST_F(FileSystemDirURLRequestJobTest, Incognito) {
357  CreateDirectory("foo");
358
359  scoped_refptr<FileSystemContext> file_system_context =
360      CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path());
361
362  TestRequestWithContext(CreateFileSystemURL("/"),
363                         file_system_context.get());
364  ASSERT_FALSE(request_->is_pending());
365  ASSERT_TRUE(request_->status().is_success());
366
367  std::istringstream in(delegate_->data_received());
368  std::string line;
369  EXPECT_TRUE(!!std::getline(in, line));
370  EXPECT_FALSE(!!std::getline(in, line));
371
372  TestRequestWithContext(CreateFileSystemURL("foo"),
373                         file_system_context.get());
374  ASSERT_FALSE(request_->is_pending());
375  ASSERT_FALSE(request_->status().is_success());
376  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
377}
378
379TEST_F(FileSystemDirURLRequestJobTest, AutoMountDirectoryListing) {
380  base::FilePath mnt_point;
381  SetUpAutoMountContext(&mnt_point);
382  ASSERT_TRUE(base::CreateDirectory(mnt_point));
383  ASSERT_TRUE(base::CreateDirectory(mnt_point.AppendASCII("foo")));
384  ASSERT_EQ(10,
385            base::WriteFile(mnt_point.AppendASCII("bar"), "1234567890", 10));
386
387  TestRequest(GURL("filesystem:http://automount/external/mnt_name"));
388
389  ASSERT_FALSE(request_->is_pending());
390  EXPECT_EQ(1, delegate_->response_started_count());
391  EXPECT_FALSE(delegate_->received_data_before_response());
392  EXPECT_GT(delegate_->bytes_received(), 0);
393
394  std::istringstream in(delegate_->data_received());
395  std::string line;
396  EXPECT_TRUE(!!std::getline(in, line));  // |line| contains the temp dir path.
397
398  // Result order is not guaranteed, so sort the results.
399  std::vector<std::string> listing_entries;
400  while (!!std::getline(in, line))
401    listing_entries.push_back(line);
402
403  ASSERT_EQ(2U, listing_entries.size());
404  std::sort(listing_entries.begin(), listing_entries.end());
405  VerifyListingEntry(listing_entries[0], "bar", "bar", false, 10);
406  VerifyListingEntry(listing_entries[1], "foo", "foo", true, -1);
407
408  ASSERT_TRUE(
409      fileapi::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
410          kValidExternalMountPoint));
411}
412
413TEST_F(FileSystemDirURLRequestJobTest, AutoMountInvalidRoot) {
414  base::FilePath mnt_point;
415  SetUpAutoMountContext(&mnt_point);
416  TestRequest(GURL("filesystem:http://automount/external/invalid"));
417
418  ASSERT_FALSE(request_->is_pending());
419  ASSERT_FALSE(request_->status().is_success());
420  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
421
422  ASSERT_FALSE(
423      fileapi::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
424          "invalid"));
425}
426
427TEST_F(FileSystemDirURLRequestJobTest, AutoMountNoHandler) {
428  base::FilePath mnt_point;
429  SetUpAutoMountContext(&mnt_point);
430  TestRequest(GURL("filesystem:http://noauto/external/mnt_name"));
431
432  ASSERT_FALSE(request_->is_pending());
433  ASSERT_FALSE(request_->status().is_success());
434  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
435
436  ASSERT_FALSE(
437      fileapi::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
438          kValidExternalMountPoint));
439}
440
441}  // namespace (anonymous)
442}  // namespace content
443