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