1 // Copyright (c) 2012 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 // Implements the Chrome Extensions Media Galleries API.
6
7 #include "chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.h"
8
9 #include <stddef.h>
10
11 #include <memory>
12 #include <set>
13 #include <string>
14 #include <utility>
15 #include <vector>
16
17 #include "base/bind.h"
18 #include "base/callback.h"
19 #include "base/lazy_instance.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/stl_util.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/values.h"
26 #include "chrome/browser/apps/platform_apps/api/media_galleries/blob_data_source_factory.h"
27 #include "chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api_util.h"
28 #include "chrome/browser/browser_process.h"
29 #include "chrome/browser/extensions/extension_tab_util.h"
30 #include "chrome/browser/media_galleries/gallery_watch_manager.h"
31 #include "chrome/browser/media_galleries/media_file_system_registry.h"
32 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
33 #include "chrome/browser/media_galleries/media_galleries_permission_controller.h"
34 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
35 #include "chrome/browser/platform_util.h"
36 #include "chrome/browser/profiles/profile.h"
37 #include "chrome/browser/ui/chrome_select_file_policy.h"
38 #include "chrome/common/apps/platform_apps/api/media_galleries.h"
39 #include "chrome/common/apps/platform_apps/media_galleries_permission.h"
40 #include "chrome/common/pref_names.h"
41 #include "chrome/grit/generated_resources.h"
42 #include "chrome/services/media_gallery_util/public/cpp/safe_media_metadata_parser.h"
43 #include "components/storage_monitor/storage_info.h"
44 #include "components/web_modal/web_contents_modal_dialog_manager.h"
45 #include "content/public/browser/blob_handle.h"
46 #include "content/public/browser/browser_context.h"
47 #include "content/public/browser/browser_thread.h"
48 #include "content/public/browser/child_process_security_policy.h"
49 #include "content/public/browser/render_frame_host.h"
50 #include "content/public/browser/render_process_host.h"
51 #include "content/public/browser/render_view_host.h"
52 #include "content/public/browser/web_contents.h"
53 #include "extensions/browser/api/file_system/file_system_api.h"
54 #include "extensions/browser/app_window/app_window.h"
55 #include "extensions/browser/app_window/app_window_registry.h"
56 #include "extensions/browser/blob_holder.h"
57 #include "extensions/browser/blob_reader.h"
58 #include "extensions/browser/extension_prefs.h"
59 #include "extensions/browser/extension_system.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/permissions/api_permission.h"
62 #include "extensions/common/permissions/permissions_data.h"
63 #include "net/base/mime_sniffer.h"
64 #include "storage/browser/blob/blob_data_handle.h"
65 #include "ui/base/l10n/l10n_util.h"
66
67 using content::WebContents;
68 using storage_monitor::MediaStorageUtil;
69 using storage_monitor::StorageInfo;
70
71 namespace chrome_apps {
72 namespace api {
73
74 namespace MediaGalleries = media_galleries;
75 namespace GetMediaFileSystems = MediaGalleries::GetMediaFileSystems;
76 namespace AddGalleryWatch = MediaGalleries::AddGalleryWatch;
77 namespace RemoveGalleryWatch = MediaGalleries::RemoveGalleryWatch;
78
79 namespace {
80
81 const char kDisallowedByPolicy[] =
82 "Media Galleries API is disallowed by policy: ";
83 const char kInvalidGalleryIdMsg[] = "Invalid gallery id.";
84 const char kMissingEventListener[] = "Missing event listener registration.";
85
86 const char kDeviceIdKey[] = "deviceId";
87 const char kGalleryIdKey[] = "galleryId";
88 const char kIsAvailableKey[] = "isAvailable";
89 const char kIsMediaDeviceKey[] = "isMediaDevice";
90 const char kIsRemovableKey[] = "isRemovable";
91 const char kNameKey[] = "name";
92
93 const char kMetadataKey[] = "metadata";
94 const char kAttachedImagesBlobInfoKey[] = "attachedImagesBlobInfo";
95 const char kBlobUUIDKey[] = "blobUUID";
96 const char kMediaGalleriesApiTypeKey[] = "type";
97 const char kSizeKey[] = "size";
98
99 const char kInvalidGalleryId[] = "-1";
100
101 const char kNoRenderFrameOrRenderProcessError[] =
102 "No render frame or render process.";
103 const char kNoWebContentsError[] = "Could not find web contents.";
104
media_file_system_registry()105 MediaFileSystemRegistry* media_file_system_registry() {
106 return g_browser_process->media_file_system_registry();
107 }
108
gallery_watch_manager()109 GalleryWatchManager* gallery_watch_manager() {
110 return media_file_system_registry()->gallery_watch_manager();
111 }
112
113 // Checks whether the MediaGalleries API is currently accessible (it may be
114 // disallowed even if an extension has the requisite permission). Then
115 // initializes the MediaGalleriesPreferences
Setup(Profile * profile,std::string * error,base::OnceClosure callback)116 bool Setup(Profile* profile, std::string* error, base::OnceClosure callback) {
117 if (!ChromeSelectFilePolicy::FileSelectDialogsAllowed()) {
118 *error =
119 std::string(kDisallowedByPolicy) + prefs::kAllowFileSelectionDialogs;
120 return false;
121 }
122
123 MediaGalleriesPreferences* preferences =
124 media_file_system_registry()->GetPreferences(profile);
125 preferences->EnsureInitialized(std::move(callback));
126 return true;
127 }
128
129 // Returns true and sets |gallery_file_path| and |gallery_pref_id| if the
130 // |gallery_id| is valid and returns false otherwise.
GetGalleryFilePathAndId(const std::string & gallery_id,Profile * profile,const extensions::Extension * extension,base::FilePath * gallery_file_path,MediaGalleryPrefId * gallery_pref_id)131 bool GetGalleryFilePathAndId(const std::string& gallery_id,
132 Profile* profile,
133 const extensions::Extension* extension,
134 base::FilePath* gallery_file_path,
135 MediaGalleryPrefId* gallery_pref_id) {
136 MediaGalleryPrefId pref_id;
137 if (!base::StringToUint64(gallery_id, &pref_id))
138 return false;
139 MediaGalleriesPreferences* preferences =
140 g_browser_process->media_file_system_registry()->GetPreferences(profile);
141 base::FilePath file_path(
142 preferences->LookUpGalleryPathForExtension(pref_id, extension, false));
143 if (file_path.empty())
144 return false;
145 *gallery_pref_id = pref_id;
146 *gallery_file_path = file_path;
147 return true;
148 }
149
ConstructFileSystemList(content::RenderFrameHost * rfh,const extensions::Extension * extension,const std::vector<MediaFileSystemInfo> & filesystems)150 base::ListValue* ConstructFileSystemList(
151 content::RenderFrameHost* rfh,
152 const extensions::Extension* extension,
153 const std::vector<MediaFileSystemInfo>& filesystems) {
154 if (!rfh)
155 return NULL;
156
157 MediaGalleriesPermission::CheckParam read_param(
158 MediaGalleriesPermission::kReadPermission);
159 const extensions::PermissionsData* permissions_data =
160 extension->permissions_data();
161 bool has_read_permission = permissions_data->CheckAPIPermissionWithParam(
162 extensions::APIPermission::kMediaGalleries, &read_param);
163 MediaGalleriesPermission::CheckParam copy_to_param(
164 MediaGalleriesPermission::kCopyToPermission);
165 bool has_copy_to_permission = permissions_data->CheckAPIPermissionWithParam(
166 extensions::APIPermission::kMediaGalleries, ©_to_param);
167 MediaGalleriesPermission::CheckParam delete_param(
168 MediaGalleriesPermission::kDeletePermission);
169 bool has_delete_permission = permissions_data->CheckAPIPermissionWithParam(
170 extensions::APIPermission::kMediaGalleries, &delete_param);
171
172 const int child_id = rfh->GetProcess()->GetID();
173 std::unique_ptr<base::ListValue> list(new base::ListValue());
174 for (size_t i = 0; i < filesystems.size(); ++i) {
175 std::unique_ptr<base::DictionaryValue> file_system_dict_value(
176 new base::DictionaryValue());
177
178 // Send the file system id so the renderer can create a valid FileSystem
179 // object.
180 file_system_dict_value->SetKey("fsid", base::Value(filesystems[i].fsid));
181
182 file_system_dict_value->SetKey(kNameKey, base::Value(filesystems[i].name));
183 file_system_dict_value->SetKey(
184 kGalleryIdKey,
185 base::Value(base::NumberToString(filesystems[i].pref_id)));
186 if (!filesystems[i].transient_device_id.empty()) {
187 file_system_dict_value->SetKey(
188 kDeviceIdKey, base::Value(filesystems[i].transient_device_id));
189 }
190 file_system_dict_value->SetKey(kIsRemovableKey,
191 base::Value(filesystems[i].removable));
192 file_system_dict_value->SetKey(kIsMediaDeviceKey,
193 base::Value(filesystems[i].media_device));
194 file_system_dict_value->SetKey(kIsAvailableKey, base::Value(true));
195
196 list->Append(std::move(file_system_dict_value));
197
198 if (filesystems[i].path.empty())
199 continue;
200
201 if (has_read_permission) {
202 content::ChildProcessSecurityPolicy* policy =
203 content::ChildProcessSecurityPolicy::GetInstance();
204 policy->GrantReadFile(child_id, filesystems[i].path);
205 if (has_delete_permission) {
206 policy->GrantDeleteFrom(child_id, filesystems[i].path);
207 if (has_copy_to_permission) {
208 policy->GrantCopyInto(child_id, filesystems[i].path);
209 }
210 }
211 }
212 }
213
214 return list.release();
215 }
216
217 class SelectDirectoryDialog : public ui::SelectFileDialog::Listener,
218 public base::RefCounted<SelectDirectoryDialog> {
219 public:
220 // Selected file path, or an empty path if the user canceled.
221 using Callback = base::RepeatingCallback<void(const base::FilePath&)>;
222
SelectDirectoryDialog(WebContents * web_contents,Callback callback)223 SelectDirectoryDialog(WebContents* web_contents, Callback callback)
224 : web_contents_(web_contents), callback_(std::move(callback)) {
225 select_file_dialog_ = ui::SelectFileDialog::Create(
226 this, std::make_unique<ChromeSelectFilePolicy>(web_contents));
227 }
228 SelectDirectoryDialog(const SelectDirectoryDialog&) = delete;
229 SelectDirectoryDialog& operator=(const SelectDirectoryDialog&) = delete;
230
Show(const base::FilePath & default_path)231 void Show(const base::FilePath& default_path) {
232 AddRef(); // Balanced in the two reachable listener outcomes.
233 select_file_dialog_->SelectFile(
234 ui::SelectFileDialog::SELECT_FOLDER,
235 l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
236 default_path, NULL, 0, base::FilePath::StringType(),
237 platform_util::GetTopLevel(web_contents_->GetNativeView()), NULL);
238 }
239
240 // ui::SelectFileDialog::Listener implementation.
FileSelected(const base::FilePath & path,int index,void * params)241 void FileSelected(const base::FilePath& path,
242 int index,
243 void* params) override {
244 callback_.Run(path);
245 Release(); // Balanced in Show().
246 }
247
MultiFilesSelected(const std::vector<base::FilePath> & files,void * params)248 void MultiFilesSelected(const std::vector<base::FilePath>& files,
249 void* params) override {
250 NOTREACHED() << "Should not be able to select multiple files";
251 }
252
FileSelectionCanceled(void * params)253 void FileSelectionCanceled(void* params) override {
254 callback_.Run(base::FilePath());
255 Release(); // Balanced in Show().
256 }
257
258 private:
259 friend class base::RefCounted<SelectDirectoryDialog>;
260 ~SelectDirectoryDialog() override = default;
261
262 scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
263 WebContents* web_contents_;
264 Callback callback_;
265 };
266
267 // Returns a web contents to use as the source for a prompt showing to the user.
268 // The web contents has to support modal dialogs, so it can't be the app's
269 // background page.
GetWebContentsForPrompt(content::WebContents * sender_web_contents,content::BrowserContext * browser_context,const std::string & app_id)270 content::WebContents* GetWebContentsForPrompt(
271 content::WebContents* sender_web_contents,
272 content::BrowserContext* browser_context,
273 const std::string& app_id) {
274 // Check if the sender web contents supports modal dialogs.
275 if (sender_web_contents &&
276 web_modal::WebContentsModalDialogManager::FromWebContents(
277 sender_web_contents)) {
278 return sender_web_contents;
279 }
280 // Otherwise, check for the current app window for the app (app windows
281 // support modal dialogs).
282 if (!app_id.empty()) {
283 extensions::AppWindow* window =
284 extensions::AppWindowRegistry::Get(browser_context)
285 ->GetCurrentAppWindowForApp(app_id);
286 if (window)
287 return window->web_contents();
288 }
289 return nullptr;
290 }
291
292 } // namespace
293
MediaGalleriesEventRouter(content::BrowserContext * context)294 MediaGalleriesEventRouter::MediaGalleriesEventRouter(
295 content::BrowserContext* context)
296 : profile_(Profile::FromBrowserContext(context)) {
297 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
298 DCHECK(profile_);
299
300 extensions::EventRouter::Get(profile_)->RegisterObserver(
301 this, MediaGalleries::OnGalleryChanged::kEventName);
302
303 gallery_watch_manager()->AddObserver(profile_, this);
304 }
305
306 MediaGalleriesEventRouter::~MediaGalleriesEventRouter() = default;
307
Shutdown()308 void MediaGalleriesEventRouter::Shutdown() {
309 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
310 weak_ptr_factory_.InvalidateWeakPtrs();
311
312 extensions::EventRouter::Get(profile_)->UnregisterObserver(this);
313
314 gallery_watch_manager()->RemoveObserver(profile_);
315 }
316
317 static base::LazyInstance<extensions::BrowserContextKeyedAPIFactory<
318 MediaGalleriesEventRouter>>::DestructorAtExit
319 g_media_galleries_api_factory = LAZY_INSTANCE_INITIALIZER;
320
321 // static
322 extensions::BrowserContextKeyedAPIFactory<MediaGalleriesEventRouter>*
GetFactoryInstance()323 MediaGalleriesEventRouter::GetFactoryInstance() {
324 return g_media_galleries_api_factory.Pointer();
325 }
326
327 // static
Get(content::BrowserContext * context)328 MediaGalleriesEventRouter* MediaGalleriesEventRouter::Get(
329 content::BrowserContext* context) {
330 DCHECK(media_file_system_registry()
331 ->GetPreferences(Profile::FromBrowserContext(context))
332 ->IsInitialized());
333 return extensions::BrowserContextKeyedAPIFactory<
334 MediaGalleriesEventRouter>::Get(context);
335 }
336
ExtensionHasGalleryChangeListener(const std::string & extension_id) const337 bool MediaGalleriesEventRouter::ExtensionHasGalleryChangeListener(
338 const std::string& extension_id) const {
339 return extensions::EventRouter::Get(profile_)->ExtensionHasEventListener(
340 extension_id, MediaGalleries::OnGalleryChanged::kEventName);
341 }
342
DispatchEventToExtension(const std::string & extension_id,extensions::events::HistogramValue histogram_value,const std::string & event_name,std::unique_ptr<base::ListValue> event_args)343 void MediaGalleriesEventRouter::DispatchEventToExtension(
344 const std::string& extension_id,
345 extensions::events::HistogramValue histogram_value,
346 const std::string& event_name,
347 std::unique_ptr<base::ListValue> event_args) {
348 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
349
350 extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
351 if (!router->ExtensionHasEventListener(extension_id, event_name))
352 return;
353
354 std::unique_ptr<extensions::Event> event(new extensions::Event(
355 histogram_value, event_name, std::move(event_args)));
356 router->DispatchEventToExtension(extension_id, std::move(event));
357 }
358
OnGalleryChanged(const std::string & extension_id,MediaGalleryPrefId gallery_id)359 void MediaGalleriesEventRouter::OnGalleryChanged(
360 const std::string& extension_id,
361 MediaGalleryPrefId gallery_id) {
362 MediaGalleries::GalleryChangeDetails details;
363 details.type = MediaGalleries::GALLERY_CHANGE_TYPE_CONTENTS_CHANGED;
364 details.gallery_id = base::NumberToString(gallery_id);
365 DispatchEventToExtension(
366 extension_id, extensions::events::MEDIA_GALLERIES_ON_GALLERY_CHANGED,
367 MediaGalleries::OnGalleryChanged::kEventName,
368 MediaGalleries::OnGalleryChanged::Create(details));
369 }
370
OnGalleryWatchDropped(const std::string & extension_id,MediaGalleryPrefId gallery_id)371 void MediaGalleriesEventRouter::OnGalleryWatchDropped(
372 const std::string& extension_id,
373 MediaGalleryPrefId gallery_id) {
374 MediaGalleries::GalleryChangeDetails details;
375 details.type = MediaGalleries::GALLERY_CHANGE_TYPE_WATCH_DROPPED;
376 details.gallery_id = base::NumberToString(gallery_id);
377 DispatchEventToExtension(
378 extension_id, extensions::events::MEDIA_GALLERIES_ON_GALLERY_CHANGED,
379 MediaGalleries::OnGalleryChanged::kEventName,
380 MediaGalleries::OnGalleryChanged::Create(details));
381 }
382
OnListenerRemoved(const extensions::EventListenerInfo & details)383 void MediaGalleriesEventRouter::OnListenerRemoved(
384 const extensions::EventListenerInfo& details) {
385 if (details.event_name == MediaGalleries::OnGalleryChanged::kEventName &&
386 !ExtensionHasGalleryChangeListener(details.extension_id)) {
387 gallery_watch_manager()->RemoveAllWatches(profile_, details.extension_id);
388 }
389 }
390
391 ///////////////////////////////////////////////////////////////////////////////
392 // MediaGalleriesGetMediaFileSystemsFunction //
393 ///////////////////////////////////////////////////////////////////////////////
394 MediaGalleriesGetMediaFileSystemsFunction::
~MediaGalleriesGetMediaFileSystemsFunction()395 ~MediaGalleriesGetMediaFileSystemsFunction() {}
396
397 ExtensionFunction::ResponseAction
Run()398 MediaGalleriesGetMediaFileSystemsFunction::Run() {
399 ::media_galleries::UsageCount(::media_galleries::GET_MEDIA_FILE_SYSTEMS);
400 std::unique_ptr<GetMediaFileSystems::Params> params(
401 GetMediaFileSystems::Params::Create(*args_));
402 EXTENSION_FUNCTION_VALIDATE(params);
403 MediaGalleries::GetMediaFileSystemsInteractivity interactive =
404 MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO;
405 if (params->details.get() &&
406 params->details->interactive !=
407 MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE) {
408 interactive = params->details->interactive;
409 }
410
411 std::string error;
412 const bool result =
413 Setup(Profile::FromBrowserContext(browser_context()), &error,
414 base::BindOnce(
415 &MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit,
416 this, interactive));
417 if (!result)
418 return RespondNow(Error(error));
419 // Note: OnPreferencesInit might have been called already.
420 return did_respond() ? AlreadyResponded() : RespondLater();
421 }
422
OnPreferencesInit(MediaGalleries::GetMediaFileSystemsInteractivity interactive)423 void MediaGalleriesGetMediaFileSystemsFunction::OnPreferencesInit(
424 MediaGalleries::GetMediaFileSystemsInteractivity interactive) {
425 switch (interactive) {
426 case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_YES: {
427 // The MediaFileSystemRegistry only updates preferences for extensions
428 // that it knows are in use. Since this may be the first call to
429 // chrome.getMediaFileSystems for this extension, call
430 // GetMediaFileSystemsForExtension() here solely so that
431 // MediaFileSystemRegistry will send preference changes.
432 GetMediaFileSystemsForExtension(base::BindOnce(
433 &MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog, this));
434 return;
435 }
436 case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_IF_NEEDED: {
437 GetMediaFileSystemsForExtension(base::BindOnce(
438 &MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries,
439 this));
440 return;
441 }
442 case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NO:
443 GetAndReturnGalleries();
444 return;
445 case MediaGalleries::GET_MEDIA_FILE_SYSTEMS_INTERACTIVITY_NONE:
446 NOTREACHED();
447 }
448 Respond(Error("Error initializing Media Galleries preferences."));
449 }
450
AlwaysShowDialog(const std::vector<MediaFileSystemInfo> &)451 void MediaGalleriesGetMediaFileSystemsFunction::AlwaysShowDialog(
452 const std::vector<MediaFileSystemInfo>& /*filesystems*/) {
453 ShowDialog();
454 }
455
ShowDialogIfNoGalleries(const std::vector<MediaFileSystemInfo> & filesystems)456 void MediaGalleriesGetMediaFileSystemsFunction::ShowDialogIfNoGalleries(
457 const std::vector<MediaFileSystemInfo>& filesystems) {
458 if (filesystems.empty())
459 ShowDialog();
460 else
461 ReturnGalleries(filesystems);
462 }
463
GetAndReturnGalleries()464 void MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries() {
465 GetMediaFileSystemsForExtension(base::BindOnce(
466 &MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries, this));
467 }
468
ReturnGalleries(const std::vector<MediaFileSystemInfo> & filesystems)469 void MediaGalleriesGetMediaFileSystemsFunction::ReturnGalleries(
470 const std::vector<MediaFileSystemInfo>& filesystems) {
471 std::unique_ptr<base::ListValue> list(
472 ConstructFileSystemList(render_frame_host(), extension(), filesystems));
473 if (!list) {
474 Respond(Error("Error returning Media Galleries filesystems."));
475 return;
476 }
477
478 // The custom JS binding will use this list to create DOMFileSystem objects.
479 Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
480 }
481
ShowDialog()482 void MediaGalleriesGetMediaFileSystemsFunction::ShowDialog() {
483 ::media_galleries::UsageCount(::media_galleries::SHOW_DIALOG);
484 WebContents* contents = GetWebContentsForPrompt(
485 GetSenderWebContents(), browser_context(), extension()->id());
486 if (!contents) {
487 Respond(Error(kNoWebContentsError));
488 return;
489 }
490
491 // Controller will delete itself.
492 base::OnceClosure cb = base::BindOnce(
493 &MediaGalleriesGetMediaFileSystemsFunction::GetAndReturnGalleries, this);
494 new MediaGalleriesPermissionController(contents, *extension(), std::move(cb));
495 }
496
GetMediaFileSystemsForExtension(MediaFileSystemsCallback cb)497 void MediaGalleriesGetMediaFileSystemsFunction::GetMediaFileSystemsForExtension(
498 MediaFileSystemsCallback cb) {
499 if (!render_frame_host()) {
500 std::move(cb).Run(std::vector<MediaFileSystemInfo>());
501 return;
502 }
503 MediaFileSystemRegistry* registry = media_file_system_registry();
504 Profile* profile = Profile::FromBrowserContext(browser_context());
505 DCHECK(registry->GetPreferences(profile)->IsInitialized());
506 registry->GetMediaFileSystemsForExtension(GetSenderWebContents(), extension(),
507 std::move(cb));
508 }
509
510 ///////////////////////////////////////////////////////////////////////////////
511 // MediaGalleriesAddUserSelectedFolderFunction //
512 ///////////////////////////////////////////////////////////////////////////////
513 MediaGalleriesAddUserSelectedFolderFunction::
~MediaGalleriesAddUserSelectedFolderFunction()514 ~MediaGalleriesAddUserSelectedFolderFunction() {}
515
516 ExtensionFunction::ResponseAction
Run()517 MediaGalleriesAddUserSelectedFolderFunction::Run() {
518 ::media_galleries::UsageCount(::media_galleries::ADD_USER_SELECTED_FOLDER);
519 std::string error;
520 const bool result =
521 Setup(Profile::FromBrowserContext(browser_context()), &error,
522 base::BindOnce(
523 &MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit,
524 this));
525 if (!result)
526 return RespondNow(Error(error));
527 // Note: OnPreferencesInit might have been called already.
528 return did_respond() ? AlreadyResponded() : RespondLater();
529 }
530
OnPreferencesInit()531 void MediaGalleriesAddUserSelectedFolderFunction::OnPreferencesInit() {
532 const std::string& app_id = extension()->id();
533 WebContents* contents = GetWebContentsForPrompt(GetSenderWebContents(),
534 browser_context(), app_id);
535 if (!contents) {
536 Respond(Error(kNoWebContentsError));
537 return;
538 }
539
540 if (!user_gesture()) {
541 OnDirectorySelected(base::FilePath());
542 return;
543 }
544
545 base::FilePath last_used_path =
546 extensions::file_system_api::GetLastChooseEntryDirectory(
547 extensions::ExtensionPrefs::Get(browser_context()), app_id);
548 SelectDirectoryDialog::Callback callback = base::BindRepeating(
549 &MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected, this);
550 auto select_directory_dialog = base::MakeRefCounted<SelectDirectoryDialog>(
551 contents, std::move(callback));
552 select_directory_dialog->Show(last_used_path);
553 }
554
OnDirectorySelected(const base::FilePath & selected_directory)555 void MediaGalleriesAddUserSelectedFolderFunction::OnDirectorySelected(
556 const base::FilePath& selected_directory) {
557 if (selected_directory.empty()) {
558 // User cancelled case.
559 GetMediaFileSystemsForExtension(base::BindOnce(
560 &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId,
561 this, kInvalidMediaGalleryPrefId));
562 return;
563 }
564
565 extensions::file_system_api::SetLastChooseEntryDirectory(
566 extensions::ExtensionPrefs::Get(browser_context()), extension()->id(),
567 selected_directory);
568
569 MediaGalleriesPreferences* preferences =
570 media_file_system_registry()->GetPreferences(
571 Profile::FromBrowserContext(browser_context()));
572 MediaGalleryPrefId pref_id = preferences->AddGalleryByPath(
573 selected_directory, MediaGalleryPrefInfo::kUserAdded);
574 preferences->SetGalleryPermissionForExtension(*extension(), pref_id, true);
575
576 GetMediaFileSystemsForExtension(base::BindOnce(
577 &MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId, this,
578 pref_id));
579 }
580
ReturnGalleriesAndId(MediaGalleryPrefId pref_id,const std::vector<MediaFileSystemInfo> & filesystems)581 void MediaGalleriesAddUserSelectedFolderFunction::ReturnGalleriesAndId(
582 MediaGalleryPrefId pref_id,
583 const std::vector<MediaFileSystemInfo>& filesystems) {
584 std::unique_ptr<base::ListValue> list(
585 ConstructFileSystemList(render_frame_host(), extension(), filesystems));
586 if (!list.get()) {
587 Respond(Error("Error returning Media Galleries filesystems."));
588 return;
589 }
590
591 int index = -1;
592 if (pref_id != kInvalidMediaGalleryPrefId) {
593 for (size_t i = 0; i < filesystems.size(); ++i) {
594 if (filesystems[i].pref_id == pref_id) {
595 index = i;
596 break;
597 }
598 }
599 }
600 std::unique_ptr<base::DictionaryValue> results(new base::DictionaryValue);
601 results->SetWithoutPathExpansion("mediaFileSystems", std::move(list));
602 results->SetKey("selectedFileSystemIndex", base::Value(index));
603 Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(results))));
604 }
605
606 void MediaGalleriesAddUserSelectedFolderFunction::
GetMediaFileSystemsForExtension(MediaFileSystemsCallback cb)607 GetMediaFileSystemsForExtension(MediaFileSystemsCallback cb) {
608 if (!render_frame_host()) {
609 std::move(cb).Run(std::vector<MediaFileSystemInfo>());
610 return;
611 }
612 MediaFileSystemRegistry* registry = media_file_system_registry();
613 DCHECK(
614 registry->GetPreferences(Profile::FromBrowserContext(browser_context()))
615 ->IsInitialized());
616 registry->GetMediaFileSystemsForExtension(GetSenderWebContents(), extension(),
617 std::move(cb));
618 }
619
620 ///////////////////////////////////////////////////////////////////////////////
621 // MediaGalleriesGetMetadataFunction //
622 ///////////////////////////////////////////////////////////////////////////////
~MediaGalleriesGetMetadataFunction()623 MediaGalleriesGetMetadataFunction::~MediaGalleriesGetMetadataFunction() {}
624
Run()625 ExtensionFunction::ResponseAction MediaGalleriesGetMetadataFunction::Run() {
626 ::media_galleries::UsageCount(::media_galleries::GET_METADATA);
627 std::string blob_uuid;
628 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &blob_uuid));
629
630 const base::Value* options_value = NULL;
631 if (!args_->Get(1, &options_value))
632 return RespondNow(Error("options parameter not specified."));
633 std::unique_ptr<MediaGalleries::MediaMetadataOptions> options =
634 MediaGalleries::MediaMetadataOptions::FromValue(*options_value);
635 if (!options)
636 return RespondNow(Error("Invalid value for options parameter."));
637
638 std::string error;
639 const bool result = Setup(
640 Profile::FromBrowserContext(browser_context()), &error,
641 base::BindOnce(&MediaGalleriesGetMetadataFunction::OnPreferencesInit,
642 this, options->metadata_type, blob_uuid));
643 if (!result)
644 return RespondNow(Error(error));
645 // Note: OnPreferencesInit might have been called already.
646 return did_respond() ? AlreadyResponded() : RespondLater();
647 }
648
OnPreferencesInit(MediaGalleries::GetMetadataType metadata_type,const std::string & blob_uuid)649 void MediaGalleriesGetMetadataFunction::OnPreferencesInit(
650 MediaGalleries::GetMetadataType metadata_type,
651 const std::string& blob_uuid) {
652 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
653
654 BlobReader::Read(
655 browser_context(), blob_uuid,
656 base::BindOnce(&MediaGalleriesGetMetadataFunction::GetMetadata, this,
657 metadata_type, blob_uuid),
658 0, net::kMaxBytesToSniff);
659 }
660
GetMetadata(MediaGalleries::GetMetadataType metadata_type,const std::string & blob_uuid,std::unique_ptr<std::string> blob_header,int64_t total_blob_length)661 void MediaGalleriesGetMetadataFunction::GetMetadata(
662 MediaGalleries::GetMetadataType metadata_type,
663 const std::string& blob_uuid,
664 std::unique_ptr<std::string> blob_header,
665 int64_t total_blob_length) {
666 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
667
668 std::string mime_type;
669 bool mime_type_sniffed =
670 net::SniffMimeTypeFromLocalData(*blob_header, &mime_type);
671
672 if (!mime_type_sniffed) {
673 Respond(Error("Could not determine MIME type."));
674 return;
675 }
676
677 if (metadata_type == MediaGalleries::GET_METADATA_TYPE_MIMETYPEONLY) {
678 MediaGalleries::MediaMetadata metadata;
679 metadata.mime_type = mime_type;
680
681 std::unique_ptr<base::DictionaryValue> result_dictionary(
682 new base::DictionaryValue);
683 result_dictionary->Set(kMetadataKey, metadata.ToValue());
684 Respond(OneArgument(
685 base::Value::FromUniquePtrValue(std::move(result_dictionary))));
686 return;
687 }
688
689 // We get attached images by default. GET_METADATA_TYPE_NONE is the default
690 // value if the caller doesn't specify the metadata type.
691 bool get_attached_images =
692 metadata_type == MediaGalleries::GET_METADATA_TYPE_ALL ||
693 metadata_type == MediaGalleries::GET_METADATA_TYPE_NONE;
694
695 auto media_data_source_factory =
696 std::make_unique<BlobDataSourceFactory>(browser_context(), blob_uuid);
697 auto parser = std::make_unique<SafeMediaMetadataParser>(
698 total_blob_length, mime_type, get_attached_images,
699 std::move(media_data_source_factory));
700 SafeMediaMetadataParser* parser_ptr = parser.get();
701 parser_ptr->Start(
702 base::BindOnce(
703 &MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone,
704 this, std::move(parser)));
705 }
706
OnSafeMediaMetadataParserDone(std::unique_ptr<SafeMediaMetadataParser> parser_keep_alive,bool parse_success,chrome::mojom::MediaMetadataPtr metadata,std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images)707 void MediaGalleriesGetMetadataFunction::OnSafeMediaMetadataParserDone(
708 std::unique_ptr<SafeMediaMetadataParser> parser_keep_alive,
709 bool parse_success,
710 chrome::mojom::MediaMetadataPtr metadata,
711 std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images) {
712 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
713
714 if (!parse_success) {
715 Respond(Error("Could not parse media metadata."));
716 return;
717 }
718
719 DCHECK(metadata);
720 DCHECK(attached_images);
721
722 std::unique_ptr<base::DictionaryValue> result_dictionary(
723 new base::DictionaryValue);
724 result_dictionary->Set(kMetadataKey,
725 SerializeMediaMetadata(std::move(metadata)));
726
727 if (attached_images->empty()) {
728 Respond(OneArgument(
729 base::Value::FromUniquePtrValue(std::move(result_dictionary))));
730 return;
731 }
732
733 result_dictionary->Set(kAttachedImagesBlobInfoKey,
734 std::make_unique<base::ListValue>());
735 metadata::AttachedImage* first_image = &attached_images->front();
736 content::BrowserContext::CreateMemoryBackedBlob(
737 browser_context(), base::as_bytes(base::make_span(first_image->data)), "",
738 base::BindOnce(&MediaGalleriesGetMetadataFunction::ConstructNextBlob,
739 this, std::move(result_dictionary),
740 std::move(attached_images),
741 base::WrapUnique(new std::vector<std::string>)));
742 }
743
ConstructNextBlob(std::unique_ptr<base::DictionaryValue> result_dictionary,std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images,std::unique_ptr<std::vector<std::string>> blob_uuids,std::unique_ptr<content::BlobHandle> current_blob)744 void MediaGalleriesGetMetadataFunction::ConstructNextBlob(
745 std::unique_ptr<base::DictionaryValue> result_dictionary,
746 std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images,
747 std::unique_ptr<std::vector<std::string>> blob_uuids,
748 std::unique_ptr<content::BlobHandle> current_blob) {
749 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
750
751 DCHECK(result_dictionary.get());
752 DCHECK(attached_images.get());
753 DCHECK(blob_uuids.get());
754 DCHECK(current_blob.get());
755
756 DCHECK(!attached_images->empty());
757 DCHECK_LT(blob_uuids->size(), attached_images->size());
758
759 // For the newly constructed Blob, store its image's metadata and Blob UUID.
760 base::ListValue* attached_images_list = NULL;
761 result_dictionary->GetList(kAttachedImagesBlobInfoKey, &attached_images_list);
762 DCHECK(attached_images_list);
763 DCHECK_LT(attached_images_list->GetSize(), attached_images->size());
764
765 metadata::AttachedImage* current_image =
766 &(*attached_images)[blob_uuids->size()];
767 std::unique_ptr<base::DictionaryValue> attached_image(
768 new base::DictionaryValue);
769 attached_image->SetString(kBlobUUIDKey, current_blob->GetUUID());
770 attached_image->SetString(kMediaGalleriesApiTypeKey, current_image->type);
771 attached_image->SetInteger(
772 kSizeKey, base::checked_cast<int>(current_image->data.size()));
773 attached_images_list->Append(std::move(attached_image));
774
775 blob_uuids->push_back(current_blob->GetUUID());
776
777 if (!render_frame_host() || !render_frame_host()->GetProcess()) {
778 Respond(Error(kNoRenderFrameOrRenderProcessError));
779 return;
780 }
781
782 extensions::BlobHolder* holder =
783 extensions::BlobHolder::FromRenderProcessHost(
784 render_frame_host()->GetProcess());
785 holder->HoldBlobReference(std::move(current_blob));
786
787 // Construct the next Blob if necessary.
788 if (blob_uuids->size() < attached_images->size()) {
789 metadata::AttachedImage* next_image =
790 &(*attached_images)[blob_uuids->size()];
791 content::BrowserContext::CreateMemoryBackedBlob(
792 browser_context(), base::as_bytes(base::make_span(next_image->data)),
793 "",
794 base::BindOnce(&MediaGalleriesGetMetadataFunction::ConstructNextBlob,
795 this, std::move(result_dictionary),
796 std::move(attached_images), std::move(blob_uuids)));
797 return;
798 }
799
800 // All Blobs have been constructed. The renderer will take ownership.
801 SetTransferredBlobUUIDs(*blob_uuids);
802 Respond(OneArgument(
803 base::Value::FromUniquePtrValue(std::move(result_dictionary))));
804 }
805
806 ///////////////////////////////////////////////////////////////////////////////
807 // MediaGalleriesAddGalleryWatchFunction //
808 ///////////////////////////////////////////////////////////////////////////////
809 MediaGalleriesAddGalleryWatchFunction::
~MediaGalleriesAddGalleryWatchFunction()810 ~MediaGalleriesAddGalleryWatchFunction() {}
811
Run()812 ExtensionFunction::ResponseAction MediaGalleriesAddGalleryWatchFunction::Run() {
813 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
814 Profile* profile = Profile::FromBrowserContext(browser_context());
815 DCHECK(profile);
816 if (!render_frame_host() || !render_frame_host()->GetProcess())
817 return RespondNow(Error(kNoRenderFrameOrRenderProcessError));
818
819 std::unique_ptr<AddGalleryWatch::Params> params(
820 AddGalleryWatch::Params::Create(*args_));
821 EXTENSION_FUNCTION_VALIDATE(params);
822
823 MediaGalleriesPreferences* preferences =
824 g_browser_process->media_file_system_registry()->GetPreferences(profile);
825 preferences->EnsureInitialized(
826 base::BindOnce(&MediaGalleriesAddGalleryWatchFunction::OnPreferencesInit,
827 this, params->gallery_id));
828 // Note: OnPreferencesInit might have been called already.
829 return did_respond() ? AlreadyResponded() : RespondLater();
830 }
831
OnPreferencesInit(const std::string & pref_id)832 void MediaGalleriesAddGalleryWatchFunction::OnPreferencesInit(
833 const std::string& pref_id) {
834 base::FilePath gallery_file_path;
835 MediaGalleryPrefId gallery_pref_id = kInvalidMediaGalleryPrefId;
836 Profile* profile = Profile::FromBrowserContext(browser_context());
837 if (!GetGalleryFilePathAndId(pref_id, profile, extension(),
838 &gallery_file_path, &gallery_pref_id)) {
839 api::media_galleries::AddGalleryWatchResult result;
840 result.gallery_id = kInvalidGalleryId;
841 result.success = false;
842 Respond(ErrorWithArguments(AddGalleryWatch::Results::Create(result),
843 kInvalidGalleryIdMsg));
844 return;
845 }
846
847 gallery_watch_manager()->AddWatch(
848 profile, extension(), gallery_pref_id,
849 base::BindOnce(&MediaGalleriesAddGalleryWatchFunction::HandleResponse,
850 this, gallery_pref_id));
851 }
852
HandleResponse(MediaGalleryPrefId gallery_id,const std::string & error)853 void MediaGalleriesAddGalleryWatchFunction::HandleResponse(
854 MediaGalleryPrefId gallery_id,
855 const std::string& error) {
856 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
857
858 // If an app added a file watch without any event listeners on the
859 // onGalleryChanged event, that's an error.
860 MediaGalleriesEventRouter* api =
861 MediaGalleriesEventRouter::Get(browser_context());
862 api::media_galleries::AddGalleryWatchResult result;
863 result.gallery_id = base::NumberToString(gallery_id);
864
865 if (!api->ExtensionHasGalleryChangeListener(extension()->id())) {
866 result.success = false;
867 Respond(ErrorWithArguments(AddGalleryWatch::Results::Create(result),
868 kMissingEventListener));
869 return;
870 }
871
872 result.success = error.empty();
873 Respond(error.empty()
874 ? OneArgument(base::Value::FromUniquePtrValue(result.ToValue()))
875 : ErrorWithArguments(AddGalleryWatch::Results::Create(result),
876 error));
877 }
878
879 ///////////////////////////////////////////////////////////////////////////////
880 // MediaGalleriesRemoveGalleryWatchFunction //
881 ///////////////////////////////////////////////////////////////////////////////
882
883 MediaGalleriesRemoveGalleryWatchFunction::
~MediaGalleriesRemoveGalleryWatchFunction()884 ~MediaGalleriesRemoveGalleryWatchFunction() {}
885
886 ExtensionFunction::ResponseAction
Run()887 MediaGalleriesRemoveGalleryWatchFunction::Run() {
888 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
889 if (!render_frame_host() || !render_frame_host()->GetProcess())
890 return RespondNow(Error(kNoRenderFrameOrRenderProcessError));
891
892 std::unique_ptr<RemoveGalleryWatch::Params> params(
893 RemoveGalleryWatch::Params::Create(*args_));
894 EXTENSION_FUNCTION_VALIDATE(params);
895
896 MediaGalleriesPreferences* preferences =
897 g_browser_process->media_file_system_registry()->GetPreferences(
898 Profile::FromBrowserContext(browser_context()));
899 preferences->EnsureInitialized(base::BindOnce(
900 &MediaGalleriesRemoveGalleryWatchFunction::OnPreferencesInit, this,
901 params->gallery_id));
902 // Note: OnPreferencesInit might have been called already.
903 return did_respond() ? AlreadyResponded() : RespondLater();
904 }
905
OnPreferencesInit(const std::string & pref_id)906 void MediaGalleriesRemoveGalleryWatchFunction::OnPreferencesInit(
907 const std::string& pref_id) {
908 base::FilePath gallery_file_path;
909 MediaGalleryPrefId gallery_pref_id = 0;
910 Profile* profile = Profile::FromBrowserContext(browser_context());
911 if (!GetGalleryFilePathAndId(pref_id, profile, extension(),
912 &gallery_file_path, &gallery_pref_id)) {
913 Respond(Error(kInvalidGalleryIdMsg));
914 return;
915 }
916
917 gallery_watch_manager()->RemoveWatch(profile, extension_id(),
918 gallery_pref_id);
919 Respond(NoArguments());
920 }
921
922 } // namespace api
923 } // namespace chrome_apps
924