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