| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_util.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/no_destructor.h" |
| #include "base/observer_list.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_drive_image_download_service.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_features.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_manager.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_manager_factory.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h" |
| #include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "net/base/url_util.h" |
| |
| namespace plugin_vm { |
| |
| const char kPitaDlc[] = "pita"; |
| const char kPluginVmShelfAppId[] = "lgjpclljbbmphhnalkeplcmnjpfmmaek"; |
| const char kPluginVmName[] = "PvmDefault"; |
| const char kChromeOSBaseDirectoryDisplayText[] = "Network \u203a ChromeOS"; |
| const char kPluginVmWindowId[] = "org.chromium.plugin_vm_ui"; |
| |
| namespace { |
| |
| static std::string& MutableFakeLicenseKey() { |
| static base::NoDestructor<std::string> license_key; |
| return *license_key; |
| } |
| |
| base::RepeatingClosureList& GetFakeLicenseKeyListeners() { |
| static base::NoDestructor<base::RepeatingClosureList> instance; |
| return *instance; |
| } |
| |
| } // namespace |
| |
| bool IsPluginVmRunning(Profile* profile) { |
| return plugin_vm::PluginVmManagerFactory::GetForProfile(profile) |
| ->vm_state() == |
| vm_tools::plugin_dispatcher::VmState::VM_STATE_RUNNING && |
| ChromeShelfController::instance()->IsOpen( |
| ash::ShelfID(kPluginVmShelfAppId)); |
| } |
| |
| bool IsPluginVmAppWindow(const aura::Window* window) { |
| const std::string* app_id = exo::GetShellApplicationId(window); |
| if (!app_id) |
| return false; |
| return *app_id == "org.chromium.plugin_vm_ui"; |
| } |
| |
| std::string GetPluginVmUserIdForProfile(const Profile* profile) { |
| DCHECK(profile); |
| return profile->GetPrefs()->GetString(plugin_vm::prefs::kPluginVmUserId); |
| } |
| |
| void SetFakePluginVmPolicy(Profile* profile, |
| const std::string& image_url, |
| const std::string& image_hash, |
| const std::string& license_key) { |
| ScopedDictPrefUpdate update(profile->GetPrefs(), |
| plugin_vm::prefs::kPluginVmImage); |
| base::Value::Dict& dict = update.Get(); |
| dict.SetByDottedPath(prefs::kPluginVmImageUrlKeyName, image_url); |
| dict.SetByDottedPath(prefs::kPluginVmImageHashKeyName, image_hash); |
| plugin_vm::PluginVmInstallerFactory::GetForProfile(profile) |
| ->SkipLicenseCheckForTesting(); // IN-TEST |
| MutableFakeLicenseKey() = license_key; |
| GetFakeLicenseKeyListeners().Notify(); |
| } |
| |
| std::string GetFakeLicenseKey() { |
| return MutableFakeLicenseKey(); |
| } |
| |
| bool FakeLicenseKeyIsSet() { |
| return !MutableFakeLicenseKey().empty(); |
| } |
| |
| void RemoveDriveDownloadDirectoryIfExists() { |
| auto log_file_deletion_if_failed = [](bool success) { |
| if (!success) { |
| LOG(ERROR) << "PluginVM failed to delete download directory"; |
| } |
| }; |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(&base::DeletePathRecursively, |
| base::FilePath(kPluginVmDriveDownloadDirectory)), |
| base::BindOnce(std::move(log_file_deletion_if_failed))); |
| } |
| |
| std::optional<std::string> GetIdFromDriveUrl(const GURL& url) { |
| const std::string& spec = url.spec(); |
| |
| const std::string kOpenUrlBase = "https://drive.google.com/open?"; |
| if (base::StartsWith(spec, kOpenUrlBase, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| // e.g. https://drive.google.com/open?id=[ID] |
| std::string id; |
| if (!net::GetValueForKeyInQuery(url, "id", &id)) |
| return std::nullopt; |
| return id; |
| } |
| |
| // These will match some invalid URLs, which is fine. |
| const std::string kViewUrlPatternWithDomain = |
| "https://drive.google.com/a/*/file/d/*/view*"; |
| const std::string kViewUrlPatternWithoutDomain = |
| "https://drive.google.com/file/d/*/view*"; |
| if (base::MatchPattern(spec, kViewUrlPatternWithDomain) || |
| base::MatchPattern(spec, kViewUrlPatternWithoutDomain)) { |
| // e.g. https://drive.google.com/a/example.org/file/d/[ID]/view?usp=sharing |
| // or https://drive.google.com/file/d/[ID]/view?usp=sharing |
| size_t id_end = spec.find("/view"); |
| size_t id_start = spec.rfind('/', id_end - 1) + 1; |
| return spec.substr(id_start, id_end - id_start); |
| } |
| |
| return std::nullopt; |
| } |
| |
| bool IsPluginvmWindowId(const std::string& window_id) { |
| return base::StartsWith(window_id, kPluginVmWindowId); |
| } |
| |
| PluginVmAvailabilitySubscription::PluginVmAvailabilitySubscription( |
| Profile* profile, |
| AvailabilityChangeCallback callback) |
| : profile_(profile), callback_(callback) { |
| DCHECK(ash::CrosSettings::IsInitialized()); |
| ash::CrosSettings* cros_settings = ash::CrosSettings::Get(); |
| // Subscriptions are automatically removed when this object is destroyed. |
| pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); |
| pref_change_registrar_->Init(profile->GetPrefs()); |
| pref_change_registrar_->Add( |
| plugin_vm::prefs::kPluginVmAllowed, |
| base::BindRepeating(&PluginVmAvailabilitySubscription::OnPolicyChanged, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| plugin_vm::prefs::kPluginVmUserId, |
| base::BindRepeating(&PluginVmAvailabilitySubscription::OnPolicyChanged, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| plugin_vm::prefs::kPluginVmImageExists, |
| base::BindRepeating( |
| &PluginVmAvailabilitySubscription::OnImageExistsChanged, |
| base::Unretained(this))); |
| device_allowed_subscription_ = cros_settings->AddSettingsObserver( |
| ash::kPluginVmAllowed, |
| base::BindRepeating(&PluginVmAvailabilitySubscription::OnPolicyChanged, |
| base::Unretained(this))); |
| fake_license_subscription_ = GetFakeLicenseKeyListeners().Add( |
| base::BindRepeating(&PluginVmAvailabilitySubscription::OnPolicyChanged, |
| base::Unretained(this))); |
| |
| is_allowed_ = PluginVmFeatures::Get()->IsAllowed(profile); |
| is_configured_ = PluginVmFeatures::Get()->IsConfigured(profile_); |
| } |
| |
| void PluginVmAvailabilitySubscription::OnPolicyChanged() { |
| bool allowed = PluginVmFeatures::Get()->IsAllowed(profile_); |
| if (allowed != is_allowed_) { |
| is_allowed_ = allowed; |
| callback_.Run(is_allowed_, is_configured_); |
| } |
| } |
| |
| void PluginVmAvailabilitySubscription::OnImageExistsChanged() { |
| bool configured = PluginVmFeatures::Get()->IsConfigured(profile_); |
| if (configured != is_configured_) { |
| is_configured_ = configured; |
| callback_.Run(is_allowed_, is_configured_); |
| } |
| } |
| |
| PluginVmAvailabilitySubscription::~PluginVmAvailabilitySubscription() = default; |
| |
| } // namespace plugin_vm |