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/file_manager/fileapi_util.h"
6
7#include "base/files/file.h"
8#include "base/files/file_path.h"
9#include "chrome/browser/chromeos/drive/file_system_util.h"
10#include "chrome/browser/chromeos/file_manager/app_id.h"
11#include "chrome/browser/extensions/extension_util.h"
12#include "chrome/browser/profiles/profile.h"
13#include "content/public/browser/browser_thread.h"
14#include "content/public/browser/render_view_host.h"
15#include "content/public/browser/site_instance.h"
16#include "content/public/browser/storage_partition.h"
17#include "extensions/common/extension.h"
18#include "google_apis/drive/task_util.h"
19#include "net/base/escape.h"
20#include "storage/browser/fileapi/file_system_context.h"
21#include "storage/browser/fileapi/open_file_system_mode.h"
22#include "storage/common/fileapi/file_system_util.h"
23#include "url/gurl.h"
24
25using content::BrowserThread;
26
27namespace file_manager {
28namespace util {
29
30namespace {
31
32GURL ConvertRelativeFilePathToFileSystemUrl(const base::FilePath& relative_path,
33                                            const std::string& extension_id) {
34  GURL base_url = storage::GetFileSystemRootURI(
35      extensions::Extension::GetBaseURLFromExtensionId(extension_id),
36      storage::kFileSystemTypeExternal);
37  return GURL(base_url.spec() +
38              net::EscapeUrlEncodedData(relative_path.AsUTF8Unsafe(),
39                                        false));  // Space to %20 instead of +.
40}
41
42// Creates an ErrorDefinition with an error set to |error|.
43EntryDefinition CreateEntryDefinitionWithError(base::File::Error error) {
44  EntryDefinition result;
45  result.error = error;
46  return result;
47}
48
49// Helper class for performing conversions from file definitions to entry
50// definitions. It is possible to do it without a class, but the code would be
51// crazy and super tricky.
52//
53// This class copies the input |file_definition_list|,
54// so there is no need to worry about validity of passed |file_definition_list|
55// reference. Also, it automatically deletes itself after converting finished,
56// or if shutdown is invoked during ResolveURL(). Must be called on UI thread.
57class FileDefinitionListConverter {
58 public:
59  FileDefinitionListConverter(Profile* profile,
60                              const std::string& extension_id,
61                              const FileDefinitionList& file_definition_list,
62                              const EntryDefinitionListCallback& callback);
63  ~FileDefinitionListConverter() {}
64
65 private:
66  // Converts the element under the iterator to an entry. First, converts
67  // the virtual path to an URL, and calls OnResolvedURL(). In case of error
68  // calls OnIteratorConverted with an error entry definition.
69  void ConvertNextIterator(scoped_ptr<FileDefinitionListConverter> self_deleter,
70                           FileDefinitionList::const_iterator iterator);
71
72  // Creates an entry definition from the URL as well as the file definition.
73  // Then, calls OnIteratorConverted with the created entry definition.
74  void OnResolvedURL(scoped_ptr<FileDefinitionListConverter> self_deleter,
75                     FileDefinitionList::const_iterator iterator,
76                     base::File::Error error,
77                     const storage::FileSystemInfo& info,
78                     const base::FilePath& file_path,
79                     storage::FileSystemContext::ResolvedEntryType type);
80
81  // Called when the iterator is converted. Adds the |entry_definition| to
82  // |results_| and calls ConvertNextIterator() for the next element.
83  void OnIteratorConverted(scoped_ptr<FileDefinitionListConverter> self_deleter,
84                           FileDefinitionList::const_iterator iterator,
85                           const EntryDefinition& entry_definition);
86
87  scoped_refptr<storage::FileSystemContext> file_system_context_;
88  const std::string extension_id_;
89  const FileDefinitionList file_definition_list_;
90  const EntryDefinitionListCallback callback_;
91  scoped_ptr<EntryDefinitionList> result_;
92};
93
94FileDefinitionListConverter::FileDefinitionListConverter(
95    Profile* profile,
96    const std::string& extension_id,
97    const FileDefinitionList& file_definition_list,
98    const EntryDefinitionListCallback& callback)
99    : extension_id_(extension_id),
100      file_definition_list_(file_definition_list),
101      callback_(callback),
102      result_(new EntryDefinitionList) {
103  DCHECK_CURRENTLY_ON(BrowserThread::UI);
104
105  // File browser APIs are meant to be used only from extension context, so
106  // the extension's site is the one in whose file system context the virtual
107  // path should be found.
108  GURL site = extensions::util::GetSiteForExtensionId(extension_id_, profile);
109  file_system_context_ =
110      content::BrowserContext::GetStoragePartitionForSite(
111          profile, site)->GetFileSystemContext();
112
113  // Deletes the converter, once the scoped pointer gets out of scope. It is
114  // either, if the conversion is finished, or ResolveURL() is terminated, and
115  // the callback not called because of shutdown.
116  scoped_ptr<FileDefinitionListConverter> self_deleter(this);
117  ConvertNextIterator(self_deleter.Pass(), file_definition_list_.begin());
118}
119
120void FileDefinitionListConverter::ConvertNextIterator(
121    scoped_ptr<FileDefinitionListConverter> self_deleter,
122    FileDefinitionList::const_iterator iterator) {
123  if (iterator == file_definition_list_.end()) {
124    // The converter object will be destroyed since |self_deleter| gets out of
125    // scope.
126    callback_.Run(result_.Pass());
127    return;
128  }
129
130  if (!file_system_context_.get()) {
131    OnIteratorConverted(self_deleter.Pass(),
132                        iterator,
133                        CreateEntryDefinitionWithError(
134                            base::File::FILE_ERROR_INVALID_OPERATION));
135    return;
136  }
137
138  storage::FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
139      extensions::Extension::GetBaseURLFromExtensionId(extension_id_),
140      storage::kFileSystemTypeExternal,
141      iterator->virtual_path);
142  DCHECK(url.is_valid());
143
144  // The converter object will be deleted if the callback is not called because
145  // of shutdown during ResolveURL().
146  file_system_context_->ResolveURL(
147      url,
148      base::Bind(&FileDefinitionListConverter::OnResolvedURL,
149                 base::Unretained(this),
150                 base::Passed(&self_deleter),
151                 iterator));
152}
153
154void FileDefinitionListConverter::OnResolvedURL(
155    scoped_ptr<FileDefinitionListConverter> self_deleter,
156    FileDefinitionList::const_iterator iterator,
157    base::File::Error error,
158    const storage::FileSystemInfo& info,
159    const base::FilePath& file_path,
160    storage::FileSystemContext::ResolvedEntryType type) {
161  DCHECK_CURRENTLY_ON(BrowserThread::UI);
162
163  if (error != base::File::FILE_OK) {
164    OnIteratorConverted(self_deleter.Pass(),
165                        iterator,
166                        CreateEntryDefinitionWithError(error));
167    return;
168  }
169
170  EntryDefinition entry_definition;
171  entry_definition.file_system_root_url = info.root_url.spec();
172  entry_definition.file_system_name = info.name;
173  switch (type) {
174    case storage::FileSystemContext::RESOLVED_ENTRY_FILE:
175      entry_definition.is_directory = false;
176      break;
177    case storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY:
178      entry_definition.is_directory = true;
179      break;
180    case storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND:
181      entry_definition.is_directory = iterator->is_directory;
182      break;
183  }
184  entry_definition.error = base::File::FILE_OK;
185
186  // Construct a target Entry.fullPath value from the virtual path and the
187  // root URL. Eg. Downloads/A/b.txt -> A/b.txt.
188  const base::FilePath root_virtual_path =
189      file_system_context_->CrackURL(info.root_url).virtual_path();
190  DCHECK(root_virtual_path == iterator->virtual_path ||
191         root_virtual_path.IsParent(iterator->virtual_path));
192  base::FilePath full_path;
193  root_virtual_path.AppendRelativePath(iterator->virtual_path, &full_path);
194  entry_definition.full_path = full_path;
195
196  OnIteratorConverted(self_deleter.Pass(), iterator, entry_definition);
197}
198
199void FileDefinitionListConverter::OnIteratorConverted(
200    scoped_ptr<FileDefinitionListConverter> self_deleter,
201    FileDefinitionList::const_iterator iterator,
202    const EntryDefinition& entry_definition) {
203  result_->push_back(entry_definition);
204  ConvertNextIterator(self_deleter.Pass(), ++iterator);
205}
206
207// Helper function to return the converted definition entry directly, without
208// the redundant container.
209void OnConvertFileDefinitionDone(
210    const EntryDefinitionCallback& callback,
211    scoped_ptr<EntryDefinitionList> entry_definition_list) {
212  DCHECK_EQ(1u, entry_definition_list->size());
213  callback.Run(entry_definition_list->at(0));
214}
215
216// Used to implement CheckIfDirectoryExists().
217void CheckIfDirectoryExistsOnIOThread(
218    scoped_refptr<storage::FileSystemContext> file_system_context,
219    const GURL& url,
220    const storage::FileSystemOperationRunner::StatusCallback& callback) {
221  DCHECK_CURRENTLY_ON(BrowserThread::IO);
222
223  storage::FileSystemURL file_system_url = file_system_context->CrackURL(url);
224  file_system_context->operation_runner()->DirectoryExists(
225      file_system_url, callback);
226}
227
228}  // namespace
229
230EntryDefinition::EntryDefinition() {
231}
232
233EntryDefinition::~EntryDefinition() {
234}
235
236storage::FileSystemContext* GetFileSystemContextForExtensionId(
237    Profile* profile,
238    const std::string& extension_id) {
239  GURL site = extensions::util::GetSiteForExtensionId(extension_id, profile);
240  return content::BrowserContext::GetStoragePartitionForSite(profile, site)->
241      GetFileSystemContext();
242}
243
244storage::FileSystemContext* GetFileSystemContextForRenderViewHost(
245    Profile* profile,
246    content::RenderViewHost* render_view_host) {
247  content::SiteInstance* site_instance = render_view_host->GetSiteInstance();
248  return content::BrowserContext::GetStoragePartition(profile, site_instance)->
249      GetFileSystemContext();
250}
251
252base::FilePath ConvertDrivePathToRelativeFileSystemPath(
253    Profile* profile,
254    const std::string& extension_id,
255    const base::FilePath& drive_path) {
256  // "/special/drive-xxx"
257  base::FilePath path = drive::util::GetDriveMountPointPath(profile);
258  // appended with (|drive_path| - "drive").
259  drive::util::GetDriveGrandRootPath().AppendRelativePath(drive_path, &path);
260
261  base::FilePath relative_path;
262  ConvertAbsoluteFilePathToRelativeFileSystemPath(profile,
263                                                  extension_id,
264                                                  path,
265                                                  &relative_path);
266  return relative_path;
267}
268
269GURL ConvertDrivePathToFileSystemUrl(Profile* profile,
270                                     const base::FilePath& drive_path,
271                                     const std::string& extension_id) {
272  const base::FilePath relative_path =
273      ConvertDrivePathToRelativeFileSystemPath(profile, extension_id,
274                                               drive_path);
275  if (relative_path.empty())
276    return GURL();
277  return ConvertRelativeFilePathToFileSystemUrl(relative_path, extension_id);
278}
279
280bool ConvertAbsoluteFilePathToFileSystemUrl(Profile* profile,
281                                            const base::FilePath& absolute_path,
282                                            const std::string& extension_id,
283                                            GURL* url) {
284  base::FilePath relative_path;
285  if (!ConvertAbsoluteFilePathToRelativeFileSystemPath(profile,
286                                                       extension_id,
287                                                       absolute_path,
288                                                       &relative_path)) {
289    return false;
290  }
291  *url = ConvertRelativeFilePathToFileSystemUrl(relative_path, extension_id);
292  return true;
293}
294
295bool ConvertAbsoluteFilePathToRelativeFileSystemPath(
296    Profile* profile,
297    const std::string& extension_id,
298    const base::FilePath& absolute_path,
299    base::FilePath* virtual_path) {
300  // File browser APIs are meant to be used only from extension context, so the
301  // extension's site is the one in whose file system context the virtual path
302  // should be found.
303  GURL site = extensions::util::GetSiteForExtensionId(extension_id, profile);
304  storage::ExternalFileSystemBackend* backend =
305      content::BrowserContext::GetStoragePartitionForSite(profile, site)
306          ->GetFileSystemContext()
307          ->external_backend();
308  if (!backend)
309    return false;
310
311  // Find if this file path is managed by the external backend.
312  if (!backend->GetVirtualPath(absolute_path, virtual_path))
313    return false;
314
315  return true;
316}
317
318void ConvertFileDefinitionListToEntryDefinitionList(
319    Profile* profile,
320    const std::string& extension_id,
321    const FileDefinitionList& file_definition_list,
322    const EntryDefinitionListCallback& callback) {
323  DCHECK_CURRENTLY_ON(BrowserThread::UI);
324
325  // The converter object destroys itself.
326  new FileDefinitionListConverter(
327      profile, extension_id, file_definition_list, callback);
328}
329
330void ConvertFileDefinitionToEntryDefinition(
331    Profile* profile,
332    const std::string& extension_id,
333    const FileDefinition& file_definition,
334    const EntryDefinitionCallback& callback) {
335  DCHECK_CURRENTLY_ON(BrowserThread::UI);
336
337  FileDefinitionList file_definition_list;
338  file_definition_list.push_back(file_definition);
339  ConvertFileDefinitionListToEntryDefinitionList(
340      profile,
341      extension_id,
342      file_definition_list,
343      base::Bind(&OnConvertFileDefinitionDone, callback));
344}
345
346void CheckIfDirectoryExists(
347    scoped_refptr<storage::FileSystemContext> file_system_context,
348    const GURL& url,
349    const storage::FileSystemOperationRunner::StatusCallback& callback) {
350  DCHECK_CURRENTLY_ON(BrowserThread::UI);
351
352  // Check the existence of directory using file system API implementation on
353  // behalf of the file manager app. We need to grant access beforehand.
354  storage::ExternalFileSystemBackend* backend =
355      file_system_context->external_backend();
356  DCHECK(backend);
357  backend->GrantFullAccessToExtension(kFileManagerAppId);
358
359  BrowserThread::PostTask(
360      BrowserThread::IO, FROM_HERE,
361      base::Bind(&CheckIfDirectoryExistsOnIOThread,
362                 file_system_context,
363                 url,
364                 google_apis::CreateRelayCallback(callback)));
365}
366
367}  // namespace util
368}  // namespace file_manager
369