chromium/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.cc

// 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/apps/app_shim/app_shim_host_bootstrap_mac.h"

#include <CoreFoundation/CoreFoundation.h>
#include <bsm/libbsm.h>

#include <memory>
#include <utility>

#include "base/apple/scoped_cftyperef.h"
#include "base/functional/bind.h"
#include "base/strings/sys_string_conversions.h"
#include "components/variations/net/variations_command_line.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/message_pipe.h"

// NSLog is in Foundation, which cannot be #included in this translation
// unit without renaming it to .mm. Because the logging is temporary, just
// forward-declare it.
extern "C" {
void NSLogv(CFStringRef, va_list);
}  // extern C

namespace {
AppShimHostBootstrap::Client* g_client = nullptr;

// TODO(crbug.com/40674145): Remove NSLog logging, and move to an
// internal debugging URL.
void LogToNSLog(std::string format, ...) {
  base::apple::ScopedCFTypeRef<CFStringRef> cf_format(
      base::SysUTF8ToCFStringRef(format));

  va_list arguments;
  va_start(arguments, format);
  NSLogv(cf_format.get(), arguments);
  va_end(arguments);
}

}  // namespace

// static
void AppShimHostBootstrap::SetClient(Client* client) {
  g_client = client;
}

// static
void AppShimHostBootstrap::CreateForChannelAndPeerAuditToken(
    mojo::PlatformChannelEndpoint endpoint,
    audit_token_t audit_token) {
  // AppShimHostBootstrap is initially owned by itself until it receives a
  // OnShimConnected message or a channel error. In OnShimConnected, ownership
  // is transferred to a unique_ptr.
  DCHECK(endpoint.platform_handle().is_mach_send());
  (new AppShimHostBootstrap(audit_token))->ServeChannel(std::move(endpoint));
}

AppShimHostBootstrap::AppShimHostBootstrap(audit_token_t audit_token)
    : audit_token_(audit_token) {}

AppShimHostBootstrap::~AppShimHostBootstrap() {
  DCHECK(!shim_connected_callback_);
  LogToNSLog("AppShim: Closing pid %d", GetAppShimPid());
}

void AppShimHostBootstrap::ServeChannel(
    mojo::PlatformChannelEndpoint endpoint) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  mojo::ScopedMessagePipeHandle message_pipe =
      bootstrap_mojo_connection_.Connect(std::move(endpoint));
  host_bootstrap_receiver_.Bind(
      mojo::PendingReceiver<chrome::mojom::AppShimHostBootstrap>(
          std::move(message_pipe)));
  host_bootstrap_receiver_.set_disconnect_with_reason_handler(base::BindOnce(
      &AppShimHostBootstrap::ChannelError, base::Unretained(this)));
}

void AppShimHostBootstrap::ChannelError(uint32_t custom_reason,
                                        const std::string& description) {
  // Once |this| has received a OnShimConnected message, it is owned by a
  // unique_ptr (not the channel anymore).
  if (app_shim_info_)
    return;
  LOG(ERROR) << "Channel error custom_reason:" << custom_reason
             << " description: " << description;
  delete this;
}

mojo::PendingReceiver<chrome::mojom::AppShimHost>
AppShimHostBootstrap::GetAppShimHostReceiver() {
  return std::move(app_shim_host_receiver_);
}

const std::string& AppShimHostBootstrap::GetAppId() const {
  return app_shim_info_->app_id;
}

const GURL& AppShimHostBootstrap::GetAppURL() {
  return app_shim_info_->app_url;
}

const base::FilePath& AppShimHostBootstrap::GetProfilePath() {
  return app_shim_info_->profile_path;
}

chrome::mojom::AppShimLaunchType AppShimHostBootstrap::GetLaunchType() const {
  return app_shim_info_->launch_type;
}

const std::vector<base::FilePath>& AppShimHostBootstrap::GetLaunchFiles()
    const {
  return app_shim_info_->files;
}

chrome::mojom::AppShimLoginItemRestoreState
AppShimHostBootstrap::GetLoginItemRestoreState() const {
  return app_shim_info_->login_item_restore_state;
}

const std::vector<GURL>& AppShimHostBootstrap::GetLaunchUrls() const {
  return app_shim_info_->urls;
}

mojo::PendingReceiver<mac_notifications::mojom::MacNotificationActionHandler>
AppShimHostBootstrap::TakeNotificationActionHandler() {
  return std::move(app_shim_info_->notification_action_handler);
}

bool AppShimHostBootstrap::IsMultiProfile() const {
  // PWAs and bookmark apps are multi-profile capable.
  return app_shim_info_->app_url.is_valid();
}

void AppShimHostBootstrap::OnShimConnected(
    mojo::PendingReceiver<chrome::mojom::AppShimHost> app_shim_host_receiver,
    chrome::mojom::AppShimInfoPtr app_shim_info,
    OnShimConnectedCallback callback) {
  LogToNSLog("AppShim: Received OnShimConnected from pid %d", GetAppShimPid());
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!app_shim_info_);
  // Only one app launch message per channel.
  if (app_shim_info_)
    return;

  app_shim_host_receiver_ = std::move(app_shim_host_receiver);
  app_shim_info_ = std::move(app_shim_info);
  shim_connected_callback_ = std::move(callback);

  // Transfer ownership to a unique_ptr and mark that OnShimConnected has been
  // received. Note that after this point, a channel error will no longer
  // cause |this| to be deleted.
  std::unique_ptr<AppShimHostBootstrap> deleter(this);

  // |g_client| takes ownership of |this| now.
  if (g_client)
    g_client->OnShimProcessConnected(std::move(deleter));

  // |g_client| can only be nullptr after AppShimListener is destroyed. Since
  // this only happens at shutdown, do nothing here.
}

void AppShimHostBootstrap::OnConnectedToHost(
    mojo::PendingReceiver<chrome::mojom::AppShim> app_shim_receiver) {
  LogToNSLog("AppShim: Performing OnConnectedToHost for pid %d",
             GetAppShimPid());
  std::move(shim_connected_callback_)
      .Run(chrome::mojom::AppShimLaunchResult::kSuccess,
           variations::VariationsCommandLine::GetForCurrentProcess(),
           std::move(app_shim_receiver));
}

void AppShimHostBootstrap::OnFailedToConnectToHost(
    chrome::mojom::AppShimLaunchResult result) {
  LogToNSLog("AppShim: Performing OnFailedToConnectToHost result %d for pid %d",
             result, GetAppShimPid());

  // Because there will be users of the AppShim interface in failure, just
  // return a dummy receiver.
  mojo::Remote<chrome::mojom::AppShim> dummy_remote;
  std::move(shim_connected_callback_)
      .Run(result, variations::VariationsCommandLine(),
           dummy_remote.BindNewPipeAndPassReceiver());
}

base::ProcessId AppShimHostBootstrap::GetAppShimPid() const {
  return audit_token_to_pid(audit_token_);
}