1 // Copyright 2018 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 "chromeos/components/drivefs/fake_drivefs.h"
6 
7 #include <tuple>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/files/file.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_util.h"
15 #include "base/no_destructor.h"
16 #include "base/numerics/safe_conversions.h"
17 #include "base/stl_util.h"
18 #include "base/strings/strcat.h"
19 #include "base/strings/string_util.h"
20 #include "base/task/post_task.h"
21 #include "base/task/thread_pool.h"
22 #include "base/threading/thread_restrictions.h"
23 #include "chromeos/components/drivefs/drivefs_util.h"
24 #include "chromeos/components/drivefs/mojom/drivefs.mojom.h"
25 #include "chromeos/dbus/dbus_thread_manager.h"
26 #include "chromeos/dbus/fake_cros_disks_client.h"
27 #include "mojo/public/cpp/bindings/pending_remote.h"
28 #include "mojo/public/cpp/bindings/remote.h"
29 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
30 #include "net/base/mime_util.h"
31 #include "url/gurl.h"
32 
33 namespace drivefs {
34 namespace {
35 
36 std::vector<std::pair<base::RepeatingCallback<std::string()>,
37                       base::WeakPtr<FakeDriveFs>>>&
GetRegisteredFakeDriveFsIntances()38 GetRegisteredFakeDriveFsIntances() {
39   static base::NoDestructor<std::vector<std::pair<
40       base::RepeatingCallback<std::string()>, base::WeakPtr<FakeDriveFs>>>>
41       registered_fake_drivefs_instances;
42   return *registered_fake_drivefs_instances;
43 }
44 
MaybeMountDriveFs(const std::string & source_path,const std::vector<std::string> & mount_options)45 base::FilePath MaybeMountDriveFs(
46     const std::string& source_path,
47     const std::vector<std::string>& mount_options) {
48   GURL source_url(source_path);
49   DCHECK(source_url.is_valid());
50   if (source_url.scheme() != "drivefs") {
51     return {};
52   }
53   std::string datadir_suffix;
54   for (const auto& option : mount_options) {
55     if (base::StartsWith(option, "datadir=", base::CompareCase::SENSITIVE)) {
56       auto datadir =
57           base::FilePath(base::StringPiece(option).substr(strlen("datadir=")));
58       CHECK(datadir.IsAbsolute());
59       CHECK(!datadir.ReferencesParent());
60       datadir_suffix = datadir.BaseName().value();
61       break;
62     }
63   }
64   CHECK(!datadir_suffix.empty());
65   for (auto& registration : GetRegisteredFakeDriveFsIntances()) {
66     std::string account_id = registration.first.Run();
67     if (registration.second && !account_id.empty() &&
68         account_id == datadir_suffix) {
69       return registration.second->mount_path();
70     }
71   }
72   NOTREACHED() << datadir_suffix;
73   return {};
74 }
75 
76 }  // namespace
77 
FakeDriveFsBootstrapListener(mojo::PendingRemote<drivefs::mojom::DriveFsBootstrap> bootstrap)78 FakeDriveFsBootstrapListener::FakeDriveFsBootstrapListener(
79     mojo::PendingRemote<drivefs::mojom::DriveFsBootstrap> bootstrap)
80     : bootstrap_(std::move(bootstrap)) {}
81 
82 FakeDriveFsBootstrapListener::~FakeDriveFsBootstrapListener() = default;
83 
SendInvitationOverPipe(base::ScopedFD)84 void FakeDriveFsBootstrapListener::SendInvitationOverPipe(base::ScopedFD) {}
85 
86 mojo::PendingRemote<mojom::DriveFsBootstrap>
bootstrap()87 FakeDriveFsBootstrapListener::bootstrap() {
88   return std::move(bootstrap_);
89 }
90 
91 struct FakeDriveFs::FileMetadata {
92   std::string mime_type;
93   bool pinned = false;
94   bool hosted = false;
95   bool shared = false;
96   std::string original_name;
97   mojom::Capabilities capabilities;
98   mojom::FolderFeature folder_feature;
99   std::string doc_id;
100 };
101 
102 class FakeDriveFs::SearchQuery : public mojom::SearchQuery {
103  public:
SearchQuery(base::WeakPtr<FakeDriveFs> drive_fs,drivefs::mojom::QueryParametersPtr params)104   SearchQuery(base::WeakPtr<FakeDriveFs> drive_fs,
105               drivefs::mojom::QueryParametersPtr params)
106       : drive_fs_(std::move(drive_fs)), params_(std::move(params)) {}
107 
108  private:
GetNextPage(GetNextPageCallback callback)109   void GetNextPage(GetNextPageCallback callback) override {
110     if (!drive_fs_) {
111       std::move(callback).Run(drive::FileError::FILE_ERROR_ABORT, {});
112     } else {
113       // Default implementation: just search for a file name.
114       callback_ = std::move(callback);
115       base::ThreadPool::PostTaskAndReplyWithResult(
116           FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
117           base::BindOnce(&SearchQuery::SearchFiles, drive_fs_->mount_path()),
118           base::BindOnce(&SearchQuery::GetMetadata,
119                          weak_ptr_factory_.GetWeakPtr()));
120     }
121   }
122 
SearchFiles(const base::FilePath & mount_path)123   static std::vector<drivefs::mojom::QueryItemPtr> SearchFiles(
124       const base::FilePath& mount_path) {
125     std::vector<drivefs::mojom::QueryItemPtr> results;
126     base::FileEnumerator walker(
127         mount_path, true,
128         base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
129     for (auto file = walker.Next(); !file.empty(); file = walker.Next()) {
130       auto item = drivefs::mojom::QueryItem::New();
131       item->path = base::FilePath("/");
132       CHECK(mount_path.AppendRelativePath(file, &item->path));
133       results.push_back(std::move(item));
134     }
135     return results;
136   }
137 
GetMetadata(std::vector<drivefs::mojom::QueryItemPtr> results)138   void GetMetadata(std::vector<drivefs::mojom::QueryItemPtr> results) {
139     if (!drive_fs_) {
140       std::move(callback_).Run(drive::FileError::FILE_ERROR_ABORT, {});
141     } else {
142       results_ = std::move(results);
143       pending_callbacks_ = results_.size() + 1;
144       for (size_t i = 0; i < results_.size(); ++i) {
145         drive_fs_->GetMetadata(
146             results_[i]->path,
147             base::BindOnce(&SearchQuery::OnMetadata,
148                            weak_ptr_factory_.GetWeakPtr(), i));
149       }
150       OnComplete();
151     }
152   }
153 
OnMetadata(size_t index,drive::FileError error,drivefs::mojom::FileMetadataPtr metadata)154   void OnMetadata(size_t index,
155                   drive::FileError error,
156                   drivefs::mojom::FileMetadataPtr metadata) {
157     if (error == drive::FileError::FILE_ERROR_OK) {
158       results_[index]->metadata = std::move(metadata);
159     }
160     OnComplete();
161   }
162 
OnComplete()163   void OnComplete() {
164     if (--pending_callbacks_ == 0) {
165       auto query = base::ToLowerASCII(
166           params_->title.value_or(params_->text_content.value_or("")));
167 
168       // Filter out non-matching results.
169       base::EraseIf(results_, [=](const auto& item_ptr) {
170         if (!item_ptr->metadata) {
171           return true;
172         }
173         const base::FilePath path = item_ptr->path;
174         const drivefs::mojom::FileMetadata* metadata = item_ptr->metadata.get();
175         if (!query.empty()) {
176           return base::ToLowerASCII(path.BaseName().value()).find(query) ==
177                  std::string::npos;
178         }
179         if (params_->available_offline) {
180           return !metadata->available_offline && IsLocal(metadata->type);
181         }
182         if (params_->shared_with_me) {
183           return !metadata->shared;
184         }
185         if (params_->mime_type.has_value()) {
186           return !net::MatchesMimeType(params_->mime_type.value() + "/*",
187                                        metadata->content_mime_type);
188         }
189         return false;
190       });
191 
192       const auto sort_direction = params_->sort_direction;
193       switch (params_->sort_field) {
194         case mojom::QueryParameters::SortField::kLastModified:
195         case mojom::QueryParameters::SortField::kLastViewedByMe:
196           std::sort(
197               results_.begin(), results_.end(),
198               [sort_direction](const auto& a, const auto& b) {
199                 auto a_fields = std::tie(a->metadata->last_viewed_by_me_time,
200                                          a->metadata->modification_time);
201                 auto b_fields = std::tie(b->metadata->last_viewed_by_me_time,
202                                          b->metadata->modification_time);
203                 if (sort_direction ==
204                     mojom::QueryParameters::SortDirection::kAscending) {
205                   return a_fields < b_fields;
206                 }
207                 return b_fields < a_fields;
208               });
209           break;
210 
211         case mojom::QueryParameters::SortField::kFileSize:
212           NOTIMPLEMENTED();
213           break;
214 
215         case mojom::QueryParameters::SortField::kNone:
216           break;
217       }
218 
219       auto page_size = base::saturated_cast<size_t>(params_->page_size);
220       if (results_.size() > page_size) {
221         results_.resize(page_size);
222       }
223       std::move(callback_).Run(drive::FileError::FILE_ERROR_OK,
224                                {std::move(results_)});
225     }
226   }
227 
228   base::WeakPtr<FakeDriveFs> drive_fs_;
229   mojom::QueryParametersPtr params_;
230   GetNextPageCallback callback_;
231   std::vector<drivefs::mojom::QueryItemPtr> results_;
232   size_t pending_callbacks_ = 0;
233 
234   base::WeakPtrFactory<SearchQuery> weak_ptr_factory_{this};
235 
236   DISALLOW_COPY_AND_ASSIGN(SearchQuery);
237 };
238 
FakeDriveFs(const base::FilePath & mount_path)239 FakeDriveFs::FakeDriveFs(const base::FilePath& mount_path)
240     : mount_path_(mount_path) {
241   CHECK(mount_path.IsAbsolute());
242   CHECK(!mount_path.ReferencesParent());
243 }
244 
245 FakeDriveFs::~FakeDriveFs() = default;
246 
RegisterMountingForAccountId(base::RepeatingCallback<std::string ()> account_id_getter)247 void FakeDriveFs::RegisterMountingForAccountId(
248     base::RepeatingCallback<std::string()> account_id_getter) {
249   chromeos::DBusThreadManager* dbus_thread_manager =
250       chromeos::DBusThreadManager::Get();
251   static_cast<chromeos::FakeCrosDisksClient*>(
252       dbus_thread_manager->GetCrosDisksClient())
253       ->AddCustomMountPointCallback(base::BindRepeating(&MaybeMountDriveFs));
254 
255   GetRegisteredFakeDriveFsIntances().emplace_back(std::move(account_id_getter),
256                                                   weak_factory_.GetWeakPtr());
257 }
258 
259 std::unique_ptr<drivefs::DriveFsBootstrapListener>
CreateMojoListener()260 FakeDriveFs::CreateMojoListener() {
261   delegate_.reset();
262   pending_delegate_receiver_ = delegate_.BindNewPipeAndPassReceiver();
263   delegate_->OnMounted();
264 
265   bootstrap_receiver_.reset();
266   return std::make_unique<FakeDriveFsBootstrapListener>(
267       bootstrap_receiver_.BindNewPipeAndPassRemote());
268 }
269 
SetMetadata(const base::FilePath & path,const std::string & mime_type,const std::string & original_name,bool pinned,bool shared,const mojom::Capabilities & capabilities,const mojom::FolderFeature & folder_feature,const std::string & doc_id)270 void FakeDriveFs::SetMetadata(const base::FilePath& path,
271                               const std::string& mime_type,
272                               const std::string& original_name,
273                               bool pinned,
274                               bool shared,
275                               const mojom::Capabilities& capabilities,
276                               const mojom::FolderFeature& folder_feature,
277                               const std::string& doc_id) {
278   auto& stored_metadata = metadata_[path];
279   stored_metadata.mime_type = mime_type;
280   stored_metadata.original_name = original_name;
281   stored_metadata.hosted = (original_name != path.BaseName().value());
282   stored_metadata.capabilities = capabilities;
283   stored_metadata.folder_feature = folder_feature;
284   stored_metadata.doc_id = doc_id;
285   if (pinned) {
286     stored_metadata.pinned = true;
287   }
288   if (shared) {
289     stored_metadata.shared = true;
290   }
291 }
292 
Init(drivefs::mojom::DriveFsConfigurationPtr config,mojo::PendingReceiver<drivefs::mojom::DriveFs> receiver,mojo::PendingRemote<drivefs::mojom::DriveFsDelegate> delegate)293 void FakeDriveFs::Init(
294     drivefs::mojom::DriveFsConfigurationPtr config,
295     mojo::PendingReceiver<drivefs::mojom::DriveFs> receiver,
296     mojo::PendingRemote<drivefs::mojom::DriveFsDelegate> delegate) {
297   {
298     base::ScopedAllowBlockingForTesting allow_io;
299     CHECK(base::CreateDirectory(mount_path_.Append(".Trash")));
300   }
301   mojo::FusePipes(std::move(pending_delegate_receiver_), std::move(delegate));
302   receiver_.reset();
303   receiver_.Bind(std::move(receiver));
304 }
305 
GetMetadata(const base::FilePath & path,GetMetadataCallback callback)306 void FakeDriveFs::GetMetadata(const base::FilePath& path,
307                               GetMetadataCallback callback) {
308   base::FilePath absolute_path = mount_path_;
309   CHECK(base::FilePath("/").AppendRelativePath(path, &absolute_path));
310   base::File::Info info;
311   {
312     base::ScopedAllowBlockingForTesting allow_io;
313     if (!base::GetFileInfo(absolute_path, &info)) {
314       std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND, nullptr);
315       return;
316     }
317   }
318   auto metadata = drivefs::mojom::FileMetadata::New();
319   metadata->size = info.size;
320   metadata->modification_time = info.last_modified;
321   metadata->last_viewed_by_me_time = info.last_accessed;
322 
323   const auto& stored_metadata = metadata_[path];
324   metadata->pinned = stored_metadata.pinned;
325   metadata->available_offline = stored_metadata.pinned;
326   metadata->shared = stored_metadata.shared;
327 
328   metadata->content_mime_type = stored_metadata.mime_type;
329   metadata->type = stored_metadata.hosted
330                        ? mojom::FileMetadata::Type::kHosted
331                        : info.is_directory
332                              ? mojom::FileMetadata::Type::kDirectory
333                              : mojom::FileMetadata::Type::kFile;
334 
335   base::StringPiece prefix;
336   if (stored_metadata.hosted) {
337     prefix = "https://document_alternate_link/";
338   } else if (info.is_directory) {
339     prefix = "https://folder_alternate_link/";
340   } else {
341     prefix = "https://file_alternate_link/";
342   }
343   std::string suffix = stored_metadata.original_name.empty()
344                            ? path.BaseName().value()
345                            : stored_metadata.original_name;
346   metadata->alternate_url = GURL(base::StrCat({prefix, suffix})).spec();
347   metadata->capabilities = stored_metadata.capabilities.Clone();
348 
349   std::move(callback).Run(drive::FILE_ERROR_OK, std::move(metadata));
350 }
351 
SetPinned(const base::FilePath & path,bool pinned,SetPinnedCallback callback)352 void FakeDriveFs::SetPinned(const base::FilePath& path,
353                             bool pinned,
354                             SetPinnedCallback callback) {
355   metadata_[path].pinned = pinned;
356   std::move(callback).Run(drive::FILE_ERROR_OK);
357 }
358 
UpdateNetworkState(bool pause_syncing,bool is_offline)359 void FakeDriveFs::UpdateNetworkState(bool pause_syncing, bool is_offline) {}
360 
ResetCache(ResetCacheCallback callback)361 void FakeDriveFs::ResetCache(ResetCacheCallback callback) {
362   std::move(callback).Run(drive::FILE_ERROR_OK);
363 }
364 
GetThumbnail(const base::FilePath & path,bool crop_to_square,GetThumbnailCallback callback)365 void FakeDriveFs::GetThumbnail(const base::FilePath& path,
366                                bool crop_to_square,
367                                GetThumbnailCallback callback) {
368   std::move(callback).Run(base::nullopt);
369 }
370 
CopyFile(const base::FilePath & source,const base::FilePath & target,CopyFileCallback callback)371 void FakeDriveFs::CopyFile(const base::FilePath& source,
372                            const base::FilePath& target,
373                            CopyFileCallback callback) {
374   base::ScopedAllowBlockingForTesting allow_io;
375   base::FilePath source_absolute_path = mount_path_;
376   base::FilePath target_absolute_path = mount_path_;
377   CHECK(base::FilePath("/").AppendRelativePath(source, &source_absolute_path));
378   CHECK(base::FilePath("/").AppendRelativePath(target, &target_absolute_path));
379 
380   base::File::Info source_info;
381   if (!base::GetFileInfo(source_absolute_path, &source_info)) {
382     std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND);
383     return;
384   }
385   if (source_info.is_directory) {
386     std::move(callback).Run(drive::FILE_ERROR_NOT_A_FILE);
387     return;
388   }
389 
390   base::File::Info target_directory_info;
391   if (!base::GetFileInfo(target_absolute_path.DirName(),
392                          &target_directory_info)) {
393     std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND);
394     return;
395   }
396   if (!target_directory_info.is_directory) {
397     std::move(callback).Run(drive::FILE_ERROR_INVALID_OPERATION);
398     return;
399   }
400 
401   if (base::PathExists(target_absolute_path)) {
402     std::move(callback).Run(drive::FILE_ERROR_INVALID_OPERATION);
403     return;
404   }
405 
406   if (!base::CopyFile(source_absolute_path, target_absolute_path)) {
407     std::move(callback).Run(drive::FILE_ERROR_FAILED);
408     return;
409   }
410   metadata_[target_absolute_path] = metadata_[source_absolute_path];
411   std::move(callback).Run(drive::FILE_ERROR_OK);
412 }
413 
StartSearchQuery(mojo::PendingReceiver<drivefs::mojom::SearchQuery> receiver,drivefs::mojom::QueryParametersPtr query_params)414 void FakeDriveFs::StartSearchQuery(
415     mojo::PendingReceiver<drivefs::mojom::SearchQuery> receiver,
416     drivefs::mojom::QueryParametersPtr query_params) {
417   auto search_query = std::make_unique<SearchQuery>(weak_factory_.GetWeakPtr(),
418                                                     std::move(query_params));
419   mojo::MakeSelfOwnedReceiver(std::move(search_query), std::move(receiver));
420 }
421 
FetchAllChangeLogs()422 void FakeDriveFs::FetchAllChangeLogs() {}
423 
FetchChangeLog(std::vector<mojom::FetchChangeLogOptionsPtr> options)424 void FakeDriveFs::FetchChangeLog(
425     std::vector<mojom::FetchChangeLogOptionsPtr> options) {}
426 
SendNativeMessageRequest(const std::string & request,SendNativeMessageRequestCallback callback)427 void FakeDriveFs::SendNativeMessageRequest(
428     const std::string& request,
429     SendNativeMessageRequestCallback callback) {
430   std::move(callback).Run(drive::FILE_ERROR_SERVICE_UNAVAILABLE, "");
431 }
432 
SetStartupArguments(const std::string & arguments,SetStartupArgumentsCallback callback)433 void FakeDriveFs::SetStartupArguments(const std::string& arguments,
434                                       SetStartupArgumentsCallback callback) {
435   std::move(callback).Run(false);
436 }
437 
GetStartupArguments(GetStartupArgumentsCallback callback)438 void FakeDriveFs::GetStartupArguments(GetStartupArgumentsCallback callback) {
439   std::move(callback).Run("");
440 }
441 
SetTracingEnabled(bool enabled)442 void FakeDriveFs::SetTracingEnabled(bool enabled) {}
443 
SetNetworkingEnabled(bool enabled)444 void FakeDriveFs::SetNetworkingEnabled(bool enabled) {}
445 
ForcePauseSyncing(bool enable)446 void FakeDriveFs::ForcePauseSyncing(bool enable) {}
447 
DumpAccountSettings()448 void FakeDriveFs::DumpAccountSettings() {}
449 
LoadAccountSettings()450 void FakeDriveFs::LoadAccountSettings() {}
451 
CreateNativeHostSession(drivefs::mojom::ExtensionConnectionParamsPtr params,mojo::PendingReceiver<drivefs::mojom::NativeMessagingHost> session,mojo::PendingRemote<drivefs::mojom::NativeMessagingPort> port)452 void FakeDriveFs::CreateNativeHostSession(
453     drivefs::mojom::ExtensionConnectionParamsPtr params,
454     mojo::PendingReceiver<drivefs::mojom::NativeMessagingHost> session,
455     mojo::PendingRemote<drivefs::mojom::NativeMessagingPort> port) {}
456 
LocateFilesByItemIds(const std::vector<std::string> & item_ids,drivefs::mojom::DriveFs::LocateFilesByItemIdsCallback callback)457 void FakeDriveFs::LocateFilesByItemIds(
458     const std::vector<std::string>& item_ids,
459     drivefs::mojom::DriveFs::LocateFilesByItemIdsCallback callback) {
460   base::flat_map<std::string, base::FilePath> results;
461   {
462     base::ScopedAllowBlockingForTesting allow_io;
463     base::FileEnumerator enumerator(
464         mount_path_, true,
465         base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
466     base::FilePath path = enumerator.Next();
467     while (!path.empty()) {
468       base::FilePath relative_path;
469       CHECK(mount_path_.AppendRelativePath(path, &relative_path));
470       const auto& stored_metadata =
471           metadata_[base::FilePath("/").Append(relative_path)];
472       if (!stored_metadata.doc_id.empty() &&
473           base::Contains(item_ids, stored_metadata.doc_id)) {
474         results[stored_metadata.doc_id] = relative_path;
475       }
476       path = enumerator.Next();
477     }
478   }
479   std::vector<drivefs::mojom::FilePathOrErrorPtr> response;
480   for (const auto& id : item_ids) {
481     auto it = results.find(id);
482     if (it == results.end()) {
483       response.push_back(drivefs::mojom::FilePathOrError::NewError(
484           drive::FileError::FILE_ERROR_NOT_FOUND));
485     } else {
486       response.push_back(drivefs::mojom::FilePathOrError::NewPath(it->second));
487     }
488   }
489   std::move(callback).Run(std::move(response));
490 }
491 
492 }  // namespace drivefs
493