blob: 4d16cdf584e2f358d59cca71e837f57b49af3b32 [file] [log] [blame]
// Copyright 2017 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/browser/chromeos/printing/printer_configurer.h"
#include <map>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/hash/md5.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/printing/ppd_provider_factory.h"
#include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon_client.h"
#include "chromeos/printing/ppd_provider.h"
#include "chromeos/printing/printer_configuration.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h"
const std::map<const std::string, const std::string>&
GetComponentizedFilters() {
// A mapping from filter names to available components for downloads.
static const auto* const componentized_filters =
new std::map<const std::string, const std::string>{
{"epson-escpr-wrapper", "epson-inkjet-printer-escpr"},
{"epson-escpr", "epson-inkjet-printer-escpr"},
{"rastertostar", "star-cups-driver"},
{"rastertostarlm", "star-cups-driver"}};
return *componentized_filters;
}
namespace chromeos {
namespace {
PrinterSetupResult PrinterSetupResultFromDbusResultCode(const Printer& printer,
int result_code) {
DCHECK_GE(result_code, 0);
switch (result_code) {
case debugd::CupsResult::CUPS_SUCCESS:
PRINTER_LOG(DEBUG) << printer.make_and_model()
<< " Printer setup successful";
return PrinterSetupResult::kSuccess;
case debugd::CupsResult::CUPS_INVALID_PPD:
PRINTER_LOG(EVENT) << printer.make_and_model() << " PPD Invalid";
return PrinterSetupResult::kInvalidPpd;
case debugd::CupsResult::CUPS_AUTOCONF_FAILURE:
PRINTER_LOG(EVENT) << printer.make_and_model() << " Autoconf failed";
// There are other reasons autoconf fails but this is the most likely.
return PrinterSetupResult::kPrinterUnreachable;
case debugd::CupsResult::CUPS_LPADMIN_FAILURE:
// Printers should always be configurable by lpadmin.
PRINTER_LOG(ERROR) << printer.make_and_model()
<< " lpadmin could not add the printer";
return PrinterSetupResult::kFatalError;
case debugd::CupsResult::CUPS_FATAL:
default:
// We have no idea. It must be fatal.
PRINTER_LOG(ERROR) << printer.make_and_model()
<< " Unrecognized printer setup error: "
<< result_code;
return PrinterSetupResult::kFatalError;
}
}
// Map D-Bus errors from the debug daemon client to D-Bus errors enumerated
// in PrinterSetupResult.
PrinterSetupResult PrinterSetupResultFromDbusErrorCode(
DbusLibraryError dbus_error) {
DCHECK_LT(dbus_error, 0);
static const base::NoDestructor<
base::flat_map<DbusLibraryError, PrinterSetupResult>>
kDbusErrorMap({
{DbusLibraryError::kNoReply, PrinterSetupResult::kDbusNoReply},
{DbusLibraryError::kTimeout, PrinterSetupResult::kDbusTimeout},
});
auto it = kDbusErrorMap->find(dbus_error);
return it != kDbusErrorMap->end() ? it->second
: PrinterSetupResult::kDbusError;
}
// Records whether a |printer| contains a valid PpdReference defined as having
// either autoconf or a ppd reference set.
void RecordValidPpdReference(const Printer& printer) {
const auto& ppd_ref = printer.ppd_reference();
// A PpdReference is valid if exactly one field is set in PpdReference.
int refs = ppd_ref.autoconf ? 1 : 0;
refs += !ppd_ref.user_supplied_ppd_url.empty() ? 1 : 0;
refs += !ppd_ref.effective_make_and_model.empty() ? 1 : 0;
base::UmaHistogramBoolean("Printing.CUPS.ValidPpdReference", refs == 1);
}
// Configures printers by downloading PPDs then adding them to CUPS through
// debugd. This class must be used on the UI thread.
class PrinterConfigurerImpl : public PrinterConfigurer {
public:
explicit PrinterConfigurerImpl(Profile* profile)
: ppd_provider_(CreatePpdProvider(profile)), weak_factory_(this) {}
~PrinterConfigurerImpl() override {}
void SetUpPrinter(const Printer& printer,
PrinterSetupCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!printer.id().empty());
DCHECK(!printer.uri().empty());
PRINTER_LOG(USER) << printer.make_and_model() << " Printer setup requested";
// Record if autoconf and a PPD are set. crbug.com/814374.
RecordValidPpdReference(printer);
if (!printer.IsIppEverywhere()) {
PRINTER_LOG(DEBUG) << printer.make_and_model() << " Lookup PPD";
ppd_provider_->ResolvePpd(
printer.ppd_reference(),
base::BindOnce(&PrinterConfigurerImpl::ResolvePpdDone,
weak_factory_.GetWeakPtr(), printer,
std::move(callback)));
return;
}
PRINTER_LOG(DEBUG) << printer.make_and_model()
<< " Attempting autoconf setup";
auto* client = DBusThreadManager::Get()->GetDebugDaemonClient();
client->CupsAddAutoConfiguredPrinter(
printer.id(), printer.uri(),
base::BindOnce(&PrinterConfigurerImpl::OnAddedPrinter,
weak_factory_.GetWeakPtr(), printer,
std::move(callback)));
}
private:
// Receive the callback from the debug daemon client once we attempt to
// add the printer.
void OnAddedPrinter(const Printer& printer,
PrinterSetupCallback cb,
int32_t result_code) {
// It's expected that debug daemon posts callbacks on the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PrinterSetupResult setup_result =
result_code < 0
? PrinterSetupResultFromDbusErrorCode(
static_cast<DbusLibraryError>(result_code))
: PrinterSetupResultFromDbusResultCode(printer, result_code);
std::move(cb).Run(setup_result);
}
void AddPrinter(const Printer& printer,
const std::string& ppd_contents,
PrinterSetupCallback cb) {
auto* client = DBusThreadManager::Get()->GetDebugDaemonClient();
PRINTER_LOG(EVENT) << printer.make_and_model() << " Manual printer setup";
client->CupsAddManuallyConfiguredPrinter(
printer.id(), printer.uri(), ppd_contents,
base::BindOnce(&PrinterConfigurerImpl::OnAddedPrinter,
weak_factory_.GetWeakPtr(), printer, std::move(cb)));
}
// Executed on component load API finish.
// Check API return result to decide whether component is successfully loaded.
void OnComponentLoad(const Printer& printer,
const std::string& ppd_contents,
PrinterSetupCallback cb,
component_updater::CrOSComponentManager::Error error,
const base::FilePath& path) {
if (error != component_updater::CrOSComponentManager::Error::NONE) {
PRINTER_LOG(ERROR) << printer.make_and_model()
<< " Filter component installation fails.";
std::move(cb).Run(PrinterSetupResult::kComponentUnavailable);
} else {
AddPrinter(printer, ppd_contents, std::move(cb));
}
}
void ResolvePpdSuccess(const Printer& printer,
PrinterSetupCallback cb,
const std::string& ppd_contents,
const std::vector<std::string>& ppd_filters) {
std::set<std::string> components_requested;
for (const auto& ppd_filter : ppd_filters) {
for (const auto& component : GetComponentizedFilters()) {
if (component.first == ppd_filter) {
components_requested.insert(component.second);
}
}
}
if (components_requested.size() == 1) {
// Only allow one filter request in ppd file.
auto& component_name = *components_requested.begin();
g_browser_process->platform_part()->cros_component_manager()->Load(
component_name,
component_updater::CrOSComponentManager::MountPolicy::kMount,
component_updater::CrOSComponentManager::UpdatePolicy::kDontForce,
base::BindOnce(&PrinterConfigurerImpl::OnComponentLoad,
weak_factory_.GetWeakPtr(), printer, ppd_contents,
std::move(cb)));
return;
}
if (components_requested.size() > 1) {
PRINTER_LOG(ERROR) << printer.make_and_model()
<< " More than one filter component is requested.";
std::move(cb).Run(PrinterSetupResult::kFatalError);
return;
}
AddPrinter(printer, ppd_contents, std::move(cb));
}
void ResolvePpdDone(const Printer& printer,
PrinterSetupCallback cb,
PpdProvider::CallbackResultCode result,
const std::string& ppd_contents,
const std::vector<std::string>& ppd_filters) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PRINTER_LOG(EVENT) << printer.make_and_model()
<< " PPD Resolution Result: " << result;
switch (result) {
case PpdProvider::SUCCESS:
DCHECK(!ppd_contents.empty());
ResolvePpdSuccess(printer, std::move(cb), ppd_contents, ppd_filters);
break;
case PpdProvider::CallbackResultCode::NOT_FOUND:
std::move(cb).Run(PrinterSetupResult::kPpdNotFound);
break;
case PpdProvider::CallbackResultCode::SERVER_ERROR:
std::move(cb).Run(PrinterSetupResult::kPpdUnretrievable);
break;
case PpdProvider::CallbackResultCode::INTERNAL_ERROR:
std::move(cb).Run(PrinterSetupResult::kFatalError);
break;
case PpdProvider::CallbackResultCode::PPD_TOO_LARGE:
std::move(cb).Run(PrinterSetupResult::kPpdTooLarge);
break;
}
}
scoped_refptr<PpdProvider> ppd_provider_;
base::WeakPtrFactory<PrinterConfigurerImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PrinterConfigurerImpl);
};
} // namespace
// static
std::string PrinterConfigurer::SetupFingerprint(const Printer& printer) {
base::MD5Context ctx;
base::MD5Init(&ctx);
base::MD5Update(&ctx, printer.id());
base::MD5Update(&ctx, printer.uri());
base::MD5Update(&ctx, printer.ppd_reference().user_supplied_ppd_url);
base::MD5Update(&ctx, printer.ppd_reference().effective_make_and_model);
char autoconf = printer.ppd_reference().autoconf ? 1 : 0;
base::MD5Update(&ctx, std::string(&autoconf, sizeof(autoconf)));
base::MD5Digest digest;
base::MD5Final(&digest, &ctx);
return std::string(reinterpret_cast<char*>(&digest.a[0]), sizeof(digest.a));
}
// static
void PrinterConfigurer::RecordUsbPrinterSetupSource(
UsbPrinterSetupSource source) {
base::UmaHistogramEnumeration("Printing.CUPS.UsbSetupSource", source);
}
// static
std::unique_ptr<PrinterConfigurer> PrinterConfigurer::Create(Profile* profile) {
return std::make_unique<PrinterConfigurerImpl>(profile);
}
std::ostream& operator<<(std::ostream& out, const PrinterSetupResult& result) {
switch (result) {
case kFatalError:
out << "fatal error";
break;
case kSuccess:
out << "add success";
break;
case kEditSuccess:
out << "edit success";
break;
case kPrinterUnreachable:
out << "printer unreachable";
break;
case kDbusError:
out << "failed to connect over dbus";
break;
case kNativePrintersNotAllowed:
out << "native printers denied by policy";
break;
case kInvalidPrinterUpdate:
out << "printer edits would make printer unusable";
break;
case kComponentUnavailable:
out << "component driver was requested but installation failed.";
break;
case kPpdTooLarge:
out << "PPD too large";
break;
case kInvalidPpd:
out << "PPD rejected by cupstestppd";
break;
case kPpdNotFound:
out << "could not find PPD";
break;
case kPpdUnretrievable:
out << "failed to download PPD";
break;
case kDbusNoReply:
out << "no reply from debugd";
break;
case kDbusTimeout:
out << "timeout in D-Bus";
break;
case kMaxValue:
out << "unexpected result";
break;
}
return out;
}
} // namespace chromeos