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_url_request_job.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/file_util.h"
     11 #include "base/files/file_path.h"
     12 #include "base/files/scoped_temp_dir.h"
     13 #include "base/format_macros.h"
     14 #include "base/memory/weak_ptr.h"
     15 #include "base/message_loop/message_loop.h"
     16 #include "base/message_loop/message_loop_proxy.h"
     17 #include "base/platform_file.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/test_file_system_context.h"
     24 #include "net/base/load_flags.h"
     25 #include "net/base/mime_util.h"
     26 #include "net/base/net_errors.h"
     27 #include "net/base/net_util.h"
     28 #include "net/base/request_priority.h"
     29 #include "net/http/http_byte_range.h"
     30 #include "net/http/http_request_headers.h"
     31 #include "net/url_request/url_request.h"
     32 #include "net/url_request/url_request_context.h"
     33 #include "net/url_request/url_request_test_util.h"
     34 #include "testing/gtest/include/gtest/gtest.h"
     35 #include "webkit/browser/fileapi/async_file_test_helper.h"
     36 #include "webkit/browser/fileapi/external_mount_points.h"
     37 #include "webkit/browser/fileapi/file_system_context.h"
     38 #include "webkit/browser/fileapi/file_system_file_util.h"
     39 
     40 namespace fileapi {
     41 namespace {
     42 
     43 // We always use the TEMPORARY FileSystem in this test.
     44 const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
     45 const char kTestFileData[] = "0123456789";
     46 
     47 void FillBuffer(char* buffer, size_t len) {
     48   base::RandBytes(buffer, len);
     49 }
     50 
     51 }  // namespace
     52 
     53 class FileSystemURLRequestJobTest : public testing::Test {
     54  protected:
     55   FileSystemURLRequestJobTest() : weak_factory_(this) {
     56   }
     57 
     58   virtual void SetUp() OVERRIDE {
     59     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     60 
     61     // We use the main thread so that we can get the root path synchronously.
     62     // TODO(adamk): Run this on the FILE thread we've created as well.
     63     file_system_context_ =
     64         CreateFileSystemContextForTesting(NULL, temp_dir_.path());
     65 
     66     file_system_context_->OpenFileSystem(
     67         GURL("http://remote/"), kFileSystemTypeTemporary,
     68         OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
     69         base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem,
     70                    weak_factory_.GetWeakPtr()));
     71     base::RunLoop().RunUntilIdle();
     72 
     73     net::URLRequest::Deprecated::RegisterProtocolFactory(
     74         "filesystem", &FileSystemURLRequestJobFactory);
     75   }
     76 
     77   virtual void TearDown() OVERRIDE {
     78     net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL);
     79     ClearUnusedJob();
     80     if (pending_job_.get()) {
     81       pending_job_->Kill();
     82       pending_job_ = NULL;
     83     }
     84     // FileReader posts a task to close the file in destructor.
     85     base::RunLoop().RunUntilIdle();
     86   }
     87 
     88   void OnOpenFileSystem(const GURL& root_url,
     89                         const std::string& name,
     90                         base::PlatformFileError result) {
     91     ASSERT_EQ(base::PLATFORM_FILE_OK, result);
     92   }
     93 
     94   void TestRequestHelper(const GURL& url,
     95                          const net::HttpRequestHeaders* headers,
     96                          bool run_to_completion,
     97                          FileSystemContext* file_system_context) {
     98     delegate_.reset(new net::TestDelegate());
     99     // Make delegate_ exit the MessageLoop when the request is done.
    100     delegate_->set_quit_on_complete(true);
    101     delegate_->set_quit_on_redirect(true);
    102     request_ = empty_context_.CreateRequest(
    103         url, net::DEFAULT_PRIORITY, delegate_.get());
    104     if (headers)
    105       request_->SetExtraRequestHeaders(*headers);
    106     ASSERT_TRUE(!job_);
    107     job_ = new FileSystemURLRequestJob(
    108         request_.get(), NULL, file_system_context);
    109     pending_job_ = job_;
    110 
    111     request_->Start();
    112     ASSERT_TRUE(request_->is_pending());  // verify that we're starting async
    113     if (run_to_completion)
    114       base::MessageLoop::current()->Run();
    115   }
    116 
    117   void TestRequest(const GURL& url) {
    118     TestRequestHelper(url, NULL, true, file_system_context_.get());
    119   }
    120 
    121   void TestRequestWithContext(const GURL& url,
    122                               FileSystemContext* file_system_context) {
    123     TestRequestHelper(url, NULL, true, file_system_context);
    124   }
    125 
    126   void TestRequestWithHeaders(const GURL& url,
    127                               const net::HttpRequestHeaders* headers) {
    128     TestRequestHelper(url, headers, true, file_system_context_.get());
    129   }
    130 
    131   void TestRequestNoRun(const GURL& url) {
    132     TestRequestHelper(url, NULL, false, file_system_context_.get());
    133   }
    134 
    135   void CreateDirectory(const base::StringPiece& dir_name) {
    136     FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
    137         GURL("http://remote"),
    138         kFileSystemTypeTemporary,
    139         base::FilePath().AppendASCII(dir_name));
    140     ASSERT_EQ(base::PLATFORM_FILE_OK, AsyncFileTestHelper::CreateDirectory(
    141         file_system_context_, url));
    142   }
    143 
    144   void WriteFile(const base::StringPiece& file_name,
    145                  const char* buf, int buf_size) {
    146     FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
    147         GURL("http://remote"),
    148         kFileSystemTypeTemporary,
    149         base::FilePath().AppendASCII(file_name));
    150     ASSERT_EQ(base::PLATFORM_FILE_OK,
    151               AsyncFileTestHelper::CreateFileWithData(
    152                   file_system_context_, url, buf, buf_size));
    153   }
    154 
    155   GURL CreateFileSystemURL(const std::string& path) {
    156     return GURL(kFileSystemURLPrefix + path);
    157   }
    158 
    159   static net::URLRequestJob* FileSystemURLRequestJobFactory(
    160       net::URLRequest* request,
    161       net::NetworkDelegate* network_delegate,
    162       const std::string& scheme) {
    163     DCHECK(job_);
    164     net::URLRequestJob* temp = job_;
    165     job_ = NULL;
    166     return temp;
    167   }
    168 
    169   static void ClearUnusedJob() {
    170     if (job_) {
    171       scoped_refptr<net::URLRequestJob> deleter = job_;
    172       job_ = NULL;
    173     }
    174   }
    175 
    176   // Put the message loop at the top, so that it's the last thing deleted.
    177   base::MessageLoopForIO message_loop_;
    178 
    179   base::ScopedTempDir temp_dir_;
    180   scoped_refptr<FileSystemContext> file_system_context_;
    181   base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_;
    182 
    183   net::URLRequestContext empty_context_;
    184 
    185   // NOTE: order matters, request must die before delegate
    186   scoped_ptr<net::TestDelegate> delegate_;
    187   scoped_ptr<net::URLRequest> request_;
    188 
    189   scoped_refptr<net::URLRequestJob> pending_job_;
    190   static net::URLRequestJob* job_;
    191 };
    192 
    193 // static
    194 net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL;
    195 
    196 namespace {
    197 
    198 TEST_F(FileSystemURLRequestJobTest, FileTest) {
    199   WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
    200   TestRequest(CreateFileSystemURL("file1.dat"));
    201 
    202   ASSERT_FALSE(request_->is_pending());
    203   EXPECT_EQ(1, delegate_->response_started_count());
    204   EXPECT_FALSE(delegate_->received_data_before_response());
    205   EXPECT_EQ(kTestFileData, delegate_->data_received());
    206   EXPECT_EQ(200, request_->GetResponseCode());
    207   std::string cache_control;
    208   request_->GetResponseHeaderByName("cache-control", &cache_control);
    209   EXPECT_EQ("no-cache", cache_control);
    210 }
    211 
    212 TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) {
    213   const size_t buffer_size = 4000;
    214   scoped_ptr<char[]> buffer(new char[buffer_size]);
    215   FillBuffer(buffer.get(), buffer_size);
    216   WriteFile("bigfile", buffer.get(), buffer_size);
    217 
    218   const size_t first_byte_position = 500;
    219   const size_t last_byte_position = buffer_size - first_byte_position;
    220   std::string partial_buffer_string(buffer.get() + first_byte_position,
    221                                     buffer.get() + last_byte_position + 1);
    222 
    223   net::HttpRequestHeaders headers;
    224   headers.SetHeader(
    225       net::HttpRequestHeaders::kRange,
    226       net::HttpByteRange::Bounded(
    227           first_byte_position, last_byte_position).GetHeaderValue());
    228   TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
    229 
    230   ASSERT_FALSE(request_->is_pending());
    231   EXPECT_EQ(1, delegate_->response_started_count());
    232   EXPECT_FALSE(delegate_->received_data_before_response());
    233   EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
    234 }
    235 
    236 TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) {
    237   const size_t buffer_size = 4000;
    238   scoped_ptr<char[]> buffer(new char[buffer_size]);
    239   FillBuffer(buffer.get(), buffer_size);
    240   WriteFile("bigfile", buffer.get(), buffer_size);
    241 
    242   const size_t first_byte_position = 500;
    243   std::string partial_buffer_string(buffer.get() + first_byte_position,
    244                                     buffer.get() + buffer_size);
    245 
    246   net::HttpRequestHeaders headers;
    247   headers.SetHeader(
    248       net::HttpRequestHeaders::kRange,
    249       net::HttpByteRange::RightUnbounded(first_byte_position).GetHeaderValue());
    250   TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
    251   ASSERT_FALSE(request_->is_pending());
    252   EXPECT_EQ(1, delegate_->response_started_count());
    253   EXPECT_FALSE(delegate_->received_data_before_response());
    254   // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
    255   EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
    256 }
    257 
    258 
    259 TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) {
    260   WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
    261   net::HttpRequestHeaders headers;
    262   headers.SetHeader(net::HttpRequestHeaders::kRange,
    263                     "bytes=0-5,10-200,200-300");
    264   TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
    265   EXPECT_TRUE(delegate_->request_failed());
    266   EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
    267             request_->status().error());
    268 }
    269 
    270 TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) {
    271   WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
    272   net::HttpRequestHeaders headers;
    273   headers.SetHeader(
    274       net::HttpRequestHeaders::kRange,
    275       net::HttpByteRange::Bounded(500, 1000).GetHeaderValue());
    276   TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
    277 
    278   ASSERT_FALSE(request_->is_pending());
    279   EXPECT_TRUE(delegate_->request_failed());
    280   EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
    281             request_->status().error());
    282 }
    283 
    284 TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) {
    285   CreateDirectory("dir");
    286   TestRequest(CreateFileSystemURL("dir"));
    287 
    288   EXPECT_EQ(1, delegate_->received_redirect_count());
    289   EXPECT_TRUE(request_->status().is_success());
    290   EXPECT_FALSE(delegate_->request_failed());
    291 
    292   // We've deferred the redirect; now cancel the request to avoid following it.
    293   request_->Cancel();
    294   base::MessageLoop::current()->Run();
    295 }
    296 
    297 TEST_F(FileSystemURLRequestJobTest, InvalidURL) {
    298   TestRequest(GURL("filesystem:/foo/bar/baz"));
    299   ASSERT_FALSE(request_->is_pending());
    300   EXPECT_TRUE(delegate_->request_failed());
    301   EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error());
    302 }
    303 
    304 TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) {
    305   TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
    306   ASSERT_FALSE(request_->is_pending());
    307   EXPECT_TRUE(delegate_->request_failed());
    308   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
    309 }
    310 
    311 TEST_F(FileSystemURLRequestJobTest, NoSuchFile) {
    312   TestRequest(CreateFileSystemURL("somefile"));
    313   ASSERT_FALSE(request_->is_pending());
    314   EXPECT_TRUE(delegate_->request_failed());
    315   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
    316 }
    317 
    318 TEST_F(FileSystemURLRequestJobTest, Cancel) {
    319   WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
    320   TestRequestNoRun(CreateFileSystemURL("file1.dat"));
    321 
    322   // Run StartAsync() and only StartAsync().
    323   base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release());
    324   base::RunLoop().RunUntilIdle();
    325   // If we get here, success! we didn't crash!
    326 }
    327 
    328 TEST_F(FileSystemURLRequestJobTest, GetMimeType) {
    329   const char kFilename[] = "hoge.html";
    330 
    331   std::string mime_type_direct;
    332   base::FilePath::StringType extension =
    333       base::FilePath().AppendASCII(kFilename).Extension();
    334   if (!extension.empty())
    335     extension = extension.substr(1);
    336   EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
    337       extension, &mime_type_direct));
    338 
    339   TestRequest(CreateFileSystemURL(kFilename));
    340 
    341   std::string mime_type_from_job;
    342   request_->GetMimeType(&mime_type_from_job);
    343   EXPECT_EQ(mime_type_direct, mime_type_from_job);
    344 }
    345 
    346 TEST_F(FileSystemURLRequestJobTest, Incognito) {
    347   WriteFile("file", kTestFileData, arraysize(kTestFileData) - 1);
    348 
    349   // Creates a new filesystem context for incognito mode.
    350   scoped_refptr<FileSystemContext> file_system_context =
    351       CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path());
    352 
    353   // The request should return NOT_FOUND error if it's in incognito mode.
    354   TestRequestWithContext(CreateFileSystemURL("file"),
    355                          file_system_context.get());
    356   ASSERT_FALSE(request_->is_pending());
    357   EXPECT_TRUE(delegate_->request_failed());
    358   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
    359 
    360   // Make sure it returns success with regular (non-incognito) context.
    361   TestRequest(CreateFileSystemURL("file"));
    362   ASSERT_FALSE(request_->is_pending());
    363   EXPECT_EQ(kTestFileData, delegate_->data_received());
    364   EXPECT_EQ(200, request_->GetResponseCode());
    365 }
    366 
    367 }  // namespace
    368 }  // namespace fileapi
    369