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 "chrome/browser/chromeos/fileapi/external_file_url_request_job.h"
6
7#include "base/bind.h"
8#include "base/files/file_util.h"
9#include "base/memory/ref_counted.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/run_loop.h"
12#include "base/threading/thread.h"
13#include "chrome/browser/chromeos/drive/drive_file_stream_reader.h"
14#include "chrome/browser/chromeos/drive/drive_integration_service.h"
15#include "chrome/browser/chromeos/drive/fake_file_system.h"
16#include "chrome/browser/chromeos/drive/file_system_util.h"
17#include "chrome/browser/chromeos/drive/test_util.h"
18#include "chrome/browser/drive/fake_drive_service.h"
19#include "chrome/browser/drive/test_util.h"
20#include "chrome/browser/prefs/browser_prefs.h"
21#include "chrome/browser/prefs/pref_service_syncable.h"
22#include "chrome/browser/profiles/profile_manager.h"
23#include "chrome/common/url_constants.h"
24#include "chrome/test/base/testing_browser_process.h"
25#include "chrome/test/base/testing_profile.h"
26#include "chrome/test/base/testing_profile_manager.h"
27#include "components/pref_registry/pref_registry_syncable.h"
28#include "components/pref_registry/testing_pref_service_syncable.h"
29#include "content/public/browser/browser_thread.h"
30#include "content/public/test/test_browser_thread_bundle.h"
31#include "content/public/test/test_file_system_options.h"
32#include "google_apis/drive/test_util.h"
33#include "net/base/request_priority.h"
34#include "net/base/test_completion_callback.h"
35#include "net/http/http_byte_range.h"
36#include "net/url_request/redirect_info.h"
37#include "net/url_request/url_request.h"
38#include "net/url_request/url_request_context.h"
39#include "net/url_request/url_request_test_util.h"
40#include "storage/browser/fileapi/external_mount_points.h"
41#include "storage/browser/fileapi/file_system_context.h"
42#include "testing/gtest/include/gtest/gtest.h"
43#include "url/gurl.h"
44
45namespace chromeos {
46namespace {
47
48// A simple URLRequestJobFactory implementation to create
49// ExternalFileURLRequestJob.
50class TestURLRequestJobFactory : public net::URLRequestJobFactory {
51 public:
52  explicit TestURLRequestJobFactory(void* profile_id)
53      : profile_id_(profile_id) {}
54
55  virtual ~TestURLRequestJobFactory() {}
56
57  // net::URLRequestJobFactory override:
58  virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
59      const std::string& scheme,
60      net::URLRequest* request,
61      net::NetworkDelegate* network_delegate) const OVERRIDE {
62    return new ExternalFileURLRequestJob(
63        profile_id_, request, network_delegate);
64  }
65
66  virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE {
67    return scheme == chrome::kExternalFileScheme;
68  }
69
70  virtual bool IsHandledURL(const GURL& url) const OVERRIDE {
71    return url.is_valid() && IsHandledProtocol(url.scheme());
72  }
73
74  virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE {
75    return true;
76  }
77
78 private:
79  void* const profile_id_;
80  DISALLOW_COPY_AND_ASSIGN(TestURLRequestJobFactory);
81};
82
83class TestDelegate : public net::TestDelegate {
84 public:
85  TestDelegate() {}
86
87  const GURL& redirect_url() const { return redirect_url_; }
88
89  // net::TestDelegate override.
90  virtual void OnReceivedRedirect(net::URLRequest* request,
91                                  const net::RedirectInfo& redirect_info,
92                                  bool* defer_redirect) OVERRIDE {
93    redirect_url_ = redirect_info.new_url;
94    net::TestDelegate::OnReceivedRedirect(
95        request, redirect_info, defer_redirect);
96  }
97
98 private:
99  GURL redirect_url_;
100
101  DISALLOW_COPY_AND_ASSIGN(TestDelegate);
102};
103
104}  // namespace
105
106class ExternalFileURLRequestJobTest : public testing::Test {
107 protected:
108  ExternalFileURLRequestJobTest()
109      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
110        integration_service_factory_callback_(base::Bind(
111            &ExternalFileURLRequestJobTest::CreateDriveIntegrationService,
112            base::Unretained(this))),
113        fake_file_system_(NULL) {}
114
115  virtual ~ExternalFileURLRequestJobTest() {}
116
117  virtual void SetUp() OVERRIDE {
118    // Create a testing profile.
119    profile_manager_.reset(
120        new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
121    ASSERT_TRUE(profile_manager_->SetUp());
122    Profile* const profile =
123        profile_manager_->CreateTestingProfile("test-user");
124
125    // Create the drive integration service for the profile.
126    integration_service_factory_scope_.reset(
127        new drive::DriveIntegrationServiceFactory::ScopedFactoryForTest(
128            &integration_service_factory_callback_));
129    drive::DriveIntegrationServiceFactory::GetForProfile(profile);
130
131    // Create the URL request job factory.
132    test_network_delegate_.reset(new net::TestNetworkDelegate);
133    test_url_request_job_factory_.reset(new TestURLRequestJobFactory(profile));
134    url_request_context_.reset(new net::URLRequestContext());
135    url_request_context_->set_job_factory(test_url_request_job_factory_.get());
136    url_request_context_->set_network_delegate(test_network_delegate_.get());
137    test_delegate_.reset(new TestDelegate);
138  }
139
140  virtual void TearDown() { profile_manager_.reset(); }
141
142  bool ReadDriveFileSync(const base::FilePath& file_path,
143                         std::string* out_content) {
144    scoped_ptr<base::Thread> worker_thread(
145        new base::Thread("ReadDriveFileSync"));
146    if (!worker_thread->Start())
147      return false;
148
149    scoped_ptr<drive::DriveFileStreamReader> reader(
150        new drive::DriveFileStreamReader(
151            base::Bind(&ExternalFileURLRequestJobTest::GetFileSystem,
152                       base::Unretained(this)),
153            worker_thread->message_loop_proxy().get()));
154    int error = net::ERR_FAILED;
155    scoped_ptr<drive::ResourceEntry> entry;
156    {
157      base::RunLoop run_loop;
158      reader->Initialize(file_path,
159                         net::HttpByteRange(),
160                         google_apis::test_util::CreateQuitCallback(
161                             &run_loop,
162                             google_apis::test_util::CreateCopyResultCallback(
163                                 &error, &entry)));
164      run_loop.Run();
165    }
166    if (error != net::OK || !entry)
167      return false;
168
169    // Read data from the reader.
170    std::string content;
171    if (drive::test_util::ReadAllData(reader.get(), &content) != net::OK)
172      return false;
173
174    if (static_cast<size_t>(entry->file_info().size()) != content.size())
175      return false;
176
177    *out_content = content;
178    return true;
179  }
180
181  scoped_ptr<net::URLRequestContext> url_request_context_;
182  scoped_ptr<TestDelegate> test_delegate_;
183
184 private:
185  // Create the drive integration service for the |profile|
186  drive::DriveIntegrationService* CreateDriveIntegrationService(
187      Profile* profile) {
188    drive::FakeDriveService* const drive_service = new drive::FakeDriveService;
189    if (!drive::test_util::SetUpTestEntries(drive_service))
190      return NULL;
191
192    const std::string& drive_mount_name =
193        drive::util::GetDriveMountPointPath(profile).BaseName().AsUTF8Unsafe();
194    storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
195        drive_mount_name,
196        storage::kFileSystemTypeDrive,
197        storage::FileSystemMountOption(),
198        drive::util::GetDriveMountPointPath(profile));
199    DCHECK(!fake_file_system_);
200    fake_file_system_ = new drive::test_util::FakeFileSystem(drive_service);
201    if (!drive_cache_dir_.CreateUniqueTempDir())
202      return NULL;
203    return new drive::DriveIntegrationService(profile,
204                                              NULL,
205                                              drive_service,
206                                              drive_mount_name,
207                                              drive_cache_dir_.path(),
208                                              fake_file_system_);
209  }
210
211  drive::FileSystemInterface* GetFileSystem() { return fake_file_system_; }
212
213  content::TestBrowserThreadBundle thread_bundle_;
214  drive::DriveIntegrationServiceFactory::FactoryCallback
215      integration_service_factory_callback_;
216  scoped_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>
217      integration_service_factory_scope_;
218  scoped_ptr<drive::DriveIntegrationService> integration_service_;
219  drive::test_util::FakeFileSystem* fake_file_system_;
220
221  scoped_ptr<net::TestNetworkDelegate> test_network_delegate_;
222  scoped_ptr<TestURLRequestJobFactory> test_url_request_job_factory_;
223
224  scoped_ptr<TestingProfileManager> profile_manager_;
225  base::ScopedTempDir drive_cache_dir_;
226  scoped_refptr<storage::FileSystemContext> file_system_context_;
227};
228
229TEST_F(ExternalFileURLRequestJobTest, NonGetMethod) {
230  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
231      GURL("externalfile:drive-test-user-hash/root/File 1.txt"),
232      net::DEFAULT_PRIORITY,
233      test_delegate_.get(),
234      NULL));
235  request->set_method("POST");  // Set non "GET" method.
236  request->Start();
237
238  base::RunLoop().Run();
239
240  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
241  EXPECT_EQ(net::ERR_METHOD_NOT_SUPPORTED, request->status().error());
242}
243
244TEST_F(ExternalFileURLRequestJobTest, RegularFile) {
245  const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt");
246  const base::FilePath kTestFilePath("drive/root/File 1.txt");
247
248  // For the first time, the file should be fetched from the server.
249  {
250    scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
251        kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL));
252    request->Start();
253
254    base::RunLoop().Run();
255
256    EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
257    // It looks weird, but the mime type for the "File 1.txt" is "audio/mpeg"
258    // on the server.
259    std::string mime_type;
260    request->GetMimeType(&mime_type);
261    EXPECT_EQ("audio/mpeg", mime_type);
262
263    // Reading file must be done after |request| runs, otherwise
264    // it'll create a local cache file, and we cannot test correctly.
265    std::string expected_data;
266    ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data));
267    EXPECT_EQ(expected_data, test_delegate_->data_received());
268  }
269
270  // For the second time, the locally cached file should be used.
271  // The caching emulation is done by FakeFileSystem.
272  {
273    test_delegate_.reset(new TestDelegate);
274    scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
275        GURL("externalfile:drive-test-user-hash/root/File 1.txt"),
276        net::DEFAULT_PRIORITY,
277        test_delegate_.get(),
278        NULL));
279    request->Start();
280
281    base::RunLoop().Run();
282
283    EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
284    std::string mime_type;
285    request->GetMimeType(&mime_type);
286    EXPECT_EQ("audio/mpeg", mime_type);
287
288    std::string expected_data;
289    ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data));
290    EXPECT_EQ(expected_data, test_delegate_->data_received());
291  }
292}
293
294TEST_F(ExternalFileURLRequestJobTest, HostedDocument) {
295  // Open a gdoc file.
296  test_delegate_->set_quit_on_redirect(true);
297  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
298      GURL(
299          "externalfile:drive-test-user-hash/root/Document 1 "
300          "excludeDir-test.gdoc"),
301      net::DEFAULT_PRIORITY,
302      test_delegate_.get(),
303      NULL));
304  request->Start();
305
306  base::RunLoop().Run();
307
308  EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
309  // Make sure that a hosted document triggers redirection.
310  EXPECT_TRUE(request->is_redirecting());
311  EXPECT_TRUE(test_delegate_->redirect_url().is_valid());
312}
313
314TEST_F(ExternalFileURLRequestJobTest, RootDirectory) {
315  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
316      GURL("externalfile:drive-test-user-hash/root"),
317      net::DEFAULT_PRIORITY,
318      test_delegate_.get(),
319      NULL));
320  request->Start();
321
322  base::RunLoop().Run();
323
324  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
325  EXPECT_EQ(net::ERR_FAILED, request->status().error());
326}
327
328TEST_F(ExternalFileURLRequestJobTest, Directory) {
329  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
330      GURL("externalfile:drive-test-user-hash/root/Directory 1"),
331      net::DEFAULT_PRIORITY,
332      test_delegate_.get(),
333      NULL));
334  request->Start();
335
336  base::RunLoop().Run();
337
338  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
339  EXPECT_EQ(net::ERR_FAILED, request->status().error());
340}
341
342TEST_F(ExternalFileURLRequestJobTest, NonExistingFile) {
343  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
344      GURL("externalfile:drive-test-user-hash/root/non-existing-file.txt"),
345      net::DEFAULT_PRIORITY,
346      test_delegate_.get(),
347      NULL));
348  request->Start();
349
350  base::RunLoop().Run();
351
352  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
353  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error());
354}
355
356TEST_F(ExternalFileURLRequestJobTest, WrongFormat) {
357  scoped_ptr<net::URLRequest> request(
358      url_request_context_->CreateRequest(GURL("externalfile:"),
359                                          net::DEFAULT_PRIORITY,
360                                          test_delegate_.get(),
361                                          NULL));
362  request->Start();
363
364  base::RunLoop().Run();
365
366  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
367  EXPECT_EQ(net::ERR_INVALID_URL, request->status().error());
368}
369
370TEST_F(ExternalFileURLRequestJobTest, Cancel) {
371  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
372      GURL("externalfile:drive-test-user-hash/root/File 1.txt"),
373      net::DEFAULT_PRIORITY,
374      test_delegate_.get(),
375      NULL));
376
377  // Start the request, and cancel it immediately after it.
378  request->Start();
379  request->Cancel();
380
381  base::RunLoop().Run();
382
383  EXPECT_EQ(net::URLRequestStatus::CANCELED, request->status().status());
384}
385
386TEST_F(ExternalFileURLRequestJobTest, RangeHeader) {
387  const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt");
388  const base::FilePath kTestFilePath("drive/root/File 1.txt");
389
390  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
391      kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL));
392
393  // Set range header.
394  request->SetExtraRequestHeaderByName(
395      "Range", "bytes=3-5", false /* overwrite */);
396  request->Start();
397
398  base::RunLoop().Run();
399
400  EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status());
401
402  // Reading file must be done after |request| runs, otherwise
403  // it'll create a local cache file, and we cannot test correctly.
404  std::string expected_data;
405  ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data));
406  EXPECT_EQ(expected_data.substr(3, 3), test_delegate_->data_received());
407}
408
409TEST_F(ExternalFileURLRequestJobTest, WrongRangeHeader) {
410  const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt");
411
412  scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest(
413      kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL));
414
415  // Set range header.
416  request->SetExtraRequestHeaderByName(
417      "Range", "Wrong Range Header Value", false /* overwrite */);
418  request->Start();
419
420  base::RunLoop().Run();
421
422  EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
423  EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, request->status().error());
424}
425
426}  // namespace chromeos
427