Commit 6840d3d0 authored by Luum Habtemariam's avatar Luum Habtemariam Committed by Commit Bot

Adding CupsProxyService PrinterInstaller

DDoc: go/cups-plugin

This internal manager of the CupsProxyService takes IPP requests, identifies
any printers referenced by them, and installs them into the CUPS daemon.
When printing normally on ChromeOS, Chrome installs printers into CUPS as
necessary; this manager allows us to do the same.

Bug: chromium:945409
Test: accompanying unit tests pass
Change-Id: I33fb20b3024a6e7bd498eda7eb7fcc778bfeede7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1621199
Commit-Queue: Luum Habtemariam <luum@chromium.org>
Reviewed-by: default avatarSean Kau <skau@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665268}
parent 4c91650b
......@@ -18,6 +18,7 @@ source_set("cups_proxy") {
deps = [
"//base",
"//chrome/services/cups_proxy/public/cpp",
"//chrome/services/cups_proxy/public/mojom",
"//chromeos/dbus",
"//net",
......@@ -28,6 +29,10 @@ source_set("cups_proxy") {
# We stub this service if libCUPS is not present.
if (use_cups) {
configs += [ "//printing:cups" ]
sources += [
"printer_installer.cc",
"printer_installer.h",
]
}
public_deps = [
......@@ -46,3 +51,20 @@ static_library("test_support") {
":cups_proxy",
]
}
source_set("unit_tests") {
testonly = true
if (use_cups) {
sources = [
"printer_installer_unittest.cc",
]
deps = [
":cups_proxy",
":test_support",
"//base",
"//testing/gtest",
]
}
}
// Copyright 2019 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/services/cups_proxy/printer_installer.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chrome/services/cups_proxy/public/cpp/cups_util.h"
#include "chromeos/printing/printer_configuration.h"
namespace cups_proxy {
namespace {
using CupsProxyServiceDelegate = chromeos::printing::CupsProxyServiceDelegate;
class PrinterInstallerImpl : public PrinterInstaller {
public:
explicit PrinterInstallerImpl(
base::WeakPtr<CupsProxyServiceDelegate> delegate);
~PrinterInstallerImpl() override = default;
void InstallPrinterIfNeeded(ipp_t* ipp, InstallPrinterCallback cb) override;
private:
void OnInstallPrinter(InstallPrinterCallback cb, bool success);
void Finish(InstallPrinterCallback cb, InstallPrinterResult res);
// Service delegate granting access to printing stack dependencies.
base::WeakPtr<CupsProxyServiceDelegate> delegate_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<PrinterInstallerImpl> weak_factory_;
};
} // namespace
PrinterInstallerImpl::PrinterInstallerImpl(
base::WeakPtr<CupsProxyServiceDelegate> delegate)
: delegate_(std::move(delegate)), weak_factory_(this) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void PrinterInstallerImpl::InstallPrinterIfNeeded(ipp_t* ipp,
InstallPrinterCallback cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto printer_uuid = GetPrinterId(ipp);
if (!printer_uuid) {
Finish(std::move(cb), InstallPrinterResult::kNoPrintersFound);
return;
}
auto printer = delegate_->GetPrinter(*printer_uuid);
if (!printer) {
// If the requested printer DNE, we proxy to CUPSd and allow it to
// handle the error.
Finish(std::move(cb), InstallPrinterResult::kUnknownPrinterFound);
return;
}
if (delegate_->IsPrinterInstalled(*printer)) {
Finish(std::move(cb), InstallPrinterResult::kSuccess);
return;
}
// Install printer.
delegate_->SetupPrinter(
*printer, base::BindOnce(&PrinterInstallerImpl::OnInstallPrinter,
weak_factory_.GetWeakPtr(), std::move(cb)));
}
void PrinterInstallerImpl::OnInstallPrinter(InstallPrinterCallback cb,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Finish(std::move(cb),
success ? InstallPrinterResult::kSuccess
: InstallPrinterResult::kPrinterInstallationFailure);
}
void PrinterInstallerImpl::Finish(InstallPrinterCallback cb,
InstallPrinterResult res) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), res));
}
// static
std::unique_ptr<PrinterInstaller> PrinterInstaller::Create(
base::WeakPtr<CupsProxyServiceDelegate> delegate) {
return std::make_unique<PrinterInstallerImpl>(std::move(delegate));
}
} // namespace cups_proxy
// Copyright 2019 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.
#ifndef CHROME_SERVICES_CUPS_PROXY_PRINTER_INSTALLER_H_
#define CHROME_SERVICES_CUPS_PROXY_PRINTER_INSTALLER_H_
#include <cups/cups.h>
#include <memory>
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "chrome/services/cups_proxy/cups_proxy_service_delegate.h"
namespace cups_proxy {
enum class InstallPrinterResult {
kSuccess = 0,
// Couldn't find any printers referenced by the incoming request.
kNoPrintersFound,
// Referenced printer is unknown to Chrome.
kUnknownPrinterFound,
kPrinterInstallationFailure,
};
using InstallPrinterCallback = base::OnceCallback<void(InstallPrinterResult)>;
// This CupsProxyService internal manager ensures that any printers referenced
// by an incoming IPP request are installed into the CUPS daemon prior to
// proxying. This class can be created anywhere, but must be accessed from a
// sequenced context.
class PrinterInstaller {
public:
// Factory function.
static std::unique_ptr<PrinterInstaller> Create(
base::WeakPtr<chromeos::printing::CupsProxyServiceDelegate> delegate);
virtual ~PrinterInstaller() = default;
// Pre-installs any printers required by |ipp| into the CUPS daemon, as
// needed. |cb| will be run on this instance's sequenced context.
virtual void InstallPrinterIfNeeded(ipp_t* ipp,
InstallPrinterCallback cb) = 0;
};
} // namespace cups_proxy
#endif // CHROME_SERVICES_CUPS_PROXY_PRINTER_INSTALLER_H_
// Copyright 2018 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 <cups/cups.h>
#include <map>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/memory/weak_ptr.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/services/cups_proxy/fake_cups_proxy_service_delegate.h"
#include "chrome/services/cups_proxy/printer_installer.h"
#include "printing/backend/cups_ipp_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cups_proxy {
namespace {
using Printer = chromeos::Printer;
// Generated via base::GenerateGUID.
const char kGenericGUID[] = "fd4c5f2e-7549-43d5-b931-9bf4e4f1bf51";
// Faked delegate gives control over PrinterInstaller's printing stack
// dependencies.
class FakeServiceDelegate
: public chromeos::printing::FakeCupsProxyServiceDelegate {
public:
FakeServiceDelegate() = default;
~FakeServiceDelegate() override = default;
void AddPrinter(const Printer& printer) {
installed_printers_.insert({printer.id(), false});
}
// Service delegate overrides.
bool IsPrinterInstalled(const Printer& printer) override {
if (!base::ContainsKey(installed_printers_, printer.id())) {
return false;
}
return installed_printers_.at(printer.id());
}
base::Optional<Printer> GetPrinter(const std::string& id) override {
if (!base::ContainsKey(installed_printers_, id)) {
return base::nullopt;
}
return Printer(id);
}
void SetupPrinter(
const Printer& printer,
chromeos::printing::PrinterSetupCallback callback) override {
// PrinterInstaller is expected to have checked if |printer| is already
// installed before trying setup.
if (IsPrinterInstalled(printer)) {
return std::move(callback).Run(false);
}
// Install printer.
installed_printers_[printer.id()] = true;
return std::move(callback).Run(true);
}
private:
std::map<std::string, bool> installed_printers_;
};
class PrinterInstallerTest : public testing::Test {
public:
PrinterInstallerTest() : weak_factory_(this) {
delegate_ = std::make_unique<FakeServiceDelegate>();
printer_installer_ = PrinterInstaller::Create(delegate_->GetWeakPtr());
}
~PrinterInstallerTest() override = default;
bool RunInstallPrinterIfNeeded(ipp_t* ipp, Printer to_install) {
bool success = false;
printer_installer_->InstallPrinterIfNeeded(
ipp, base::BindOnce(&PrinterInstallerTest::OnRunInstallPrinterIfNeeded,
weak_factory_.GetWeakPtr(), &success,
std::move(to_install)));
scoped_task_environment_.RunUntilIdle();
return success;
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
void OnRunInstallPrinterIfNeeded(bool* ret,
Printer to_install,
InstallPrinterResult result) {
if (result != InstallPrinterResult::kSuccess) {
return;
}
// If printer wasn't installed, fail.
if (!delegate_->IsPrinterInstalled(to_install)) {
return;
}
*ret = true;
}
// Backend fake driving the PrinterInstaller.
std::unique_ptr<FakeServiceDelegate> delegate_;
// The class being tested. This must be declared after the fakes, as its
// initialization must come after that of the fakes.
std::unique_ptr<PrinterInstaller> printer_installer_;
base::WeakPtrFactory<PrinterInstallerTest> weak_factory_;
};
// Return a valid ScopedIppPtr that correctly references |id| in
// the printer-uri field.
::printing::ScopedIppPtr MakeIppReferencingPrinters(const std::string& id) {
::printing::ScopedIppPtr ret = ::printing::WrapIpp(ippNew());
std::string uri = "ipp://localhost/printers/" + id;
ippAddString(ret.get(), IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri", NULL,
uri.c_str());
return ret;
}
// Standard install known printer workflow.
TEST_F(PrinterInstallerTest, SimpleSanityTest) {
Printer to_install(kGenericGUID);
delegate_->AddPrinter(to_install);
auto ipp = MakeIppReferencingPrinters(to_install.id());
EXPECT_TRUE(RunInstallPrinterIfNeeded(ipp.get(), std::move(to_install)));
}
// Should fail to install an unknown(previously unseen) printer.
TEST_F(PrinterInstallerTest, UnknownPrinter) {
Printer to_install(kGenericGUID);
auto ipp = MakeIppReferencingPrinters(to_install.id());
EXPECT_FALSE(RunInstallPrinterIfNeeded(ipp.get(), std::move(to_install)));
}
// Ensure we never setup a printer that's already installed.
TEST_F(PrinterInstallerTest, InstallPrinterTwice) {
Printer to_install(kGenericGUID);
delegate_->AddPrinter(to_install);
auto ipp = MakeIppReferencingPrinters(to_install.id());
EXPECT_TRUE(RunInstallPrinterIfNeeded(ipp.get(), to_install));
// |printer_installer_| should notice printer is already installed and bail
// out. If it attempts setup, FakeServiceDelegate will fail the request.
EXPECT_TRUE(RunInstallPrinterIfNeeded(ipp.get(), to_install));
}
} // namespace
} // namespace cups_proxy
......@@ -4481,6 +4481,7 @@ test("unit_tests") {
if (is_chromeos) {
deps += [
"//chrome/browser/chromeos:unit_tests",
"//chrome/services/cups_proxy:unit_tests",
"//chromeos/ime:gencode",
]
sources -=
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment