blob: c7ab7dc15eed47e0ddeb06c85cfca39e5c023d7e [file] [log] [blame]
// Copyright 2016 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/ui/webui/settings/chromeos/cups_printers_handler.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
#include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
#include "chrome/browser/chromeos/printing/ppd_provider_factory.h"
#include "chrome/browser/chromeos/printing/printer_configurer.h"
#include "chrome/browser/chromeos/printing/printer_event_tracker.h"
#include "chrome/browser/chromeos/printing/printer_event_tracker_factory.h"
#include "chrome/browser/chromeos/printing/printer_info.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon_client.h"
#include "chromeos/printing/ppd_cache.h"
#include "chromeos/printing/ppd_line_reader.h"
#include "chromeos/printing/ppd_provider.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/printer_translator.h"
#include "chromeos/printing/printing_constants.h"
#include "chromeos/printing/uri_components.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "google_apis/google_api_keys.h"
#include "net/base/filename_util.h"
#include "net/base/ip_endpoint.h"
#include "net/url_request/url_request_context_getter.h"
#include "printing/backend/print_backend.h"
namespace chromeos {
namespace settings {
namespace {
// These values are written to logs. New enum values can be added, but existing
// enums must never be renumbered or deleted and reused.
enum PpdSourceForHistogram { kUser = 0, kScs = 1, kPpdSourceMax };
constexpr int kPpdMaxLineLength = 255;
void RecordPpdSource(const PpdSourceForHistogram& source) {
UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PpdSource", source, kPpdSourceMax);
}
void OnRemovedPrinter(const Printer::PrinterProtocol& protocol, bool success) {
if (success) {
PRINTER_LOG(DEBUG) << "Printer removal succeeded.";
} else {
PRINTER_LOG(DEBUG) << "Printer removal failed.";
}
UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PrinterRemoved", protocol,
Printer::PrinterProtocol::kProtocolMax);
}
// Log if the IPP attributes request was succesful.
void RecordIppQuerySuccess(bool success) {
UMA_HISTOGRAM_BOOLEAN("Printing.CUPS.IppAttributesSuccess", success);
}
// Returns true if |printer_uri| is an IPP uri.
bool IsIppUri(base::StringPiece printer_uri) {
base::StringPiece::size_type separator_location =
printer_uri.find(url::kStandardSchemeSeparator);
if (separator_location == base::StringPiece::npos) {
return false;
}
base::StringPiece scheme_part = printer_uri.substr(0, separator_location);
return scheme_part == kIppScheme || scheme_part == kIppsScheme;
}
// Query an IPP printer to check for autoconf support where the printer is
// located at |printer_uri|. Results are reported through |callback|. It is an
// error to attempt this with a non-IPP printer.
void QueryAutoconf(const std::string& printer_uri,
const PrinterInfoCallback& callback) {
auto optional = ParseUri(printer_uri);
// Behavior for querying a non-IPP uri is undefined and disallowed.
if (!IsIppUri(printer_uri) || !optional.has_value()) {
PRINTER_LOG(ERROR) << "Printer uri is invalid: " << printer_uri;
callback.Run(false, "", "", "", {}, false);
return;
}
UriComponents uri = optional.value();
QueryIppPrinter(uri.host(), uri.port(), uri.path(), uri.encrypted(),
callback);
}
// Returns the list of |printers| formatted as a CupsPrintersList.
base::Value BuildCupsPrintersList(const std::vector<Printer>& printers) {
base::Value printers_list(base::Value::Type::LIST);
for (const Printer& printer : printers) {
// Some of these printers could be invalid but we want to allow the user
// to edit them. crbug.com/778383
printers_list.GetList().push_back(
base::Value::FromUniquePtrValue(GetCupsPrinterInfo(printer)));
}
base::Value response(base::Value::Type::DICTIONARY);
response.SetKey("printerList", std::move(printers_list));
return response;
}
// Extracts a sanitized value of printerQueue from |printer_dict|. Returns an
// empty string if the value was not present in the dictionary.
std::string GetPrinterQueue(const base::DictionaryValue& printer_dict) {
std::string queue;
if (!printer_dict.GetString("printerQueue", &queue)) {
return queue;
}
if (!queue.empty() && queue[0] == '/') {
// Strip the leading backslash. It is expected that this results in an
// empty string if the input is just a backslash.
queue = queue.substr(1);
}
return queue;
}
// Generates a Printer from |printer_dict| where |printer_dict| is a
// CupsPrinterInfo representation. If any of the required fields are missing,
// returns nullptr.
std::unique_ptr<chromeos::Printer> DictToPrinter(
const base::DictionaryValue& printer_dict) {
std::string printer_id;
std::string printer_name;
std::string printer_description;
std::string printer_manufacturer;
std::string printer_model;
std::string printer_make_and_model;
std::string printer_address;
std::string printer_protocol;
if (!printer_dict.GetString("printerId", &printer_id) ||
!printer_dict.GetString("printerName", &printer_name) ||
!printer_dict.GetString("printerDescription", &printer_description) ||
!printer_dict.GetString("printerManufacturer", &printer_manufacturer) ||
!printer_dict.GetString("printerModel", &printer_model) ||
!printer_dict.GetString("printerMakeAndModel", &printer_make_and_model) ||
!printer_dict.GetString("printerAddress", &printer_address) ||
!printer_dict.GetString("printerProtocol", &printer_protocol)) {
return nullptr;
}
std::string printer_queue = GetPrinterQueue(printer_dict);
std::string printer_uri =
printer_protocol + url::kStandardSchemeSeparator + printer_address;
if (!printer_queue.empty()) {
printer_uri += "/" + printer_queue;
}
auto printer = std::make_unique<chromeos::Printer>(printer_id);
printer->set_display_name(printer_name);
printer->set_description(printer_description);
printer->set_manufacturer(printer_manufacturer);
printer->set_model(printer_model);
printer->set_make_and_model(printer_make_and_model);
printer->set_uri(printer_uri);
return printer;
}
std::string ReadFileToStringWithMaxSize(const base::FilePath& path,
int max_size) {
std::string contents;
// This call can fail, but it doesn't matter for our purposes. If it fails,
// we simply return an empty string for the contents, and it will be rejected
// as an invalid PPD.
base::ReadFileToStringWithMaxSize(path, &contents, max_size);
return contents;
}
// Determines whether changing the URI in |existing_printer| to the URI in
// |new_printer| would be valid. Network printers are not allowed to change
// their protocol to a non-network protocol, but can change anything else.
// Non-network printers are not allowed to change anything in their URI.
bool IsValidUriChange(const Printer& existing_printer,
const Printer& new_printer) {
if (new_printer.GetProtocol() == Printer::PrinterProtocol::kUnknown) {
return false;
}
if (existing_printer.HasNetworkProtocol()) {
return new_printer.HasNetworkProtocol();
}
return existing_printer.uri() == new_printer.uri();
}
// Assumes |info| is a dictionary.
void SetPpdReference(const Printer::PpdReference& ppd_ref, base::Value* info) {
if (!ppd_ref.user_supplied_ppd_url.empty()) {
info->SetKey("ppdRefUserSuppliedPpdUrl",
base::Value(ppd_ref.user_supplied_ppd_url));
} else if (!ppd_ref.effective_make_and_model.empty()) {
info->SetKey("ppdRefEffectiveMakeAndModel",
base::Value(ppd_ref.effective_make_and_model));
} else { // Must be autoconf, shouldn't be possible
NOTREACHED() << "Succeeded in PPD matching without emm";
}
}
Printer::PpdReference GetPpdReference(const base::Value* info) {
const char ppd_ref_pathname[] = "printerPpdReference";
auto* user_supplied_ppd_url =
info->FindPath({ppd_ref_pathname, "userSuppliedPPDUrl"});
auto* effective_make_and_model =
info->FindPath({ppd_ref_pathname, "effectiveMakeAndModel"});
auto* autoconf = info->FindPath({ppd_ref_pathname, "autoconf"});
if (user_supplied_ppd_url != nullptr) {
DCHECK(!effective_make_and_model && !autoconf);
return Printer::PpdReference{user_supplied_ppd_url->GetString(), "", false};
}
if (effective_make_and_model != nullptr) {
DCHECK(!user_supplied_ppd_url && !autoconf);
return Printer::PpdReference{"", effective_make_and_model->GetString(),
false};
}
// Otherwise it must be autoconf
DCHECK(autoconf && autoconf->GetBool());
return Printer::PpdReference{"", "", true};
}
} // namespace
CupsPrintersHandler::CupsPrintersHandler(
Profile* profile,
scoped_refptr<PpdProvider> ppd_provider,
std::unique_ptr<PrinterConfigurer> printer_configurer,
CupsPrintersManager* printers_manager)
: profile_(profile),
ppd_provider_(ppd_provider),
printer_configurer_(std::move(printer_configurer)),
printers_manager_(printers_manager),
printers_manager_observer_(this),
weak_factory_(this) {}
// static
std::unique_ptr<CupsPrintersHandler> CupsPrintersHandler::Create(
content::WebUI* webui) {
Profile* profile(Profile::FromWebUI(webui));
auto ppd_provider = CreatePpdProvider(profile);
auto printer_configurer = PrinterConfigurer::Create(profile);
CupsPrintersManager* printers_manager =
CupsPrintersManagerFactory::GetForBrowserContext(profile);
// Using 'new' to access non-public constructor.
return base::WrapUnique(new CupsPrintersHandler(
profile, ppd_provider, std::move(printer_configurer), printers_manager));
}
// static
std::unique_ptr<CupsPrintersHandler> CupsPrintersHandler::CreateForTesting(
Profile* profile,
scoped_refptr<PpdProvider> ppd_provider,
std::unique_ptr<PrinterConfigurer> printer_configurer,
CupsPrintersManager* printers_manager) {
// Using 'new' to access non-public constructor.
return base::WrapUnique(new CupsPrintersHandler(
profile, ppd_provider, std::move(printer_configurer), printers_manager));
}
CupsPrintersHandler::~CupsPrintersHandler() = default;
void CupsPrintersHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"getCupsPrintersList",
base::BindRepeating(&CupsPrintersHandler::HandleGetCupsPrintersList,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"updateCupsPrinter",
base::BindRepeating(&CupsPrintersHandler::HandleUpdateCupsPrinter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"removeCupsPrinter",
base::BindRepeating(&CupsPrintersHandler::HandleRemoveCupsPrinter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"addCupsPrinter",
base::BindRepeating(&CupsPrintersHandler::HandleAddCupsPrinter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"reconfigureCupsPrinter",
base::BindRepeating(&CupsPrintersHandler::HandleReconfigureCupsPrinter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getPrinterInfo",
base::BindRepeating(&CupsPrintersHandler::HandleGetPrinterInfo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getCupsPrinterManufacturersList",
base::BindRepeating(
&CupsPrintersHandler::HandleGetCupsPrinterManufacturers,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getCupsPrinterModelsList",
base::BindRepeating(&CupsPrintersHandler::HandleGetCupsPrinterModels,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"selectPPDFile",
base::BindRepeating(&CupsPrintersHandler::HandleSelectPPDFile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"startDiscoveringPrinters",
base::BindRepeating(&CupsPrintersHandler::HandleStartDiscovery,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"stopDiscoveringPrinters",
base::BindRepeating(&CupsPrintersHandler::HandleStopDiscovery,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getPrinterPpdManufacturerAndModel",
base::BindRepeating(
&CupsPrintersHandler::HandleGetPrinterPpdManufacturerAndModel,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"addDiscoveredPrinter",
base::BindRepeating(&CupsPrintersHandler::HandleAddDiscoveredPrinter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"cancelPrinterSetUp",
base::BindRepeating(&CupsPrintersHandler::HandleSetUpCancel,
base::Unretained(this)));
}
void CupsPrintersHandler::OnJavascriptAllowed() {
if (!printers_manager_observer_.IsObservingSources()) {
printers_manager_observer_.Add(printers_manager_);
}
}
void CupsPrintersHandler::OnJavascriptDisallowed() {
printers_manager_observer_.RemoveAll();
}
void CupsPrintersHandler::SetWebUIForTest(content::WebUI* web_ui) {
set_web_ui(web_ui);
}
void CupsPrintersHandler::HandleGetCupsPrintersList(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
std::vector<Printer> printers =
printers_manager_->GetPrinters(CupsPrintersManager::kSaved);
auto response = BuildCupsPrintersList(printers);
ResolveJavascriptCallback(base::Value(callback_id), response);
}
void CupsPrintersHandler::HandleUpdateCupsPrinter(const base::ListValue* args) {
std::string printer_id;
std::string printer_name;
CHECK(args->GetString(0, &printer_id));
CHECK(args->GetString(1, &printer_name));
Printer printer(printer_id);
printer.set_display_name(printer_name);
if (!profile_->GetPrefs()->GetBoolean(prefs::kUserNativePrintersAllowed)) {
PRINTER_LOG(DEBUG) << "HandleUpdateCupsPrinter() called when "
"kUserNativePrintersAllowed is set to false";
OnAddedOrEditedPrinterCommon(printer,
PrinterSetupResult::kNativePrintersNotAllowed,
false /* is_automatic */);
// Used to fire the web UI listener.
OnAddOrEditPrinterError(PrinterSetupResult::kNativePrintersNotAllowed);
return;
}
OnAddedOrEditedSpecifiedPrinter(printer, true /* is_printer_edit */,
PrinterSetupResult::kEditSuccess);
}
void CupsPrintersHandler::HandleRemoveCupsPrinter(const base::ListValue* args) {
PRINTER_LOG(USER) << "Removing printer";
std::string printer_id;
std::string printer_name;
CHECK(args->GetString(0, &printer_id));
CHECK(args->GetString(1, &printer_name));
auto printer = printers_manager_->GetPrinter(printer_id);
if (!printer)
return;
// Record removal before the printer is deleted.
PrinterEventTrackerFactory::GetForBrowserContext(profile_)
->RecordPrinterRemoved(*printer);
Printer::PrinterProtocol protocol = printer->GetProtocol();
// Printer is deleted here. Do not access after this line.
printers_manager_->RemoveSavedPrinter(printer_id);
DebugDaemonClient* client = DBusThreadManager::Get()->GetDebugDaemonClient();
client->CupsRemovePrinter(printer_id,
base::BindOnce(&OnRemovedPrinter, protocol),
base::DoNothing());
}
void CupsPrintersHandler::HandleGetPrinterInfo(const base::ListValue* args) {
DCHECK(args);
std::string callback_id;
if (!args->GetString(0, &callback_id)) {
NOTREACHED() << "Expected request for a promise";
return;
}
const base::DictionaryValue* printer_dict = nullptr;
if (!args->GetDictionary(1, &printer_dict)) {
NOTREACHED() << "Dictionary missing";
return;
}
AllowJavascript();
std::string printer_address;
if (!printer_dict->GetString("printerAddress", &printer_address)) {
NOTREACHED() << "Address missing";
return;
}
if (printer_address.empty()) {
// Run the failure callback.
OnAutoconfQueried(callback_id, false, "", "", "", {}, false);
return;
}
std::string printer_queue = GetPrinterQueue(*printer_dict);
std::string printer_protocol;
if (!printer_dict->GetString("printerProtocol", &printer_protocol)) {
NOTREACHED() << "Protocol missing";
return;
}
DCHECK(printer_protocol == kIppScheme || printer_protocol == kIppsScheme)
<< "Printer info requests only supported for IPP and IPPS printers";
PRINTER_LOG(DEBUG) << "Querying printer info";
std::string printer_uri =
base::StringPrintf("%s://%s/%s", printer_protocol.c_str(),
printer_address.c_str(), printer_queue.c_str());
QueryAutoconf(printer_uri,
base::Bind(&CupsPrintersHandler::OnAutoconfQueried,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::OnAutoconfQueriedDiscovered(
std::unique_ptr<Printer> printer,
bool success,
const std::string& make,
const std::string& model,
const std::string& make_and_model,
const std::vector<std::string>& document_formats,
bool ipp_everywhere) {
RecordIppQuerySuccess(success);
if (success) {
// If we queried a valid make and model, use it. The mDNS record isn't
// guaranteed to have it. However, don't overwrite it if the printer
// advertises an empty value through printer-make-and-model.
if (!make_and_model.empty()) {
// manufacturer and model are set with make_and_model because they are
// derived from make_and_model for compatability and are slated for
// removal.
printer->set_manufacturer(make);
printer->set_model(model);
printer->set_make_and_model(make_and_model);
PRINTER_LOG(DEBUG) << "Printer queried for make and model "
<< make_and_model;
}
// Autoconfig available, use it.
if (ipp_everywhere) {
PRINTER_LOG(DEBUG) << "Performing autoconf setup";
printer->mutable_ppd_reference()->autoconf = true;
printer_configurer_->SetUpPrinter(
*printer, base::Bind(&CupsPrintersHandler::OnAddedDiscoveredPrinter,
weak_factory_.GetWeakPtr(), *printer));
return;
}
}
// We don't have enough from discovery to configure the printer. Fill in as
// much information as we can about the printer, and ask the user to supply
// the rest.
PRINTER_LOG(EVENT) << "Could not query printer. Fallback to asking the user";
FireManuallyAddDiscoveredPrinter(*printer);
}
void CupsPrintersHandler::OnAutoconfQueried(
const std::string& callback_id,
bool success,
const std::string& make,
const std::string& model,
const std::string& make_and_model,
const std::vector<std::string>& document_formats,
bool ipp_everywhere) {
RecordIppQuerySuccess(success);
if (!success) {
PRINTER_LOG(DEBUG) << "Could not query printer";
base::DictionaryValue reject;
reject.SetString("message", "Querying printer failed");
RejectJavascriptCallback(base::Value(callback_id), reject);
return;
}
PRINTER_LOG(DEBUG) << "Resolved printer information: make_and_model("
<< make_and_model << ") autoconf(" << ipp_everywhere
<< ")";
// Bundle printer metadata
base::Value info(base::Value::Type::DICTIONARY);
info.SetKey("manufacturer", base::Value(make));
info.SetKey("model", base::Value(model));
info.SetKey("makeAndModel", base::Value(make_and_model));
info.SetKey("autoconf", base::Value(ipp_everywhere));
if (ipp_everywhere) {
info.SetKey("ppdReferenceResolved", base::Value(true));
ResolveJavascriptCallback(base::Value(callback_id), info);
return;
}
PrinterSearchData ppd_search_data;
ppd_search_data.discovery_type =
PrinterSearchData::PrinterDiscoveryType::kManual;
ppd_search_data.make_and_model.push_back(make_and_model);
ppd_search_data.supported_document_formats = document_formats;
// Try to resolve the PPD matching.
ppd_provider_->ResolvePpdReference(
ppd_search_data,
base::BindOnce(&CupsPrintersHandler::OnPpdResolved,
weak_factory_.GetWeakPtr(), callback_id, std::move(info)));
}
void CupsPrintersHandler::OnPpdResolved(const std::string& callback_id,
base::Value info,
PpdProvider::CallbackResultCode res,
const Printer::PpdReference& ppd_ref) {
if (res != PpdProvider::CallbackResultCode::SUCCESS) {
info.SetKey("ppdReferenceResolved", base::Value(false));
ResolveJavascriptCallback(base::Value(callback_id), info);
return;
}
SetPpdReference(ppd_ref, &info);
info.SetKey("ppdReferenceResolved", base::Value(true));
ResolveJavascriptCallback(base::Value(callback_id), info);
}
void CupsPrintersHandler::HandleAddCupsPrinter(const base::ListValue* args) {
AllowJavascript();
AddOrReconfigurePrinter(args, false /* is_printer_edit */);
}
void CupsPrintersHandler::HandleReconfigureCupsPrinter(
const base::ListValue* args) {
AllowJavascript();
AddOrReconfigurePrinter(args, true /* is_printer_edit */);
}
void CupsPrintersHandler::AddOrReconfigurePrinter(const base::ListValue* args,
bool is_printer_edit) {
const base::DictionaryValue* printer_dict = nullptr;
CHECK(args->GetDictionary(0, &printer_dict));
std::unique_ptr<Printer> printer = DictToPrinter(*printer_dict);
if (!printer) {
PRINTER_LOG(ERROR) << "Failed to parse printer URI";
OnAddOrEditPrinterError(PrinterSetupResult::kFatalError);
return;
}
if (!profile_->GetPrefs()->GetBoolean(prefs::kUserNativePrintersAllowed)) {
PRINTER_LOG(DEBUG) << "AddOrReconfigurePrinter() called when "
"kUserNativePrintersAllowed is set to false";
// Used to log UMA metrics.
OnAddedOrEditedPrinterCommon(
*printer, PrinterSetupResult::kNativePrintersNotAllowed, false);
// Used to fire the web UI listener.
OnAddOrEditPrinterError(PrinterSetupResult::kNativePrintersNotAllowed);
return;
}
if (!printer->GetUriComponents().has_value()) {
// If the returned optional does not contain a value then it means that the
// printer's uri was not able to be parsed successfully.
PRINTER_LOG(ERROR) << "Failed to parse printer URI";
OnAddOrEditPrinterError(PrinterSetupResult::kFatalError);
return;
}
// Grab the existing printer object and check that we are not making any
// changes that will make |existing_printer_object| unusable.
if (printer->id().empty()) {
// If the printer object has not already been created, error out since this
// is not a valid case.
PRINTER_LOG(ERROR) << "Failed to parse printer ID";
OnAddOrEditPrinterError(PrinterSetupResult::kFatalError);
return;
}
std::unique_ptr<Printer> existing_printer_object =
printers_manager_->GetPrinter(printer->id());
if (existing_printer_object) {
if (!IsValidUriChange(*existing_printer_object, *printer)) {
OnAddOrEditPrinterError(PrinterSetupResult::kInvalidPrinterUpdate);
return;
}
}
// Read PPD selection if it was used.
std::string ppd_manufacturer;
std::string ppd_model;
printer_dict->GetString("ppdManufacturer", &ppd_manufacturer);
printer_dict->GetString("ppdModel", &ppd_model);
// Read user provided PPD if it was used.
std::string printer_ppd_path;
printer_dict->GetString("printerPPDPath", &printer_ppd_path);
// Checks whether a resolved PPD Reference is available.
bool ppd_ref_resolved = false;
printer_dict->GetBoolean("printerPpdReferenceResolved", &ppd_ref_resolved);
// Verify that the printer is autoconf or a valid ppd path is present.
if (ppd_ref_resolved) {
*printer->mutable_ppd_reference() = GetPpdReference(printer_dict);
} else if (!printer_ppd_path.empty()) {
RecordPpdSource(kUser);
GURL tmp = net::FilePathToFileURL(base::FilePath(printer_ppd_path));
if (!tmp.is_valid()) {
LOG(ERROR) << "Invalid ppd path: " << printer_ppd_path;
OnAddOrEditPrinterError(PrinterSetupResult::kInvalidPpd);
return;
}
printer->mutable_ppd_reference()->user_supplied_ppd_url = tmp.spec();
} else if (!ppd_manufacturer.empty() && !ppd_model.empty()) {
RecordPpdSource(kScs);
// Pull out the ppd reference associated with the selected manufacturer and
// model.
bool found = false;
for (const auto& resolved_printer : resolved_printers_[ppd_manufacturer]) {
if (resolved_printer.name == ppd_model) {
*printer->mutable_ppd_reference() = resolved_printer.ppd_ref;
found = true;
break;
}
}
if (!found) {
LOG(ERROR) << "Failed to get ppd reference";
OnAddOrEditPrinterError(PrinterSetupResult::kPpdNotFound);
return;
}
if (printer->make_and_model().empty()) {
// In lieu of more accurate information, populate the make and model
// fields with the PPD information.
printer->set_manufacturer(ppd_manufacturer);
printer->set_model(ppd_model);
// PPD Model names are actually make and model.
printer->set_make_and_model(ppd_model);
}
} else {
// TODO(https://crbug.com/738514): Support PPD guessing for non-autoconf
// printers. i.e. !autoconf && !manufacturer.empty() && !model.empty()
NOTREACHED()
<< "A configuration option must have been selected to add a printer";
}
printer_configurer_->SetUpPrinter(
*printer,
base::BindOnce(&CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter,
weak_factory_.GetWeakPtr(), *printer, is_printer_edit));
}
void CupsPrintersHandler::OnAddedOrEditedPrinterCommon(
const Printer& printer,
PrinterSetupResult result_code,
bool is_automatic) {
UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PrinterSetupResult", result_code,
PrinterSetupResult::kMaxValue);
switch (result_code) {
case PrinterSetupResult::kSuccess:
UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PrinterAdded",
printer.GetProtocol(), Printer::kProtocolMax);
PRINTER_LOG(USER) << "Performing printer setup";
printers_manager_->PrinterInstalled(printer, is_automatic);
return;
case PrinterSetupResult::kEditSuccess:
PRINTER_LOG(USER) << "Printer updated";
printers_manager_->UpdateSavedPrinter(printer);
return;
case PrinterSetupResult::kPpdNotFound:
PRINTER_LOG(ERROR) << "Could not locate requested PPD";
break;
case PrinterSetupResult::kPpdTooLarge:
PRINTER_LOG(ERROR) << "PPD is too large";
break;
case PrinterSetupResult::kPpdUnretrievable:
PRINTER_LOG(ERROR) << "Could not retrieve PPD from server";
break;
case PrinterSetupResult::kInvalidPpd:
PRINTER_LOG(ERROR) << "Provided PPD is invalid.";
break;
case PrinterSetupResult::kPrinterUnreachable:
PRINTER_LOG(ERROR) << "Could not contact printer for configuration";
break;
case PrinterSetupResult::kComponentUnavailable:
LOG(WARNING) << "Could not install component";
break;
case PrinterSetupResult::kDbusError:
case PrinterSetupResult::kFatalError:
PRINTER_LOG(ERROR) << "Unrecoverable error. Reboot required.";
break;
case PrinterSetupResult::kNativePrintersNotAllowed:
PRINTER_LOG(ERROR)
<< "Unable to add or edit printer due to enterprise policy.";
break;
case PrinterSetupResult::kInvalidPrinterUpdate:
PRINTER_LOG(ERROR)
<< "Requested printer changes would make printer unusable";
break;
case PrinterSetupResult::kDbusNoReply:
PRINTER_LOG(ERROR) << "Couldn't talk to debugd over D-Bus.";
break;
case PrinterSetupResult::kDbusTimeout:
PRINTER_LOG(ERROR) << "Timed out trying to reach debugd over D-Bus.";
break;
case PrinterSetupResult::kMaxValue:
NOTREACHED() << "This is not an expected value";
break;
}
// Log an event that tells us this printer setup failed, so we can get
// statistics about which printers are giving users difficulty.
printers_manager_->RecordSetupAbandoned(printer);
}
void CupsPrintersHandler::OnAddedDiscoveredPrinter(
const Printer& printer,
PrinterSetupResult result_code) {
OnAddedOrEditedPrinterCommon(printer, result_code, true);
if (result_code == PrinterSetupResult::kSuccess) {
FireWebUIListener("on-add-or-edit-cups-printer", base::Value(result_code),
base::Value(printer.display_name()));
} else {
PRINTER_LOG(EVENT) << "Automatic setup failed for discovered printer. "
"Fall back to manual.";
// Could not set up printer. Asking user for manufacturer data.
FireManuallyAddDiscoveredPrinter(printer);
}
}
void CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter(
const Printer& printer,
bool is_printer_edit,
PrinterSetupResult result_code) {
if (is_printer_edit && result_code == PrinterSetupResult::kSuccess) {
result_code = PrinterSetupResult::kEditSuccess;
}
PRINTER_LOG(EVENT) << "Add/Update manual printer: " << result_code;
OnAddedOrEditedPrinterCommon(printer, result_code, false);
FireWebUIListener("on-add-or-edit-cups-printer", base::Value(result_code),
base::Value(printer.display_name()));
}
void CupsPrintersHandler::OnAddOrEditPrinterError(
PrinterSetupResult result_code) {
PRINTER_LOG(EVENT) << "Add printer error: " << result_code;
FireWebUIListener("on-add-or-edit-cups-printer", base::Value(result_code),
base::Value(""));
}
void CupsPrintersHandler::HandleGetCupsPrinterManufacturers(
const base::ListValue* args) {
AllowJavascript();
std::string js_callback;
CHECK_EQ(1U, args->GetSize());
CHECK(args->GetString(0, &js_callback));
ppd_provider_->ResolveManufacturers(
base::Bind(&CupsPrintersHandler::ResolveManufacturersDone,
weak_factory_.GetWeakPtr(), js_callback));
}
void CupsPrintersHandler::HandleGetCupsPrinterModels(
const base::ListValue* args) {
AllowJavascript();
std::string js_callback;
std::string manufacturer;
CHECK_EQ(2U, args->GetSize());
CHECK(args->GetString(0, &js_callback));
CHECK(args->GetString(1, &manufacturer));
// Empty manufacturer queries may be triggered as a part of the ui
// initialization, and should just return empty results.
if (manufacturer.empty()) {
base::DictionaryValue response;
response.SetBoolean("success", true);
response.Set("models", std::make_unique<base::ListValue>());
ResolveJavascriptCallback(base::Value(js_callback), response);
return;
}
ppd_provider_->ResolvePrinters(
manufacturer,
base::Bind(&CupsPrintersHandler::ResolvePrintersDone,
weak_factory_.GetWeakPtr(), manufacturer, js_callback));
}
void CupsPrintersHandler::HandleSelectPPDFile(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
CHECK(args->GetString(0, &webui_callback_id_));
base::FilePath downloads_path =
DownloadPrefs::FromDownloadManager(
content::BrowserContext::GetDownloadManager(profile_))
->DownloadPath();
select_file_dialog_ = ui::SelectFileDialog::Create(
this,
std::make_unique<ChromeSelectFilePolicy>(web_ui()->GetWebContents()));
gfx::NativeWindow owning_window =
chrome::FindBrowserWithWebContents(web_ui()->GetWebContents())
->window()
->GetNativeWindow();
select_file_dialog_->SelectFile(
ui::SelectFileDialog::SELECT_OPEN_FILE, base::string16(), downloads_path,
nullptr, 0, FILE_PATH_LITERAL(""), owning_window, nullptr);
}
void CupsPrintersHandler::ResolveManufacturersDone(
const std::string& js_callback,
PpdProvider::CallbackResultCode result_code,
const std::vector<std::string>& manufacturers) {
auto manufacturers_value = std::make_unique<base::ListValue>();
if (result_code == PpdProvider::SUCCESS) {
manufacturers_value->AppendStrings(manufacturers);
}
base::DictionaryValue response;
response.SetBoolean("success", result_code == PpdProvider::SUCCESS);
response.Set("manufacturers", std::move(manufacturers_value));
ResolveJavascriptCallback(base::Value(js_callback), response);
}
void CupsPrintersHandler::ResolvePrintersDone(
const std::string& manufacturer,
const std::string& js_callback,
PpdProvider::CallbackResultCode result_code,
const PpdProvider::ResolvedPrintersList& printers) {
auto printers_value = std::make_unique<base::ListValue>();
if (result_code == PpdProvider::SUCCESS) {
resolved_printers_[manufacturer] = printers;
for (const auto& printer : printers) {
printers_value->AppendString(printer.name);
}
}
base::DictionaryValue response;
response.SetBoolean("success", result_code == PpdProvider::SUCCESS);
response.Set("models", std::move(printers_value));
ResolveJavascriptCallback(base::Value(js_callback), response);
}
void CupsPrintersHandler::FileSelected(const base::FilePath& path,
int index,
void* params) {
DCHECK(!webui_callback_id_.empty());
// Load the beggining contents of the file located at |path| and callback into
// VerifyPpdContents() in order to determine whether the file appears to be a
// PPD file. The task's priority is USER_BLOCKING because the this task
// updates the UI as a result of a direct user action.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&ReadFileToStringWithMaxSize, path, kPpdMaxLineLength),
base::BindOnce(&CupsPrintersHandler::VerifyPpdContents,
weak_factory_.GetWeakPtr(), path));
}
void CupsPrintersHandler::VerifyPpdContents(const base::FilePath& path,
const std::string& contents) {
std::string result = "";
if (PpdLineReader::ContainsMagicNumber(contents, kPpdMaxLineLength))
result = path.value();
ResolveJavascriptCallback(base::Value(webui_callback_id_),
base::Value(result));
webui_callback_id_.clear();
}
void CupsPrintersHandler::HandleStartDiscovery(const base::ListValue* args) {
PRINTER_LOG(DEBUG) << "Start printer discovery";
discovery_active_ = true;
OnPrintersChanged(
CupsPrintersManager::kAutomatic,
printers_manager_->GetPrinters(CupsPrintersManager::kAutomatic));
OnPrintersChanged(
CupsPrintersManager::kDiscovered,
printers_manager_->GetPrinters(CupsPrintersManager::kDiscovered));
UMA_HISTOGRAM_COUNTS_100(
"Printing.CUPS.PrintersDiscovered",
discovered_printers_.size() + automatic_printers_.size());
// Scan completes immediately right now. Emit done.
FireWebUIListener("on-printer-discovery-done");
}
void CupsPrintersHandler::HandleStopDiscovery(const base::ListValue* args) {
PRINTER_LOG(DEBUG) << "Stop printer discovery";
discovered_printers_.clear();
automatic_printers_.clear();
// Free up memory while we're not discovering.
discovered_printers_.shrink_to_fit();
automatic_printers_.shrink_to_fit();
discovery_active_ = false;
}
void CupsPrintersHandler::HandleSetUpCancel(const base::ListValue* args) {
PRINTER_LOG(DEBUG) << "Printer setup cancelled";
const base::DictionaryValue* printer_dict;
CHECK(args->GetDictionary(0, &printer_dict));
std::unique_ptr<Printer> printer = DictToPrinter(*printer_dict);
if (printer) {
printers_manager_->RecordSetupAbandoned(*printer);
}
}
void CupsPrintersHandler::OnPrintersChanged(
CupsPrintersManager::PrinterClass printer_class,
const std::vector<Printer>& printers) {
switch (printer_class) {
case CupsPrintersManager::kAutomatic:
automatic_printers_ = printers;
UpdateDiscoveredPrinters();
break;
case CupsPrintersManager::kDiscovered:
discovered_printers_ = printers;
UpdateDiscoveredPrinters();
break;
case CupsPrintersManager::kSaved: {
auto printers_list = BuildCupsPrintersList(printers);
FireWebUIListener("on-printers-changed", printers_list);
break;
}
case CupsPrintersManager::kEnterprise:
case CupsPrintersManager::kNumPrinterClasses:
// These classes are not shown.
return;
}
}
void CupsPrintersHandler::UpdateDiscoveredPrinters() {
if (!discovery_active_) {
return;
}
std::unique_ptr<base::ListValue> printers_list =
std::make_unique<base::ListValue>();
for (const Printer& printer : automatic_printers_) {
printers_list->Append(GetCupsPrinterInfo(printer));
}
for (const Printer& printer : discovered_printers_) {
printers_list->Append(GetCupsPrinterInfo(printer));
}
FireWebUIListener("on-printer-discovered", *printers_list);
}
void CupsPrintersHandler::HandleAddDiscoveredPrinter(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
std::string printer_id;
CHECK(args->GetString(0, &printer_id));
PRINTER_LOG(USER) << "Adding discovered printer";
std::unique_ptr<Printer> printer = printers_manager_->GetPrinter(printer_id);
if (printer == nullptr) {
PRINTER_LOG(ERROR) << "Discovered printer disappeared";
// Printer disappeared, so we don't have information about it anymore and
// can't really do much. Fail the add.
FireWebUIListener("on-add-or-edit-cups-printer", base::Value(false),
base::Value(printer_id));
return;
}
if (!printer->GetUriComponents().has_value()) {
PRINTER_LOG(DEBUG) << "Could not parse uri";
// The printer uri was not parsed successfully. Fail the add.
FireWebUIListener("on-add-or-edit-cups-printer", base::Value(false),
base::Value(printer_id));
return;
}
if (printer->ppd_reference().autoconf ||
!printer->ppd_reference().effective_make_and_model.empty() ||
!printer->ppd_reference().user_supplied_ppd_url.empty()) {
PRINTER_LOG(EVENT) << "Start setup of discovered printer";
// If we have something that looks like a ppd reference for this printer,
// try to configure it.
printer_configurer_->SetUpPrinter(
*printer, base::Bind(&CupsPrintersHandler::OnAddedDiscoveredPrinter,
weak_factory_.GetWeakPtr(), *printer));
return;
}
// The mDNS record doesn't guarantee we can setup the printer. Query it to
// see if we want to try IPP.
auto address = printer->GetHostAndPort();
if (address.IsEmpty()) {
PRINTER_LOG(ERROR) << "Address is invalid";
OnAddedDiscoveredPrinter(*printer, PrinterSetupResult::kPrinterUnreachable);
return;
}
endpoint_resolver_->Start(
address, base::BindOnce(&CupsPrintersHandler::OnIpResolved,
weak_factory_.GetWeakPtr(), std::move(printer)));
}
void CupsPrintersHandler::HandleGetPrinterPpdManufacturerAndModel(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(2U, args->GetSize());
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
std::string printer_id;
CHECK(args->GetString(1, &printer_id));
auto printer = printers_manager_->GetPrinter(printer_id);
if (!printer) {
RejectJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
ppd_provider_->ReverseLookup(
printer->ppd_reference().effective_make_and_model,
base::Bind(&CupsPrintersHandler::OnGetPrinterPpdManufacturerAndModel,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::OnGetPrinterPpdManufacturerAndModel(
const std::string& callback_id,
PpdProvider::CallbackResultCode result_code,
const std::string& manufacturer,
const std::string& model) {
if (result_code != PpdProvider::SUCCESS) {
RejectJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
base::DictionaryValue info;
info.SetString("ppdManufacturer", manufacturer);
info.SetString("ppdModel", model);
ResolveJavascriptCallback(base::Value(callback_id), info);
}
void CupsPrintersHandler::FireManuallyAddDiscoveredPrinter(
const Printer& printer) {
FireWebUIListener("on-manually-add-discovered-printer",
*GetCupsPrinterInfo(printer));
}
void CupsPrintersHandler::OnIpResolved(std::unique_ptr<Printer> printer,
const net::IPEndPoint& endpoint) {
bool address_resolved = endpoint.address().IsValid();
UMA_HISTOGRAM_BOOLEAN("Printing.CUPS.AddressResolutionResult",
address_resolved);
if (!address_resolved) {
PRINTER_LOG(ERROR) << printer->make_and_model() << " IP Resolution failed";
OnAddedDiscoveredPrinter(*printer, PrinterSetupResult::kPrinterUnreachable);
return;
}
PRINTER_LOG(EVENT) << printer->make_and_model() << " IP Resolution succeeded";
std::string resolved_uri = printer->ReplaceHostAndPort(endpoint);
if (IsIppUri(resolved_uri)) {
PRINTER_LOG(EVENT) << "Query printer for IPP attributes";
QueryAutoconf(resolved_uri,
base::BindRepeating(
&CupsPrintersHandler::OnAutoconfQueriedDiscovered,
weak_factory_.GetWeakPtr(), base::Passed(&printer)));
return;
}
PRINTER_LOG(EVENT) << "Request make and model from user";
// If it's not an IPP printer, the user must choose a PPD.
FireManuallyAddDiscoveredPrinter(*printer);
}
} // namespace settings
} // namespace chromeos