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, &copy_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