Home | History | Annotate | Download | only in fileapi
      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 
     37 using fileapi::FileSystemContext;
     38 using fileapi::FileSystemOperationContext;
     39 using fileapi::FileSystemURL;
     40 
     41 namespace content {
     42 namespace {
     43 
     44 // We always use the TEMPORARY FileSystem in this test.
     45 const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
     46 
     47 const 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".
     51 bool 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 
     74 class 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 
    109 class 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 
    290 namespace {
    291 
    292 TEST_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 
    325 TEST_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 
    333 TEST_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 
    340 TEST_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 
    347 TEST_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 
    356 TEST_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 
    379 TEST_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 
    413 TEST_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 
    427 TEST_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