| // Copyright 2012 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/extensions/api/terminal/terminal_private_api.h" |
| |
| #include <cstdlib> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/webui/settings/public/constants/routes.mojom.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/containers/flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/crostini/crostini_features.h" |
| #include "chrome/browser/ash/crostini/crostini_pref_names.h" |
| #include "chrome/browser/ash/crostini/crostini_util.h" |
| #include "chrome/browser/ash/guest_os/guest_id.h" |
| #include "chrome/browser/ash/guest_os/guest_os_pref_names.h" |
| #include "chrome/browser/ash/guest_os/guest_os_session_tracker.h" |
| #include "chrome/browser/ash/guest_os/guest_os_session_tracker_factory.h" |
| #include "chrome/browser/ash/guest_os/guest_os_terminal.h" |
| #include "chrome/browser/ash/guest_os/public/guest_os_service.h" |
| #include "chrome/browser/ash/guest_os/public/guest_os_service_factory.h" |
| #include "chrome/browser/ash/guest_os/public/guest_os_terminal_provider.h" |
| #include "chrome/browser/ash/guest_os/public/guest_os_terminal_provider_registry.h" |
| #include "chrome/browser/ash/guest_os/public/types.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/api/terminal/startup_status.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/profile_util.h" |
| #include "chrome/browser/policy/system_features_disable_list_policy_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/settings_window_manager_chromeos.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/api/terminal_private.h" |
| #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h" |
| #include "chromeos/ash/experiences/guest_os/virtual_machines/virtual_machines_util.h" |
| #include "chromeos/process_proxy/process_proxy_registry.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/common/constants.h" |
| #include "ui/display/types/display_constants.h" |
| |
| namespace terminal_private = extensions::api::terminal_private; |
| namespace OnTerminalResize = |
| extensions::api::terminal_private::OnTerminalResize; |
| namespace OpenTerminalProcess = |
| extensions::api::terminal_private::OpenTerminalProcess; |
| namespace OpenVmshellProcess = |
| extensions::api::terminal_private::OpenVmshellProcess; |
| namespace CloseTerminalProcess = |
| extensions::api::terminal_private::CloseTerminalProcess; |
| namespace SendInput = extensions::api::terminal_private::SendInput; |
| namespace AckOutput = extensions::api::terminal_private::AckOutput; |
| namespace OpenWindow = extensions::api::terminal_private::OpenWindow; |
| namespace GetPrefs = extensions::api::terminal_private::GetPrefs; |
| namespace SetPrefs = extensions::api::terminal_private::SetPrefs; |
| |
| namespace { |
| |
| const char kCroshName[] = "crosh"; |
| const char kCroshCommand[] = "/usr/bin/crosh"; |
| // We make stubbed crosh just echo back input. |
| const char kStubbedCroshCommand[] = "cat"; |
| |
| const char kVmShellName[] = "vmshell"; |
| const char kVmShellCommand[] = "/usr/bin/vsh"; |
| |
| const char kSwitchOwnerId[] = "owner_id"; |
| const char kSwitchVmName[] = "vm_name"; |
| const char kSwitchTargetContainer[] = "target_container"; |
| const char kSwitchStartupId[] = "startup_id"; |
| const char kSwitchCurrentWorkingDir[] = "cwd"; |
| const char kSwitchContainerFeatures[] = "container_features"; |
| |
| const char kCwdTerminalIdPrefix[] = "terminal_id:"; |
| |
| // Prefs that we read and observe. |
| static const base::NoDestructor<std::vector<std::string>> kPrefsReadAllowList{{ |
| ash::prefs::kAccessibilitySpokenFeedbackEnabled, |
| crostini::prefs::kCrostiniEnabled, |
| guest_os::prefs::kGuestOsTerminalSettings, |
| crostini::prefs::kTerminalSshAllowedByPolicy, |
| guest_os::prefs::kGuestOsContainers, |
| }}; |
| |
| void CloseTerminal(const std::string& terminal_id, |
| base::OnceCallback<void(bool)> callback) { |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce( |
| [](const std::string& terminal_id) { |
| return chromeos::ProcessProxyRegistry::Get()->CloseProcess( |
| terminal_id); |
| }, |
| terminal_id), |
| std::move(callback)); |
| } |
| |
| class TerminalTabHelper |
| : public content::WebContentsUserData<TerminalTabHelper> { |
| public: |
| ~TerminalTabHelper() override { |
| // The web contents object is being destructed. We should close all |
| // terminals that haven't been closed already. This can happen when the JS |
| // code didn't have a chance to do that (e.g. memory stress causes the web |
| // contents to be killed directly). |
| for (const std::string& terminal_id : terminal_ids_) { |
| CloseTerminal(terminal_id, base::DoNothing()); |
| } |
| } |
| |
| void AddTerminalId(const std::string& terminal_id) { |
| if (!terminal_ids_.insert(terminal_id).second) { |
| LOG(ERROR) << "Terminal id already exists: " << terminal_id; |
| } |
| } |
| |
| void RemoveTerminalId(const std::string& terminal_id) { |
| if (terminal_ids_.erase(terminal_id) == 0) { |
| LOG(ERROR) << "Terminal id does not exist: " << terminal_id; |
| } |
| } |
| |
| static bool ValidateTerminalId(const content::WebContents* contents, |
| const std::string& terminal_id) { |
| if (contents != nullptr) { |
| auto* helper = TerminalTabHelper::FromWebContents(contents); |
| if (helper != nullptr) { |
| return helper->terminal_ids_.contains(terminal_id); |
| } |
| } |
| return false; |
| } |
| |
| private: |
| explicit TerminalTabHelper(content::WebContents* contents) |
| : content::WebContentsUserData<TerminalTabHelper>(*contents) {} |
| |
| friend class content::WebContentsUserData<TerminalTabHelper>; |
| WEB_CONTENTS_USER_DATA_KEY_DECL(); |
| |
| base::flat_set<std::string> terminal_ids_; |
| }; |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(TerminalTabHelper); |
| |
| // Copies the value of |switch_name| if present from |src| to |dst|. If not |
| // present, uses |default_value| if nonempty. Returns the value set into |dst|. |
| std::string GetSwitch(const base::CommandLine& src, |
| base::CommandLine* dst, |
| const std::string& switch_name, |
| const std::string& default_value) { |
| std::string result = src.HasSwitch(switch_name) |
| ? src.GetSwitchValueASCII(switch_name) |
| : default_value; |
| if (!result.empty()) { |
| dst->AppendSwitchASCII(switch_name, result); |
| } |
| return result; |
| } |
| |
| std::string GetContainerFeaturesArg() { |
| std::string result; |
| // There are only a few available features so concatenating strings is |
| // sufficient. |
| for (vm_tools::cicerone::ContainerFeature feature : |
| crostini::GetContainerFeatures()) { |
| if (!result.empty()) |
| result += ","; |
| result += base::NumberToString(static_cast<int>(feature)); |
| } |
| return result; |
| } |
| |
| void NotifyProcessOutput(content::BrowserContext* browser_context, |
| const std::string& terminal_id, |
| const std::string& output_type, |
| const std::string& output) { |
| if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&NotifyProcessOutput, browser_context, |
| terminal_id, output_type, output)); |
| return; |
| } |
| |
| base::Value::List args; |
| args.Append(terminal_id); |
| args.Append(output_type); |
| args.Append(base::Value(base::as_byte_span(output))); |
| |
| extensions::EventRouter* event_router = |
| extensions::EventRouter::Get(browser_context); |
| if (event_router) { |
| std::unique_ptr<extensions::Event> event(new extensions::Event( |
| extensions::events::TERMINAL_PRIVATE_ON_PROCESS_OUTPUT, |
| terminal_private::OnProcessOutput::kEventName, std::move(args))); |
| event_router->BroadcastEvent(std::move(event)); |
| } |
| } |
| |
| void PrefChanged(Profile* profile, const std::string& pref_name) { |
| extensions::EventRouter* event_router = extensions::EventRouter::Get(profile); |
| if (!event_router) { |
| return; |
| } |
| base::Value::List args; |
| base::Value::Dict prefs; |
| prefs.Set(pref_name, profile->GetPrefs()->GetValue(pref_name).Clone()); |
| args.Append(std::move(prefs)); |
| auto event = std::make_unique<extensions::Event>( |
| extensions::events::TERMINAL_PRIVATE_ON_PREF_CHANGED, |
| terminal_private::OnPrefChanged::kEventName, std::move(args)); |
| event_router->BroadcastEvent(std::move(event)); |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| TerminalPrivateAPI::TerminalPrivateAPI(content::BrowserContext* context) |
| : context_(context), |
| pref_change_registrar_(std::make_unique<PrefChangeRegistrar>()) { |
| Profile* profile = Profile::FromBrowserContext(context); |
| pref_change_registrar_->Init(profile->GetPrefs()); |
| for (const auto& pref : *kPrefsReadAllowList) { |
| pref_change_registrar_->Add(pref, |
| base::BindRepeating(&PrefChanged, profile)); |
| } |
| } |
| |
| TerminalPrivateAPI::~TerminalPrivateAPI() = default; |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<TerminalPrivateAPI>>:: |
| DestructorAtExit g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<TerminalPrivateAPI>* |
| TerminalPrivateAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| TerminalPrivateOpenTerminalProcessFunction:: |
| TerminalPrivateOpenTerminalProcessFunction() = default; |
| |
| TerminalPrivateOpenTerminalProcessFunction:: |
| ~TerminalPrivateOpenTerminalProcessFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOpenTerminalProcessFunction::Run() { |
| std::optional<OpenTerminalProcess::Params> params = |
| OpenTerminalProcess::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| return OpenProcess(params->process_name, std::move(params->args)); |
| } |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOpenTerminalProcessFunction::OpenProcess( |
| const std::string& process_name, |
| std::optional<std::vector<std::string>> args) { |
| const std::string& user_id_hash = |
| extensions::ExtensionsBrowserClient::Get()->GetUserIdHashFromContext( |
| browser_context()); |
| content::WebContents* caller_contents = GetSenderWebContents(); |
| if (!caller_contents) |
| return RespondNow(Error("No web contents.")); |
| |
| // Passing --crosh-command overrides any JS process name. |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kCroshCommand)) { |
| OpenProcess( |
| user_id_hash, |
| base::CommandLine(base::FilePath( |
| command_line->GetSwitchValueASCII(switches::kCroshCommand)))); |
| |
| } else if (process_name == kCroshName) { |
| // Ensure crosh is allowed before starting terminal. |
| if (policy::SystemFeaturesDisableListPolicyHandler::IsSystemFeatureDisabled( |
| policy::SystemFeature::kCrosh, g_browser_process->local_state())) { |
| return RespondNow(Error("crosh not allowed")); |
| } |
| |
| // command=crosh: use '/usr/bin/crosh' on a device, 'cat' otherwise. |
| if (base::SysInfo::IsRunningOnChromeOS()) { |
| OpenProcess(user_id_hash, |
| base::CommandLine(base::FilePath(kCroshCommand))); |
| } else { |
| OpenProcess(user_id_hash, |
| base::CommandLine(base::FilePath(kStubbedCroshCommand))); |
| } |
| } else if (process_name == kVmShellName) { |
| // Ensure vms are allowed before starting terminal. |
| if (!virtual_machines::AreVirtualMachinesAllowedByPolicy()) { |
| return RespondNow(Error("vmshell not allowed")); |
| } |
| // command=vmshell: ensure --owner_id, --vm_name, --target_container, --cwd |
| // are set, and the specified vm/container is running. |
| base::CommandLine cmdline((base::FilePath(kVmShellCommand))); |
| if (!args) |
| args.emplace(); |
| args->insert(args->begin(), kVmShellCommand); |
| base::CommandLine params_args(*args); |
| VLOG(1) << "Original cmdline= " << params_args.GetCommandLineString(); |
| std::string owner_id = |
| GetSwitch(params_args, &cmdline, kSwitchOwnerId, user_id_hash); |
| std::string vm_name = GetSwitch(params_args, &cmdline, kSwitchVmName, |
| crostini::kCrostiniDefaultVmName); |
| std::string container_name = |
| GetSwitch(params_args, &cmdline, kSwitchTargetContainer, ""); |
| GetSwitch(params_args, &cmdline, kSwitchCurrentWorkingDir, ""); |
| std::string startup_id = params_args.GetSwitchValueASCII(kSwitchStartupId); |
| guest_id_ = std::make_unique<guest_os::GuestId>(guest_os::VmType::UNKNOWN, |
| vm_name, container_name); |
| |
| // Unlike the other switches, this is computed here directly rather than |
| // taken from |args|. |
| std::string container_features = GetContainerFeaturesArg(); |
| if (!container_features.empty()) |
| cmdline.AppendSwitchASCII(kSwitchContainerFeatures, container_features); |
| |
| // Append trailing passthrough args if any. E.g. `-- vim file.txt` |
| auto passthrough_args = params_args.GetArgs(); |
| if (!passthrough_args.empty()) { |
| cmdline.AppendArg("--"); |
| for (const auto& arg : passthrough_args) { |
| cmdline.AppendArg(arg); |
| } |
| } |
| VLOG(1) << "Starting " << *guest_id_ |
| << ", cmdline=" << cmdline.GetCommandLineString(); |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context()); |
| auto* service = guest_os::GuestOsServiceFactory::GetForProfile(profile); |
| guest_os::GuestOsTerminalProvider* provider = nullptr; |
| if (service) { |
| provider = service->TerminalProviderRegistry()->Get(*guest_id_); |
| } |
| auto* tracker = |
| guest_os::GuestOsSessionTrackerFactory::GetForProfile(profile); |
| bool verbose = !(tracker && tracker->GetInfo(*guest_id_).has_value()); |
| auto status_printer = std::make_unique<StartupStatusPrinter>( |
| base::BindRepeating(&NotifyProcessOutput, browser_context(), |
| std::move(startup_id), |
| api::terminal_private::ToString( |
| api::terminal_private::OutputType::kStdout)), |
| verbose); |
| if (provider) { |
| startup_status_ = |
| provider->CreateStartupStatus(std::move(status_printer)); |
| startup_status_->StartShowingSpinner(); |
| provider->EnsureRunning( |
| startup_status_.get(), |
| base::BindOnce( |
| &TerminalPrivateOpenTerminalProcessFunction::OnGuestRunning, this, |
| user_id_hash, std::move(cmdline))); |
| } else { |
| // Don't recognise the guest. Options include: |
| // * It doesn't exist but the terminal app thinks it does e.g. out-of-sync |
| // prefs, race between uninstalling and launching terminal. |
| // * We're installing Bruschetta, and it's using the terminal to finish |
| // installing. We don't show Bruschetta until it's installed, but it's |
| // running and users need to connect to it. |
| // Given this, try and connect directly to it, if it succeeds, great, if |
| // it fails, then report failure to the user. |
| startup_status_ = |
| std::make_unique<StartupStatus>(std::move(status_printer), 2); |
| startup_status_->StartShowingSpinner(); |
| OpenVmshellProcess(user_id_hash, std::move(cmdline)); |
| } |
| } else { |
| // command=[unrecognized]. |
| return RespondNow(Error("Invalid process name: " + process_name)); |
| } |
| return RespondLater(); |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::OnGuestRunning( |
| const std::string& user_id_hash, |
| base::CommandLine cmdline, |
| bool success, |
| std::string failure_reason) { |
| if (success) { |
| OpenVmshellProcess(user_id_hash, std::move(cmdline)); |
| } else { |
| startup_status_->OnFinished(success, failure_reason); |
| LOG(ERROR) << failure_reason; |
| Respond(Error(failure_reason)); |
| } |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess( |
| const std::string& user_id_hash, |
| base::CommandLine cmdline) { |
| startup_status_->OnConnectingToVsh(); |
| const std::string cwd = cmdline.GetSwitchValueASCII(kSwitchCurrentWorkingDir); |
| |
| if (!base::StartsWith(cwd, kCwdTerminalIdPrefix)) { |
| return OpenProcess(user_id_hash, std::move(cmdline)); |
| } |
| cmdline.RemoveSwitch(kSwitchCurrentWorkingDir); |
| |
| // The cwd has this format `terminal_id:<terminal_id>`. We need to convert the |
| // terminal id to the pid of the shell process inside the container. |
| int host_pid = chromeos::ProcessProxyRegistry::ConvertToSystemPID( |
| cwd.substr(sizeof(kCwdTerminalIdPrefix) - 1)); |
| |
| // Lookup container shell pid from cicierone to use for cwd. |
| vm_tools::cicerone::GetVshSessionRequest request; |
| request.set_vm_name(guest_id_->vm_name); |
| request.set_container_name(guest_id_->container_name); |
| request.set_owner_id(crostini::CryptohomeIdForProfile( |
| Profile::FromBrowserContext(browser_context()))); |
| request.set_host_vsh_pid(host_pid); |
| ash::CiceroneClient::Get()->GetVshSession( |
| request, |
| base::BindOnce( |
| &TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession, this, |
| user_id_hash, std::move(cmdline), /*terminal_id=*/cwd)); |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession( |
| const std::string& user_id_hash, |
| base::CommandLine cmdline, |
| const std::string& terminal_id, |
| std::optional<vm_tools::cicerone::GetVshSessionResponse> response) { |
| if (!response || !response->success()) { |
| LOG(WARNING) << "Failed to get vsh session for " << terminal_id << ": " |
| << (response ? response->failure_reason() : "empty response"); |
| } else { |
| cmdline.AppendSwitchASCII( |
| kSwitchCurrentWorkingDir, |
| base::NumberToString(response->container_shell_pid())); |
| } |
| OpenProcess(user_id_hash, std::move(cmdline)); |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::OpenProcess( |
| const std::string& user_id_hash, |
| base::CommandLine cmdline) { |
| DCHECK(!cmdline.argv().empty()); |
| // Registry lives on its own task runner. |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner, |
| this, base::BindRepeating(&NotifyProcessOutput, browser_context()), |
| base::BindOnce( |
| &TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread, |
| this), |
| std::move(cmdline), user_id_hash)); |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner( |
| ProcessOutputCallback output_callback, |
| OpenProcessCallback callback, |
| base::CommandLine cmdline, |
| const std::string& user_id_hash) { |
| chromeos::ProcessProxyRegistry* registry = |
| chromeos::ProcessProxyRegistry::Get(); |
| std::string terminal_id; |
| bool success = |
| registry->OpenProcess(std::move(cmdline), user_id_hash, |
| std::move(output_callback), &terminal_id); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), success, terminal_id)); |
| } |
| |
| void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread( |
| bool success, |
| const std::string& terminal_id) { |
| if (startup_status_) { |
| startup_status_->OnFinished( |
| success, success ? "" : "Error connecting shell to guest"); |
| } |
| auto* contents = GetSenderWebContents(); |
| if (!contents) { |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](const std::string& terminal_id) { |
| if (!chromeos::ProcessProxyRegistry::Get()->CloseProcess( |
| terminal_id)) { |
| LOG(ERROR) << "Unable to close terminal " << terminal_id; |
| } |
| }, |
| terminal_id)); |
| const std::string msg = "Web contents closed during OpenProcess"; |
| LOG(WARNING) << msg; |
| Respond(Error(msg)); |
| return; |
| } |
| |
| if (!success) { |
| Respond(Error("Failed to open process.")); |
| return; |
| } |
| Respond(WithArguments(terminal_id)); |
| |
| TerminalTabHelper::CreateForWebContents(contents); |
| TerminalTabHelper::FromWebContents(contents)->AddTerminalId(terminal_id); |
| } |
| |
| TerminalPrivateOpenVmshellProcessFunction:: |
| ~TerminalPrivateOpenVmshellProcessFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOpenVmshellProcessFunction::Run() { |
| std::optional<OpenVmshellProcess::Params> params = |
| OpenVmshellProcess::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| // Only opens 'vmshell'. |
| return OpenProcess(kVmShellName, std::move(params->args)); |
| } |
| |
| TerminalPrivateSendInputFunction::~TerminalPrivateSendInputFunction() = default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateSendInputFunction::Run() { |
| std::optional<SendInput::Params> params = SendInput::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(), |
| params->id)) { |
| LOG(ERROR) << "invalid terminal id " << params->id; |
| return RespondNow(Error("invalid terminal id")); |
| } |
| |
| |
| // Registry lives on its own task runner. |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &TerminalPrivateSendInputFunction::SendInputOnRegistryTaskRunner, |
| this, params->id, params->input)); |
| return RespondLater(); |
| } |
| |
| void TerminalPrivateSendInputFunction::SendInputOnRegistryTaskRunner( |
| const std::string& terminal_id, |
| const std::string& text) { |
| chromeos::ProcessProxyRegistry::Get()->SendInput( |
| terminal_id, text, |
| base::BindOnce(&TerminalPrivateSendInputFunction::OnSendInput, this)); |
| } |
| |
| void TerminalPrivateSendInputFunction::OnSendInput(bool success) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TerminalPrivateSendInputFunction::RespondOnUIThread, this, |
| success)); |
| } |
| |
| void TerminalPrivateSendInputFunction::RespondOnUIThread(bool success) { |
| Respond(WithArguments(success)); |
| } |
| |
| TerminalPrivateCloseTerminalProcessFunction:: |
| ~TerminalPrivateCloseTerminalProcessFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateCloseTerminalProcessFunction::Run() { |
| std::optional<CloseTerminalProcess::Params> params = |
| CloseTerminalProcess::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(), |
| params->id)) { |
| LOG(ERROR) << "invalid terminal id " << params->id; |
| return RespondNow(Error("invalid terminal id")); |
| } |
| TerminalTabHelper::FromWebContents(GetSenderWebContents()) |
| ->RemoveTerminalId(params->id); |
| |
| CloseTerminal( |
| params->id, |
| base::BindOnce( |
| &TerminalPrivateCloseTerminalProcessFunction::RespondOnUIThread, |
| this)); |
| |
| return RespondLater(); |
| } |
| |
| void TerminalPrivateCloseTerminalProcessFunction::RespondOnUIThread( |
| bool success) { |
| Respond(WithArguments(success)); |
| } |
| |
| TerminalPrivateOnTerminalResizeFunction:: |
| ~TerminalPrivateOnTerminalResizeFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOnTerminalResizeFunction::Run() { |
| std::optional<OnTerminalResize::Params> params = |
| OnTerminalResize::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(), |
| params->id)) { |
| LOG(ERROR) << "invalid terminal id " << params->id; |
| return RespondNow(Error("invalid terminal id")); |
| } |
| |
| // Registry lives on its own task runner. |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TerminalPrivateOnTerminalResizeFunction:: |
| OnResizeOnRegistryTaskRunner, |
| this, params->id, params->width, params->height)); |
| |
| return RespondLater(); |
| } |
| |
| void TerminalPrivateOnTerminalResizeFunction::OnResizeOnRegistryTaskRunner( |
| const std::string& terminal_id, |
| int width, |
| int height) { |
| bool success = chromeos::ProcessProxyRegistry::Get()->OnTerminalResize( |
| terminal_id, width, height); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread, this, |
| success)); |
| } |
| |
| void TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread(bool success) { |
| Respond(WithArguments(success)); |
| } |
| |
| TerminalPrivateAckOutputFunction::~TerminalPrivateAckOutputFunction() = default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateAckOutputFunction::Run() { |
| std::optional<AckOutput::Params> params = AckOutput::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| // Every running terminal page will call ackOutput(), but we should only react |
| // for the one who actually owns the output. |
| if (TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(), |
| params->id)) { |
| // Registry lives on its own task runner. |
| chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &TerminalPrivateAckOutputFunction::AckOutputOnRegistryTaskRunner, |
| this, params->id)); |
| } |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| void TerminalPrivateAckOutputFunction::AckOutputOnRegistryTaskRunner( |
| const std::string& terminal_id) { |
| chromeos::ProcessProxyRegistry::Get()->AckOutput(terminal_id); |
| } |
| |
| TerminalPrivateOpenWindowFunction::~TerminalPrivateOpenWindowFunction() = |
| default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateOpenWindowFunction::Run() { |
| std::optional<OpenWindow::Params> params = OpenWindow::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| const std::string* url = &guest_os::GetTerminalHomeUrl(); |
| bool as_tab = false; |
| |
| auto& data = params->data; |
| if (data) { |
| if (data->url) { |
| url = &*data->url; |
| } |
| if (data->as_tab) { |
| as_tab = *data->as_tab; |
| } |
| } |
| |
| if (as_tab) { |
| auto* browser = chrome::FindBrowserWithTab(GetSenderWebContents()); |
| if (browser) { |
| chrome::AddTabAt(browser, GURL(*url), -1, true); |
| } else { |
| LOG(ERROR) << "cannot find the browser"; |
| } |
| } else { |
| guest_os::LaunchTerminalWithUrl( |
| Profile::FromBrowserContext(browser_context()), |
| display::kInvalidDisplayId, /*restore_id=*/0, GURL(*url)); |
| } |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| TerminalPrivateOpenOptionsPageFunction:: |
| ~TerminalPrivateOpenOptionsPageFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOpenOptionsPageFunction::Run() { |
| guest_os::LaunchTerminalSettings( |
| Profile::FromBrowserContext(browser_context())); |
| return RespondNow(NoArguments()); |
| } |
| |
| TerminalPrivateOpenSettingsSubpageFunction:: |
| ~TerminalPrivateOpenSettingsSubpageFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| TerminalPrivateOpenSettingsSubpageFunction::Run() { |
| Profile* profile = profile_util::GetActiveUserProfile(); |
| // Ignore params->subpage for now, and always open crostini. |
| if (crostini::CrostiniFeatures::Get()->IsEnabled(profile)) { |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| profile, chromeos::settings::mojom::kCrostiniDetailsSubpagePath); |
| } else { |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| profile, chromeos::settings::mojom::kAboutChromeOsSectionPath, |
| chromeos::settings::mojom::Setting::kSetUpCrostini); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| TerminalPrivateGetOSInfoFunction::~TerminalPrivateGetOSInfoFunction() = default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateGetOSInfoFunction::Run() { |
| base::Value::Dict info; |
| info.Set("tast", extensions::ExtensionRegistry::Get(browser_context()) |
| ->enabled_extensions() |
| .Contains(extension_misc::kGuestModeTestExtensionId)); |
| return RespondNow(WithArguments(std::move(info))); |
| } |
| |
| TerminalPrivateGetPrefsFunction::~TerminalPrivateGetPrefsFunction() = default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateGetPrefsFunction::Run() { |
| std::optional<GetPrefs::Params> params = GetPrefs::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| PrefService* service = |
| Profile::FromBrowserContext(browser_context())->GetPrefs(); |
| base::Value::Dict result; |
| |
| for (const auto& path : params->paths) { |
| // Ignore non-allowed paths. |
| if (!base::Contains(*kPrefsReadAllowList, path)) { |
| LOG(WARNING) << "Ignoring non-allowed GetPrefs path=" << path; |
| continue; |
| } |
| if (path == guest_os::prefs::kGuestOsTerminalSettings) { |
| guest_os::RecordTerminalSettingsChangesUMAs( |
| Profile::FromBrowserContext(browser_context())); |
| } |
| result.Set(path, service->GetValue(path).Clone()); |
| } |
| return RespondNow(WithArguments(std::move(result))); |
| } |
| |
| TerminalPrivateSetPrefsFunction::~TerminalPrivateSetPrefsFunction() = default; |
| |
| ExtensionFunction::ResponseAction TerminalPrivateSetPrefsFunction::Run() { |
| std::optional<SetPrefs::Params> params = SetPrefs::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| PrefService* service = |
| Profile::FromBrowserContext(browser_context())->GetPrefs(); |
| |
| static constexpr auto kAllowList = |
| base::MakeFixedFlatMap<std::string_view, base::Value::Type>( |
| {{guest_os::prefs::kGuestOsTerminalSettings, |
| base::Value::Type::DICT}}); |
| |
| for (const auto item : params->prefs.additional_properties) { |
| // Write prefs if they are allowed, and match expected type, else ignore. |
| auto allow_it = kAllowList.find(item.first); |
| if (allow_it == kAllowList.end() || |
| allow_it->second != item.second.type()) { |
| LOG(WARNING) << "Ignoring non-allowed SetPrefs path=" << item.first |
| << ", type=" << item.second.type(); |
| continue; |
| } |
| service->Set(item.first, item.second); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace extensions |