普通文本  |  432行  |  14.66 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/service/service_utility_process_host.h"

#include <queue>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/metrics/histogram.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/task_runner_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_utility_printing_messages.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/sandbox_init.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#include "ipc/ipc_switches.h"
#include "printing/emf_win.h"
#include "sandbox/win/src/sandbox_policy_base.h"
#include "ui/base/ui_base_switches.h"

namespace {

using content::ChildProcessHost;

enum ServiceUtilityProcessHostEvent {
  SERVICE_UTILITY_STARTED,
  SERVICE_UTILITY_DISCONNECTED,
  SERVICE_UTILITY_METAFILE_REQUEST,
  SERVICE_UTILITY_METAFILE_SUCCEEDED,
  SERVICE_UTILITY_METAFILE_FAILED,
  SERVICE_UTILITY_CAPS_REQUEST,
  SERVICE_UTILITY_CAPS_SUCCEEDED,
  SERVICE_UTILITY_CAPS_FAILED,
  SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST,
  SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED,
  SERVICE_UTILITY_SEMANTIC_CAPS_FAILED,
  SERVICE_UTILITY_FAILED_TO_START,
  SERVICE_UTILITY_EVENT_MAX,
};

void ReportUmaEvent(ServiceUtilityProcessHostEvent id) {
  UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
                            id,
                            SERVICE_UTILITY_EVENT_MAX);
}

// NOTE: changes to this class need to be reviewed by the security team.
class ServiceSandboxedProcessLauncherDelegate
    : public content::SandboxedProcessLauncherDelegate {
 public:
  ServiceSandboxedProcessLauncherDelegate() {}

  virtual void PreSpawnTarget(sandbox::TargetPolicy* policy,
                              bool* success) OVERRIDE {
    // Service process may run as windows service and it fails to create a
    // window station.
    policy->SetAlternateDesktop(false);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(ServiceSandboxedProcessLauncherDelegate);
};

}  // namespace

class ServiceUtilityProcessHost::PdfToEmfState {
 public:
  explicit PdfToEmfState(ServiceUtilityProcessHost* host)
      : host_(host), page_count_(0), current_page_(0), pages_in_progress_(0) {}
  ~PdfToEmfState() { Stop(); }

  bool Start(base::File pdf_file,
             const printing::PdfRenderSettings& conversion_settings) {
    if (!temp_dir_.CreateUniqueTempDir())
      return false;
    return host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
        IPC::TakeFileHandleForProcess(pdf_file.Pass(), host_->handle()),
        conversion_settings));
  }

  void GetMorePages() {
    const int kMaxNumberOfTempFilesPerDocument = 3;
    while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
           current_page_ < page_count_) {
      ++pages_in_progress_;
      emf_files_.push(CreateTempFile());
      host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
          current_page_++,
          IPC::GetFileHandleForProcess(
              emf_files_.back().GetPlatformFile(), host_->handle(), false)));
    }
  }

  // Returns true if all pages processed and client should not expect more
  // results.
  bool OnPageProcessed() {
    --pages_in_progress_;
    GetMorePages();
    if (pages_in_progress_ || current_page_ < page_count_)
      return false;
    Stop();
    return true;
  }

  base::File TakeNextFile() {
    DCHECK(!emf_files_.empty());
    base::File file;
    if (!emf_files_.empty())
      file = emf_files_.front().Pass();
    emf_files_.pop();
    return file.Pass();
  }

  void set_page_count(int page_count) { page_count_ = page_count; }
  bool has_page_count() { return page_count_ > 0; }

 private:
  void Stop() {
    host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
  }

  base::File CreateTempFile() {
    base::FilePath path;
    if (!base::CreateTemporaryFileInDir(temp_dir_.path(), &path))
      return base::File();
    return base::File(path,
                      base::File::FLAG_CREATE_ALWAYS |
                      base::File::FLAG_WRITE |
                      base::File::FLAG_READ |
                      base::File::FLAG_DELETE_ON_CLOSE |
                      base::File::FLAG_TEMPORARY);
  }

  base::ScopedTempDir temp_dir_;
  ServiceUtilityProcessHost* host_;
  std::queue<base::File> emf_files_;
  int page_count_;
  int current_page_;
  int pages_in_progress_;
};

ServiceUtilityProcessHost::ServiceUtilityProcessHost(
    Client* client,
    base::MessageLoopProxy* client_message_loop_proxy)
    : handle_(base::kNullProcessHandle),
      client_(client),
      client_message_loop_proxy_(client_message_loop_proxy),
      waiting_for_reply_(false),
      weak_ptr_factory_(this) {
  child_process_host_.reset(ChildProcessHost::Create(this));
}

ServiceUtilityProcessHost::~ServiceUtilityProcessHost() {
  // We need to kill the child process when the host dies.
  base::KillProcess(handle_, content::RESULT_CODE_NORMAL_EXIT, false);
}

bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile(
    const base::FilePath& pdf_path,
    const printing::PdfRenderSettings& render_settings) {
  ReportUmaEvent(SERVICE_UTILITY_METAFILE_REQUEST);
  start_time_ = base::Time::Now();
  base::File pdf_file(pdf_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!pdf_file.IsValid() || !StartProcess(false))
    return false;

  DCHECK(!waiting_for_reply_);
  waiting_for_reply_ = true;

  pdf_to_emf_state_.reset(new PdfToEmfState(this));
  return pdf_to_emf_state_->Start(pdf_file.Pass(), render_settings);
}

bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults(
    const std::string& printer_name) {
  ReportUmaEvent(SERVICE_UTILITY_CAPS_REQUEST);
  start_time_ = base::Time::Now();
  if (!StartProcess(true))
    return false;
  DCHECK(!waiting_for_reply_);
  waiting_for_reply_ = true;
  return Send(new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name));
}

bool ServiceUtilityProcessHost::StartGetPrinterSemanticCapsAndDefaults(
    const std::string& printer_name) {
  ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST);
  start_time_ = base::Time::Now();
  if (!StartProcess(true))
    return false;
  DCHECK(!waiting_for_reply_);
  waiting_for_reply_ = true;
  return Send(
      new ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults(printer_name));
}

bool ServiceUtilityProcessHost::StartProcess(bool no_sandbox) {
  std::string channel_id = child_process_host_->CreateChannel();
  if (channel_id.empty())
    return false;

  base::FilePath exe_path = GetUtilityProcessCmd();
  if (exe_path.empty()) {
    NOTREACHED() << "Unable to get utility process binary name.";
    return false;
  }

  base::CommandLine cmd_line(exe_path);
  cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess);
  cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id);
  cmd_line.AppendSwitch(switches::kLang);

  if (Launch(&cmd_line, no_sandbox)) {
    ReportUmaEvent(SERVICE_UTILITY_STARTED);
    return true;
  }
  ReportUmaEvent(SERVICE_UTILITY_FAILED_TO_START);
  return false;
}

bool ServiceUtilityProcessHost::Launch(base::CommandLine* cmd_line,
                                       bool no_sandbox) {
  if (no_sandbox) {
    base::ProcessHandle process = base::kNullProcessHandle;
    cmd_line->AppendSwitch(switches::kNoSandbox);
    base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_);
  } else {
    ServiceSandboxedProcessLauncherDelegate delegate;
    handle_ = content::StartSandboxedProcess(&delegate, cmd_line);
  }
  return (handle_ != base::kNullProcessHandle);
}

bool ServiceUtilityProcessHost::Send(IPC::Message* msg) {
  if (child_process_host_)
    return child_process_host_->Send(msg);
  delete msg;
  return false;
}

base::FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() {
#if defined(OS_LINUX)
  int flags = ChildProcessHost::CHILD_ALLOW_SELF;
#else
  int flags = ChildProcessHost::CHILD_NORMAL;
#endif
  return ChildProcessHost::GetChildPath(flags);
}

void ServiceUtilityProcessHost::OnChildDisconnected() {
  if (waiting_for_reply_) {
    // If we are yet to receive a reply then notify the client that the
    // child died.
    client_message_loop_proxy_->PostTask(
        FROM_HERE, base::Bind(&Client::OnChildDied, client_.get()));
    ReportUmaEvent(SERVICE_UTILITY_DISCONNECTED);
    UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityDisconnectTime",
                        base::Time::Now() - start_time_);
  }
  delete this;
}

bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message)
    IPC_MESSAGE_HANDLER(
        ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount,
        OnRenderPDFPagesToMetafilesPageCount)
    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
                        OnRenderPDFPagesToMetafilesPageDone)
    IPC_MESSAGE_HANDLER(
        ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
        OnGetPrinterCapsAndDefaultsSucceeded)
    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
                        OnGetPrinterCapsAndDefaultsFailed)
    IPC_MESSAGE_HANDLER(
        ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Succeeded,
        OnGetPrinterSemanticCapsAndDefaultsSucceeded)
    IPC_MESSAGE_HANDLER(
        ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Failed,
        OnGetPrinterSemanticCapsAndDefaultsFailed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

base::ProcessHandle ServiceUtilityProcessHost::GetHandle() const {
  return handle_;
}

void ServiceUtilityProcessHost::OnMetafileSpooled(bool success) {
  if (!success || pdf_to_emf_state_->OnPageProcessed())
    OnPDFToEmfFinished(success);
}

void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageCount(
    int page_count) {
  DCHECK(waiting_for_reply_);
  if (!pdf_to_emf_state_ || page_count <= 0 ||
      pdf_to_emf_state_->has_page_count()) {
    return OnPDFToEmfFinished(false);
  }
  pdf_to_emf_state_->set_page_count(page_count);
  pdf_to_emf_state_->GetMorePages();
}

void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageDone(
    bool success,
    double scale_factor) {
  DCHECK(waiting_for_reply_);
  if (!pdf_to_emf_state_ || !success)
    return OnPDFToEmfFinished(false);
  base::File emf_file = pdf_to_emf_state_->TakeNextFile();
  base::PostTaskAndReplyWithResult(
      client_message_loop_proxy_,
      FROM_HERE,
      base::Bind(&Client::MetafileAvailable,
                 client_.get(),
                 scale_factor,
                 base::Passed(&emf_file)),
      base::Bind(&ServiceUtilityProcessHost::OnMetafileSpooled,
                 weak_ptr_factory_.GetWeakPtr()));
}

void ServiceUtilityProcessHost::OnPDFToEmfFinished(bool success) {
  if (!waiting_for_reply_)
    return;
  waiting_for_reply_ = false;
  if (success) {
    ReportUmaEvent(SERVICE_UTILITY_METAFILE_SUCCEEDED);
    UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime",
                        base::Time::Now() - start_time_);
  } else {
    ReportUmaEvent(SERVICE_UTILITY_METAFILE_FAILED);
    UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime",
                        base::Time::Now() - start_time_);
  }
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      base::Bind(
          &Client::OnRenderPDFPagesToMetafileDone, client_.get(), success));
  pdf_to_emf_state_.reset();
}

void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded(
    const std::string& printer_name,
    const printing::PrinterCapsAndDefaults& caps_and_defaults) {
  DCHECK(waiting_for_reply_);
  ReportUmaEvent(SERVICE_UTILITY_CAPS_SUCCEEDED);
  UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsTime",
                      base::Time::Now() - start_time_);
  waiting_for_reply_ = false;
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      base::Bind(&Client::OnGetPrinterCapsAndDefaults, client_.get(), true,
                 printer_name, caps_and_defaults));
}

void ServiceUtilityProcessHost::OnGetPrinterSemanticCapsAndDefaultsSucceeded(
    const std::string& printer_name,
    const printing::PrinterSemanticCapsAndDefaults& caps_and_defaults) {
  DCHECK(waiting_for_reply_);
  ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED);
  UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilitySemanticCapsTime",
                      base::Time::Now() - start_time_);
  waiting_for_reply_ = false;
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      base::Bind(&Client::OnGetPrinterSemanticCapsAndDefaults, client_.get(),
                 true, printer_name, caps_and_defaults));
}

void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsFailed(
    const std::string& printer_name) {
  DCHECK(waiting_for_reply_);
  ReportUmaEvent(SERVICE_UTILITY_CAPS_FAILED);
  UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsFailTime",
                      base::Time::Now() - start_time_);
  waiting_for_reply_ = false;
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      base::Bind(&Client::OnGetPrinterCapsAndDefaults, client_.get(), false,
                 printer_name, printing::PrinterCapsAndDefaults()));
}

void ServiceUtilityProcessHost::OnGetPrinterSemanticCapsAndDefaultsFailed(
    const std::string& printer_name) {
  DCHECK(waiting_for_reply_);
  ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_FAILED);
  UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilitySemanticCapsFailTime",
                      base::Time::Now() - start_time_);
  waiting_for_reply_ = false;
  client_message_loop_proxy_->PostTask(
      FROM_HERE,
      base::Bind(&Client::OnGetPrinterSemanticCapsAndDefaults,
                 client_.get(), false, printer_name,
                 printing::PrinterSemanticCapsAndDefaults()));
}

bool ServiceUtilityProcessHost::Client::MetafileAvailable(double scale_factor,
                                                          base::File file) {
  file.Seek(base::File::FROM_BEGIN, 0);
  int64 size = file.GetLength();
  if (size <= 0) {
    OnRenderPDFPagesToMetafileDone(false);
    return false;
  }
  std::vector<char> data(size);
  if (file.ReadAtCurrentPos(data.data(), data.size()) != size) {
    OnRenderPDFPagesToMetafileDone(false);
    return false;
  }
  printing::Emf emf;
  if (!emf.InitFromData(data.data(), data.size())) {
    OnRenderPDFPagesToMetafileDone(false);
    return false;
  }
  OnRenderPDFPagesToMetafilePageDone(scale_factor, emf);
  return true;
}