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 "url/gurl.h"
21 #include "webkit/browser/fileapi/file_system_context.h"
22 #include "webkit/browser/fileapi/open_file_system_mode.h"
23 #include "webkit/common/fileapi/file_system_util.h"
24
25 using content::BrowserThread;
26
27 namespace file_manager {
28 namespace util {
29
30 namespace {
31
ConvertRelativeFilePathToFileSystemUrl(const base::FilePath & relative_path,const std::string & extension_id)32 GURL ConvertRelativeFilePathToFileSystemUrl(const base::FilePath& relative_path,
33 const std::string& extension_id) {
34 GURL base_url = fileapi::GetFileSystemRootURI(
35 extensions::Extension::GetBaseURLFromExtensionId(extension_id),
36 fileapi::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|.
CreateEntryDefinitionWithError(base::File::Error error)43 EntryDefinition 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.
57 class FileDefinitionListConverter {
58 public:
59 FileDefinitionListConverter(Profile* profile,
60 const std::string& extension_id,
61 const FileDefinitionList& file_definition_list,
62 const EntryDefinitionListCallback& callback);
~FileDefinitionListConverter()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 fileapi::FileSystemInfo& info,
78 const base::FilePath& file_path,
79 fileapi::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<fileapi::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
FileDefinitionListConverter(Profile * profile,const std::string & extension_id,const FileDefinitionList & file_definition_list,const EntryDefinitionListCallback & callback)94 FileDefinitionListConverter::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
ConvertNextIterator(scoped_ptr<FileDefinitionListConverter> self_deleter,FileDefinitionList::const_iterator iterator)120 void 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 fileapi::FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
139 extensions::Extension::GetBaseURLFromExtensionId(extension_id_),
140 fileapi::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
OnResolvedURL(scoped_ptr<FileDefinitionListConverter> self_deleter,FileDefinitionList::const_iterator iterator,base::File::Error error,const fileapi::FileSystemInfo & info,const base::FilePath & file_path,fileapi::FileSystemContext::ResolvedEntryType type)154 void FileDefinitionListConverter::OnResolvedURL(
155 scoped_ptr<FileDefinitionListConverter> self_deleter,
156 FileDefinitionList::const_iterator iterator,
157 base::File::Error error,
158 const fileapi::FileSystemInfo& info,
159 const base::FilePath& file_path,
160 fileapi::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 fileapi::FileSystemContext::RESOLVED_ENTRY_FILE:
175 entry_definition.is_directory = false;
176 break;
177 case fileapi::FileSystemContext::RESOLVED_ENTRY_DIRECTORY:
178 entry_definition.is_directory = true;
179 break;
180 case fileapi::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
OnIteratorConverted(scoped_ptr<FileDefinitionListConverter> self_deleter,FileDefinitionList::const_iterator iterator,const EntryDefinition & entry_definition)199 void 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.
OnConvertFileDefinitionDone(const EntryDefinitionCallback & callback,scoped_ptr<EntryDefinitionList> entry_definition_list)209 void 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().
CheckIfDirectoryExistsOnIOThread(scoped_refptr<fileapi::FileSystemContext> file_system_context,const GURL & url,const fileapi::FileSystemOperationRunner::StatusCallback & callback)217 void CheckIfDirectoryExistsOnIOThread(
218 scoped_refptr<fileapi::FileSystemContext> file_system_context,
219 const GURL& url,
220 const fileapi::FileSystemOperationRunner::StatusCallback& callback) {
221 DCHECK_CURRENTLY_ON(BrowserThread::IO);
222
223 fileapi::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
EntryDefinition()230 EntryDefinition::EntryDefinition() {
231 }
232
~EntryDefinition()233 EntryDefinition::~EntryDefinition() {
234 }
235
GetFileSystemContextForExtensionId(Profile * profile,const std::string & extension_id)236 fileapi::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
GetFileSystemContextForRenderViewHost(Profile * profile,content::RenderViewHost * render_view_host)244 fileapi::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
ConvertDrivePathToRelativeFileSystemPath(Profile * profile,const std::string & extension_id,const base::FilePath & drive_path)252 base::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
ConvertDrivePathToFileSystemUrl(Profile * profile,const base::FilePath & drive_path,const std::string & extension_id)269 GURL 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
ConvertAbsoluteFilePathToFileSystemUrl(Profile * profile,const base::FilePath & absolute_path,const std::string & extension_id,GURL * url)280 bool 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
ConvertAbsoluteFilePathToRelativeFileSystemPath(Profile * profile,const std::string & extension_id,const base::FilePath & absolute_path,base::FilePath * virtual_path)295 bool 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 fileapi::ExternalFileSystemBackend* backend =
305 content::BrowserContext::GetStoragePartitionForSite(profile, site)->
306 GetFileSystemContext()->external_backend();
307 if (!backend)
308 return false;
309
310 // Find if this file path is managed by the external backend.
311 if (!backend->GetVirtualPath(absolute_path, virtual_path))
312 return false;
313
314 return true;
315 }
316
ConvertFileDefinitionListToEntryDefinitionList(Profile * profile,const std::string & extension_id,const FileDefinitionList & file_definition_list,const EntryDefinitionListCallback & callback)317 void ConvertFileDefinitionListToEntryDefinitionList(
318 Profile* profile,
319 const std::string& extension_id,
320 const FileDefinitionList& file_definition_list,
321 const EntryDefinitionListCallback& callback) {
322 DCHECK_CURRENTLY_ON(BrowserThread::UI);
323
324 // The converter object destroys itself.
325 new FileDefinitionListConverter(
326 profile, extension_id, file_definition_list, callback);
327 }
328
ConvertFileDefinitionToEntryDefinition(Profile * profile,const std::string & extension_id,const FileDefinition & file_definition,const EntryDefinitionCallback & callback)329 void ConvertFileDefinitionToEntryDefinition(
330 Profile* profile,
331 const std::string& extension_id,
332 const FileDefinition& file_definition,
333 const EntryDefinitionCallback& callback) {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI);
335
336 FileDefinitionList file_definition_list;
337 file_definition_list.push_back(file_definition);
338 ConvertFileDefinitionListToEntryDefinitionList(
339 profile,
340 extension_id,
341 file_definition_list,
342 base::Bind(&OnConvertFileDefinitionDone, callback));
343 }
344
CheckIfDirectoryExists(scoped_refptr<fileapi::FileSystemContext> file_system_context,const GURL & url,const fileapi::FileSystemOperationRunner::StatusCallback & callback)345 void CheckIfDirectoryExists(
346 scoped_refptr<fileapi::FileSystemContext> file_system_context,
347 const GURL& url,
348 const fileapi::FileSystemOperationRunner::StatusCallback& callback) {
349 DCHECK_CURRENTLY_ON(BrowserThread::UI);
350
351 // Check the existence of directory using file system API implementation on
352 // behalf of the file manager app. We need to grant access beforehand.
353 fileapi::ExternalFileSystemBackend* backend =
354 file_system_context->external_backend();
355 DCHECK(backend);
356 backend->GrantFullAccessToExtension(kFileManagerAppId);
357
358 BrowserThread::PostTask(
359 BrowserThread::IO, FROM_HERE,
360 base::Bind(&CheckIfDirectoryExistsOnIOThread,
361 file_system_context,
362 url,
363 google_apis::CreateRelayCallback(callback)));
364 }
365
366 } // namespace util
367 } // namespace file_manager
368