// Copyright 2016 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/ui/webui/ash/settings/pages/printing/cups_printers_handler.h"
#include <optional>
#include <set>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/ash/printing/cups_printers_manager.h"
#include "chrome/browser/ash/printing/ppd_provider_factory.h"
#include "chrome/browser/ash/printing/printer_event_tracker.h"
#include "chrome/browser/ash/printing/printer_event_tracker_factory.h"
#include "chrome/browser/ash/printing/printer_info.h"
#include "chrome/browser/ash/printing/server_printers_fetcher.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/local_discovery/endpoint_resolver.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_pages.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/ash/settings/pages/printing/server_printer_url_util.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/ppd_line_reader.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/printer_translator.h"
#include "chromeos/printing/printing_constants.h"
#include "chromeos/printing/uri.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_contents.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 "printing/backend/print_backend.h"
#include "printing/printer_status.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/gurl.h"
namespace ash::settings {
namespace {
using ::chromeos::PpdProvider;
using ::chromeos::Printer;
using ::chromeos::PrinterClass;
using ::chromeos::Uri;
using ::printing::PrinterQueryResult;
constexpr int kPpdMaxLineLength = 255;
constexpr char kNearbyAutomaticPrintersHistogramName[] =
"Printing.CUPS.NearbyNetworkAutomaticPrintersCount";
constexpr char kNearbyDiscoveredPrintersHistogramName[] =
"Printing.CUPS.NearbyNetworkDiscoveredPrintersCount";
constexpr char kSavedPrintersCountHistogramName[] =
"Printing.CUPS.SavedPrintersCount";
// Log if the IPP attributes request was succesful.
void RecordIppQueryResult(const PrinterQueryResult& result) {
bool reachable = result != PrinterQueryResult::kHostnameResolution &&
result != PrinterQueryResult::kUnreachable;
UMA_HISTOGRAM_BOOLEAN("Printing.CUPS.IppDeviceReachable", reachable);
if (reachable) {
// Only record whether the query was successful if we reach the printer.
bool query_success = (result == PrinterQueryResult::kSuccess);
UMA_HISTOGRAM_BOOLEAN("Printing.CUPS.IppAttributesSuccess", query_success);
}
}
// Query an IPP printer to check for autoconf support where the printer is
// located at |printer_uri|. Results are reported through |callback|. The
// scheme of |printer_uri| must equal "ipp" or "ipps".
void QueryAutoconf(const Uri& uri, PrinterInfoCallback callback) {
QueryIppPrinter(
uri.GetHostEncoded(), uri.GetPort(), uri.GetPathEncodedAsString(),
uri.GetScheme() == chromeos::kIppsScheme, std::move(callback));
}
// Returns the list of |printers| formatted as a CupsPrintersList.
base::Value::Dict BuildCupsPrintersList(const std::vector<Printer>& printers) {
base::Value::List printers_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.Append(GetCupsPrinterInfo(printer));
}
base::Value::Dict response;
response.Set("printerList", std::move(printers_list));
return response;
}
// 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::Value::Dict& printer_dict) {
const std::string* printer_id = printer_dict.FindString("printerId");
const std::string* printer_name = printer_dict.FindString("printerName");
const std::string* printer_description =
printer_dict.FindString("printerDescription");
const std::string* printer_make_and_model =
printer_dict.FindString("printerMakeAndModel");
const std::string* printer_address =
printer_dict.FindString("printerAddress");
const std::string* printer_protocol =
printer_dict.FindString("printerProtocol");
const std::string* print_server_uri =
printer_dict.FindString("printServerUri");
if (!printer_id || !printer_name || !printer_description ||
!printer_make_and_model || !printer_address || !printer_protocol ||
!print_server_uri) {
return nullptr;
}
std::string printer_queue;
// The protocol "socket" does not allow path.
if (*printer_protocol != "socket") {
if (const std::string* ptr = printer_dict.FindString("printerQueue")) {
printer_queue = *ptr;
// Path must start from '/' character.
if (!printer_queue.empty() && printer_queue.front() != '/') {
printer_queue.insert(0, "/");
}
}
}
auto printer = std::make_unique<chromeos::Printer>(*printer_id);
printer->set_display_name(*printer_name);
printer->set_description(*printer_description);
printer->set_make_and_model(*printer_make_and_model);
printer->set_print_server_uri(*print_server_uri);
Uri uri(*printer_protocol + url::kStandardSchemeSeparator + *printer_address +
printer_queue);
if (uri.GetLastParsingError().status != Uri::ParserStatus::kNoErrors) {
PRINTER_LOG(ERROR) << "Uri parse error: "
<< static_cast<int>(uri.GetLastParsingError().status);
return nullptr;
}
std::string message;
if (!printer->SetUri(uri, &message)) {
PRINTER_LOG(ERROR) << "Incorrect uri: " << message;
return nullptr;
}
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::Dict* info) {
if (!ppd_ref.user_supplied_ppd_url.empty()) {
info->Set("ppdRefUserSuppliedPpdUrl", ppd_ref.user_supplied_ppd_url);
} else if (!ppd_ref.effective_make_and_model.empty()) {
info->Set("ppdRefEffectiveMakeAndModel", ppd_ref.effective_make_and_model);
} else { // Must be autoconf, shouldn't be possible
NOTREACHED_IN_MIGRATION() << "Succeeded in PPD matching without emm";
}
}
Printer::PpdReference GetPpdReference(const base::Value::Dict* info) {
auto* user_supplied_ppd_url =
info->FindByDottedPath("printerPpdReference.userSuppliedPPDUrl");
auto* effective_make_and_model =
info->FindByDottedPath("printerPpdReference.effectiveMakeAndModel");
auto* autoconf = info->FindByDottedPath("printerPpdReference.autoconf");
Printer::PpdReference ret;
if (user_supplied_ppd_url) {
ret.user_supplied_ppd_url = user_supplied_ppd_url->GetString();
}
if (effective_make_and_model) {
ret.effective_make_and_model = effective_make_and_model->GetString();
}
if (autoconf) {
ret.autoconf = autoconf->GetBool();
}
return ret;
}
GURL GenerateHttpCupsServerUrl(const GURL& server_url) {
GURL::Replacements replacement;
replacement.SetSchemeStr("http");
replacement.SetPortStr("631");
return server_url.ReplaceComponents(replacement);
}
} // namespace
CupsPrintersHandler::CupsPrintersHandler(Profile* profile,
CupsPrintersManager* printers_manager)
: CupsPrintersHandler(profile,
CreatePpdProvider(profile),
printers_manager) {}
CupsPrintersHandler::CupsPrintersHandler(
Profile* profile,
scoped_refptr<PpdProvider> ppd_provider,
CupsPrintersManager* printers_manager)
: profile_(profile),
ppd_provider_(ppd_provider),
printers_manager_(printers_manager),
endpoint_resolver_(
std::make_unique<local_discovery::EndpointResolver>()) {}
// static
std::unique_ptr<CupsPrintersHandler> CupsPrintersHandler::CreateForTesting(
Profile* profile,
scoped_refptr<PpdProvider> ppd_provider,
CupsPrintersManager* printers_manager) {
// Using 'new' to access non-public constructor.
return base::WrapUnique(
new CupsPrintersHandler(profile, ppd_provider, printers_manager));
}
CupsPrintersHandler::~CupsPrintersHandler() {
if (select_file_dialog_) {
select_file_dialog_->ListenerDestroyed();
}
}
void CupsPrintersHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"getCupsSavedPrintersList",
base::BindRepeating(&CupsPrintersHandler::HandleGetCupsSavedPrintersList,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getCupsEnterprisePrintersList",
base::BindRepeating(
&CupsPrintersHandler::HandleGetCupsEnterprisePrintersList,
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(
"retrieveCupsPrinterPpd",
base::BindRepeating(&CupsPrintersHandler::HandleRetrieveCupsPrinterPpd,
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)));
web_ui()->RegisterMessageCallback(
"getEulaUrl", base::BindRepeating(&CupsPrintersHandler::HandleGetEulaUrl,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"queryPrintServer",
base::BindRepeating(&CupsPrintersHandler::HandleQueryPrintServer,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"openPrintManagementApp",
base::BindRepeating(&CupsPrintersHandler::HandleOpenPrintManagementApp,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"openScanningApp",
base::BindRepeating(&CupsPrintersHandler::HandleOpenScanningApp,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestPrinterStatus",
base::BindRepeating(&CupsPrintersHandler::HandleRequestPrinterStatus,
base::Unretained(this)));
}
void CupsPrintersHandler::OnJavascriptAllowed() {
DCHECK(!printers_manager_observation_.IsObserving());
printers_manager_observation_.Observe(printers_manager_.get());
DCHECK(!local_printers_observation_.IsObserving());
local_printers_observation_.Observe(printers_manager_.get());
}
void CupsPrintersHandler::OnJavascriptDisallowed() {
printers_manager_observation_.Reset();
local_printers_observation_.Reset();
}
void CupsPrintersHandler::SetWebUIForTest(content::WebUI* web_ui) {
set_web_ui(web_ui);
}
void CupsPrintersHandler::HandleGetCupsSavedPrintersList(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
const std::string& callback_id = args[0].GetString();
std::vector<Printer> printers =
printers_manager_->GetPrinters(PrinterClass::kSaved);
base::UmaHistogramCounts100(kSavedPrintersCountHistogramName,
printers.size());
ResolveJavascriptCallback(base::Value(callback_id),
BuildCupsPrintersList(printers));
}
void CupsPrintersHandler::HandleGetCupsEnterprisePrintersList(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
std::vector<Printer> printers =
printers_manager_->GetPrinters(PrinterClass::kEnterprise);
ResolveJavascriptCallback(base::Value(callback_id),
BuildCupsPrintersList(printers));
}
void CupsPrintersHandler::HandleUpdateCupsPrinter(
const base::Value::List& args) {
CHECK_EQ(3U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& printer_id = args[1].GetString();
const std::string& printer_name = args[2].GetString();
Printer printer(printer_id);
printer.set_display_name(printer_name);
if (!profile_->GetPrefs()->GetBoolean(prefs::kUserPrintersAllowed)) {
PRINTER_LOG(DEBUG) << "HandleUpdateCupsPrinter() called when "
"kUserPrintersAllowed is set to false";
OnAddedOrEditedPrinterCommon(printer,
PrinterSetupResult::kNativePrintersNotAllowed);
// Logs the error and runs the callback.
OnAddOrEditPrinterError(callback_id,
PrinterSetupResult::kNativePrintersNotAllowed);
return;
}
OnAddedOrEditedSpecifiedPrinter(callback_id, printer,
true /* is_printer_edit */,
PrinterSetupResult::kEditSuccess);
}
void CupsPrintersHandler::HandleRetrieveCupsPrinterPpd(
const base::Value::List& args) {
CHECK_EQ(3U, args.size());
const std::string& printer_id = args[0].GetString();
const std::string& printer_name = args[1].GetString();
const std::string& eula = args[2].GetString();
PRINTER_LOG(DEBUG) << "Retrieving printer PPD for " << printer_id << " ("
<< printer_name << ")";
// We first make sure the printer is setup in CUPS backend (when the user logs
// out, CUPS will clear a bunch of cached state).
std::optional<chromeos::Printer> printer =
printers_manager_->GetPrinter(printer_id);
if (!printer) {
PRINTER_LOG(ERROR) << "Unable to retrieve printer " << printer_id;
OnRetrievePpdError(printer_name);
return;
}
printers_manager_->SetUpPrinter(
*printer, /*is_automatic_installation=*/true,
base::BindOnce(&CupsPrintersHandler::OnSetUpPrinter,
weak_factory_.GetWeakPtr(), printer_id, printer_name,
eula));
}
void CupsPrintersHandler::OnSetUpPrinter(const std::string& printer_id,
const std::string& printer_name,
const std::string& eula,
PrinterSetupResult result) {
if (result != PrinterSetupResult::kSuccess) {
PRINTER_LOG(ERROR) << "Cannot setup a printer " << printer_id << " ("
<< printer_name << ")";
OnRetrievePpdError(printer_name);
return;
}
// Once the printer has been setup we can request the PPD.
printscanmgr::CupsRetrievePpdResponse empty_response;
printscanmgr::CupsRetrievePpdRequest request;
request.set_name(printer_id);
PrintscanmgrClient::Get()->CupsRetrievePrinterPpd(
request,
base::BindOnce(&CupsPrintersHandler::OnRetrieveCupsPrinterPpd,
weak_factory_.GetWeakPtr(), printer_name, eula),
base::BindOnce(&CupsPrintersHandler::OnRetrieveCupsPrinterPpd,
weak_factory_.GetWeakPtr(), printer_name, eula,
empty_response));
}
void CupsPrintersHandler::OnRetrieveCupsPrinterPpd(
const std::string& printer_name,
const std::string& eula,
std::optional<printscanmgr::CupsRetrievePpdResponse> response) {
if (!response) {
PRINTER_LOG(ERROR) << "No response to retrieve PPD request";
OnRetrievePpdError(printer_name);
return;
}
if (response->ppd() == "") {
PRINTER_LOG(ERROR) << "Retrieved an empty PPD";
OnRetrievePpdError(printer_name);
return;
}
std::string ppd = response->ppd();
// If we have a eula link, insert that into our PPD as a comment.
if (!eula.empty()) {
const std::string ppd_start(R"(*PPD-Adobe: "4.3")");
std::string::size_type index = ppd.find(ppd_start);
if (index == std::string::npos) {
PRINTER_LOG(ERROR)
<< "Unable to find start of PPD while inserting license";
OnRetrievePpdError(printer_name);
return;
}
index += ppd_start.length();
const std::string eulaText =
l10n_util::GetStringFUTF8(IDS_SETTINGS_PRINTING_CUPS_EULA_NOTICE_HEADER,
std::u16string(eula.begin(), eula.end()));
ppd.insert(index, base::StringPrintf(R"(
*%%
*%% %s
*%%)",
eulaText.data()));
}
WriteAndDisplayPpdFile(printer_name, ppd);
}
void CupsPrintersHandler::OnRetrievePpdError(const std::string& printer_name) {
// When there is an error retrieving the PPD, instead of saving the PPD file
// to the Downloads dir, we write a file containing an error message and
// display that.
const std::string message = l10n_util::GetStringFUTF8(
IDS_SETTINGS_PRINTING_CUPS_VIEW_PPD_ERROR_MESSAGE,
std::u16string(printer_name.begin(), printer_name.end()));
WriteAndDisplayPpdFile(printer_name, message);
}
base::FilePath DownloadPpdFile(const base::FilePath& ppd_file_path_base,
const std::string& ppd) {
// Make sure we don't overwrite any of the user's current files.
const base::FilePath ppd_file_path = base::GetUniquePath(ppd_file_path_base);
if (ppd_file_path.empty()) {
PRINTER_LOG(ERROR) << "Unable to save PPD file ("
<< ppd_file_path_base.value() << ") - file exists";
return base::FilePath();
}
if (!base::WriteFile(ppd_file_path, ppd)) {
PRINTER_LOG(ERROR) << "Unable to save PPD file to specified path: "
<< ppd_file_path.value();
return base::FilePath();
}
return ppd_file_path;
}
void CupsPrintersHandler::WriteAndDisplayPpdFile(
const std::string& printer_name,
const std::string& ppd) {
const base::FilePath downloads_path =
DownloadPrefs::FromDownloadManager(profile_->GetDownloadManager())
->DownloadPath();
// To make sure an appropriate filename is created, remove any dir separators.
std::string sanitized_name = printer_name;
base::ReplaceChars(sanitized_name, "/", "_", &sanitized_name);
const base::FilePath ppd_file_path_base =
downloads_path.Append(sanitized_name).AddExtension("ppd");
// Use USER_BLOCKING here since the user is expecting a new web page to load
// after clicking the View PPD link.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(DownloadPpdFile, ppd_file_path_base, ppd),
base::BindOnce(&CupsPrintersHandler::DisplayPpdFile,
weak_factory_.GetWeakPtr()));
}
void CupsPrintersHandler::DisplayPpdFile(const base::FilePath& ppd_file_path) {
if (ppd_file_path.empty()) {
return;
}
PRINTER_LOG(DEBUG) << "PPD saved to " << ppd_file_path;
ash::NewWindowDelegate::GetPrimary()->OpenUrl(
GURL(base::StringPrintf("file://%s", ppd_file_path.value().c_str())),
ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kSwitchToTab);
}
void CupsPrintersHandler::HandleRemoveCupsPrinter(
const base::Value::List& args) {
PRINTER_LOG(USER) << "Removing printer";
// Printer name also expected in 2nd parameter.
const std::string& printer_id = args[0].GetString();
auto printer = printers_manager_->GetPrinter(printer_id);
if (!printer) {
return;
}
// Record removal before the printer is deleted.
PrinterEventTrackerFactory::GetForBrowserContext(profile_)
->RecordPrinterRemoved(*printer);
// Printer is deleted here. Do not access after this line.
printers_manager_->RemoveSavedPrinter(printer_id);
}
void CupsPrintersHandler::HandleGetPrinterInfo(const base::Value::List& args) {
if (args.empty() || !args[0].is_string()) {
NOTREACHED_IN_MIGRATION() << "Expected request for a promise";
return;
}
const std::string& callback_id = args[0].GetString();
if (args.size() < 2u) {
NOTREACHED_IN_MIGRATION() << "Dictionary missing";
return;
}
const base::Value& printer_value = args[1];
if (!printer_value.is_dict()) {
NOTREACHED_IN_MIGRATION() << "Dictionary missing";
return;
}
const base::Value::Dict& printer_dict = printer_value.GetDict();
AllowJavascript();
const std::string* printer_address =
printer_dict.FindString("printerAddress");
if (!printer_address) {
NOTREACHED_IN_MIGRATION() << "Address missing";
return;
}
std::string printer_queue;
if (const std::string* ptr = printer_dict.FindString("printerQueue")) {
printer_queue = *ptr;
// Path must start from '/' character.
if (!printer_queue.empty() && printer_queue.front() != '/') {
printer_queue = "/" + printer_queue;
}
}
const std::string* printer_protocol =
printer_dict.FindString("printerProtocol");
if (!printer_protocol) {
NOTREACHED_IN_MIGRATION() << "Protocol missing";
return;
}
DCHECK(*printer_protocol == chromeos::kIppScheme ||
*printer_protocol == chromeos::kIppsScheme)
<< "Printer info requests only supported for IPP and IPPS printers";
Uri uri(*printer_protocol + url::kStandardSchemeSeparator + *printer_address +
printer_queue);
if (uri.GetLastParsingError().status != Uri::ParserStatus::kNoErrors ||
!IsValidPrinterUri(uri)) {
// Run the failure callback.
OnAutoconfQueried(callback_id, PrinterQueryResult::kUnknownFailure,
::printing::PrinterStatus(), /*make_and_model=*/"",
/*document_formats=*/{}, /*ipp_everywhere=*/false,
chromeos::PrinterAuthenticationInfo{});
return;
}
PRINTER_LOG(DEBUG) << "Querying printer info";
QueryAutoconf(uri, base::BindOnce(&CupsPrintersHandler::OnAutoconfQueried,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::OnAutoconfQueriedDiscovered(
const std::string& callback_id,
Printer printer,
PrinterQueryResult result,
const ::printing::PrinterStatus& /*printer_status*/,
const std::string& make_and_model,
const std::vector<std::string>& /*document_formats*/,
bool ipp_everywhere,
const chromeos::PrinterAuthenticationInfo& /*auth_info*/) {
RecordIppQueryResult(result);
const bool success = result == PrinterQueryResult::kSuccess;
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()) {
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;
printers_manager_->SetUpPrinter(
printer, /*is_automatic_installation=*/true,
base::BindOnce(&CupsPrintersHandler::OnAddedDiscoveredPrinter,
weak_factory_.GetWeakPtr(), callback_id, 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";
RejectJavascriptCallback(base::Value(callback_id),
GetCupsPrinterInfo(printer));
}
void CupsPrintersHandler::OnAutoconfQueried(
const std::string& callback_id,
PrinterQueryResult result,
const ::printing::PrinterStatus& /*printer_status*/,
const std::string& make_and_model,
const std::vector<std::string>& document_formats,
bool ipp_everywhere,
const chromeos::PrinterAuthenticationInfo& /*auth_info*/) {
RecordIppQueryResult(result);
const bool success = result == PrinterQueryResult::kSuccess;
if (result == PrinterQueryResult::kHostnameResolution ||
result == PrinterQueryResult::kUnreachable) {
PRINTER_LOG(DEBUG) << "Could not reach printer";
RejectJavascriptCallback(
base::Value(callback_id),
base::Value(static_cast<int>(PrinterSetupResult::kPrinterUnreachable)));
return;
}
if (!success) {
PRINTER_LOG(DEBUG) << "Could not query printer";
base::Value::Dict reject;
reject.Set("message", "Querying printer failed");
RejectJavascriptCallback(
base::Value(callback_id),
base::Value(static_cast<int>(PrinterSetupResult::kFatalError)));
return;
}
PRINTER_LOG(DEBUG) << "Resolved printer information: make_and_model("
<< make_and_model << ") autoconf(" << ipp_everywhere
<< ")";
// Bundle printer metadata
base::Value::Dict info;
info.Set("makeAndModel", make_and_model);
info.Set("autoconf", ipp_everywhere);
if (ipp_everywhere) {
info.Set("ppdReferenceResolved", true);
ResolveJavascriptCallback(base::Value(callback_id), info);
return;
}
chromeos::PrinterSearchData ppd_search_data;
ppd_search_data.discovery_type =
chromeos::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::Dict info,
PpdProvider::CallbackResultCode res,
const Printer::PpdReference& ppd_ref,
const std::string& usb_manufacturer) {
if (res != PpdProvider::CallbackResultCode::SUCCESS) {
info.Set("ppdReferenceResolved", false);
ResolveJavascriptCallback(base::Value(callback_id), info);
return;
}
SetPpdReference(ppd_ref, &info);
info.Set("ppdReferenceResolved", true);
ResolveJavascriptCallback(base::Value(callback_id), info);
}
void CupsPrintersHandler::HandleAddCupsPrinter(const base::Value::List& args) {
AllowJavascript();
AddOrReconfigurePrinter(args, false /* is_printer_edit */);
}
void CupsPrintersHandler::HandleReconfigureCupsPrinter(
const base::Value::List& args) {
AllowJavascript();
AddOrReconfigurePrinter(args, true /* is_printer_edit */);
}
void CupsPrintersHandler::AddOrReconfigurePrinter(const base::Value::List& args,
bool is_printer_edit) {
CHECK_EQ(2U, args.size());
std::string callback_id = args[0].GetString();
const base::Value& printer_value = args[1];
CHECK(printer_value.is_dict());
const base::Value::Dict& printer_dict = printer_value.GetDict();
std::unique_ptr<Printer> printer = DictToPrinter(printer_dict);
if (!printer) {
PRINTER_LOG(ERROR) << "Failed to parse printer URI";
OnAddOrEditPrinterError(callback_id, PrinterSetupResult::kFatalError);
return;
}
if (!profile_->GetPrefs()->GetBoolean(prefs::kUserPrintersAllowed)) {
PRINTER_LOG(DEBUG) << "AddOrReconfigurePrinter() called when "
"kUserPrintersAllowed is set to false";
OnAddedOrEditedPrinterCommon(*printer,
PrinterSetupResult::kNativePrintersNotAllowed);
// Used to fire the web UI listener.
OnAddOrEditPrinterError(callback_id,
PrinterSetupResult::kNativePrintersNotAllowed);
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(callback_id, PrinterSetupResult::kFatalError);
return;
}
std::optional<Printer> existing_printer_object =
printers_manager_->GetPrinter(printer->id());
if (existing_printer_object) {
if (!IsValidUriChange(*existing_printer_object, *printer)) {
OnAddOrEditPrinterError(callback_id,
PrinterSetupResult::kInvalidPrinterUpdate);
return;
}
}
// Read PPD selection if it was used.
const std::string* ppd_manufacturer =
printer_dict.FindString("ppdManufacturer");
const std::string* ppd_model = printer_dict.FindString("ppdModel");
// Read user provided PPD if it was used.
const std::string* printer_ppd_path =
printer_dict.FindString("printerPPDPath");
// Check if the printer already has a valid ppd_reference.
Printer::PpdReference ppd_ref = GetPpdReference(&printer_dict);
if (ppd_ref.IsFilled()) {
*printer->mutable_ppd_reference() = ppd_ref;
} else if (printer_ppd_path && !printer_ppd_path->empty()) {
GURL tmp = net::FilePathToFileURL(base::FilePath(*printer_ppd_path));
if (!tmp.is_valid()) {
LOG(ERROR) << "Invalid ppd path: " << *printer_ppd_path;
OnAddOrEditPrinterError(callback_id, PrinterSetupResult::kInvalidPpd);
return;
}
printer->mutable_ppd_reference()->user_supplied_ppd_url = tmp.spec();
} else if (ppd_manufacturer && !ppd_manufacturer->empty() && ppd_model &&
!ppd_model->empty()) {
// 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(callback_id, PrinterSetupResult::kPpdNotFound);
return;
}
if (printer->make_and_model().empty()) {
// 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_IN_MIGRATION()
<< "A configuration option must have been selected to add a printer";
}
printers_manager_->SetUpPrinter(
*printer,
/*is_automatic_installation=*/false,
base::BindOnce(&CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter,
weak_factory_.GetWeakPtr(), callback_id, *printer,
is_printer_edit));
}
void CupsPrintersHandler::OnAddedOrEditedPrinterCommon(
const Printer& printer,
PrinterSetupResult result_code) {
if (printer.IsZeroconf()) {
UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.ZeroconfPrinterSetupResult",
result_code, PrinterSetupResult::kMaxValue);
} else {
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_->SavePrinter(printer);
if (printer.IsUsbProtocol()) {
// Record UMA for USB printer setup source.
PrinterConfigurer::RecordUsbPrinterSetupSource(
UsbPrinterSetupSource::kSettings);
}
return;
case PrinterSetupResult::kEditSuccess:
PRINTER_LOG(USER) << ResultCodeToMessage(result_code);
printers_manager_->SavePrinter(printer);
return;
case PrinterSetupResult::kNativePrintersNotAllowed:
case PrinterSetupResult::kBadUri:
case PrinterSetupResult::kInvalidPrinterUpdate:
case PrinterSetupResult::kPrinterUnreachable:
case PrinterSetupResult::kPrinterSentWrongResponse:
case PrinterSetupResult::kPrinterIsNotAutoconfigurable:
case PrinterSetupResult::kPpdTooLarge:
case PrinterSetupResult::kInvalidPpd:
case PrinterSetupResult::kPpdNotFound:
case PrinterSetupResult::kPpdUnretrievable:
case PrinterSetupResult::kDbusError:
case PrinterSetupResult::kDbusNoReply:
case PrinterSetupResult::kDbusTimeout:
case PrinterSetupResult::kIoError:
case PrinterSetupResult::kMemoryAllocationError:
case PrinterSetupResult::kFatalError:
case PrinterSetupResult::kManualSetupRequired:
case PrinterSetupResult::kPrinterRemoved:
PRINTER_LOG(ERROR) << ResultCodeToMessage(result_code);
break;
case PrinterSetupResult::kComponentUnavailable:
NOTREACHED_IN_MIGRATION() << ResultCodeToMessage(result_code);
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 std::string& callback_id,
const Printer& printer,
PrinterSetupResult result_code) {
OnAddedOrEditedPrinterCommon(printer, result_code);
if (result_code == PrinterSetupResult::kSuccess) {
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(static_cast<int>(result_code)));
} else {
PRINTER_LOG(EVENT) << "Automatic setup failed for discovered printer. "
"Fall back to manual.";
// Could not set up printer. Asking user for manufacturer data.
RejectJavascriptCallback(base::Value(callback_id),
GetCupsPrinterInfo(printer));
}
}
void CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter(
const std::string& callback_id,
const Printer& printer,
bool is_printer_edit,
PrinterSetupResult result_code) {
if (is_printer_edit && result_code == PrinterSetupResult::kSuccess) {
result_code = PrinterSetupResult::kEditSuccess;
}
const int result_code_int = static_cast<int>(result_code);
PRINTER_LOG(EVENT) << "Add/Update manual printer: " << result_code_int;
OnAddedOrEditedPrinterCommon(printer, result_code);
if (result_code != PrinterSetupResult::kSuccess &&
result_code != PrinterSetupResult::kEditSuccess) {
RejectJavascriptCallback(base::Value(callback_id),
base::Value(result_code_int));
return;
}
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(result_code_int));
}
void CupsPrintersHandler::OnAddOrEditPrinterError(
const std::string& callback_id,
PrinterSetupResult result_code) {
const int result_code_int = static_cast<int>(result_code);
PRINTER_LOG(EVENT) << "Add printer error: " << result_code_int;
RejectJavascriptCallback(base::Value(callback_id),
base::Value(result_code_int));
}
void CupsPrintersHandler::HandleGetCupsPrinterManufacturers(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
const std::string& callback_id = args[0].GetString();
ppd_provider_->ResolveManufacturers(
base::BindOnce(&CupsPrintersHandler::ResolveManufacturersDone,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::HandleGetCupsPrinterModels(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& manufacturer = args[1].GetString();
// Empty manufacturer queries may be triggered as a part of the ui
// initialization, and should just return empty results.
if (manufacturer.empty()) {
base::Value::Dict response;
response.Set("success", true);
response.Set("models", base::Value::List());
ResolveJavascriptCallback(base::Value(callback_id), response);
return;
}
ppd_provider_->ResolvePrinters(
manufacturer,
base::BindOnce(&CupsPrintersHandler::ResolvePrintersDone,
weak_factory_.GetWeakPtr(), manufacturer, callback_id));
}
void CupsPrintersHandler::HandleSelectPPDFile(const base::Value::List& args) {
// Early return if the select file dialog is already active.
if (select_file_dialog_) {
return;
}
CHECK_EQ(1U, args.size());
webui_callback_id_ = args[0].GetString();
base::FilePath downloads_path =
DownloadPrefs::FromDownloadManager(profile_->GetDownloadManager())
->DownloadPath();
content::WebContents* web_contents = web_ui()->GetWebContents();
select_file_dialog_ = ui::SelectFileDialog::Create(
this, std::make_unique<ChromeSelectFilePolicy>(web_contents));
gfx::NativeWindow owning_window =
web_contents ? chrome::FindBrowserWithTab(web_contents)
->window()
->GetNativeWindow()
: gfx::NativeWindow();
ui::SelectFileDialog::FileTypeInfo file_type_info;
file_type_info.extensions.push_back({"ppd"});
file_type_info.extensions.push_back({"ppd.gz"});
select_file_dialog_->SelectFile(
ui::SelectFileDialog::SELECT_OPEN_FILE, std::u16string(), downloads_path,
&file_type_info, 0, FILE_PATH_LITERAL(""), owning_window);
}
void CupsPrintersHandler::ResolveManufacturersDone(
const std::string& callback_id,
PpdProvider::CallbackResultCode result_code,
const std::vector<std::string>& manufacturers) {
base::Value::List manufacturers_value;
if (result_code == PpdProvider::SUCCESS) {
for (const std::string& manufacturer : manufacturers) {
manufacturers_value.Append(manufacturer);
}
}
base::Value::Dict response;
response.Set("success", result_code == PpdProvider::SUCCESS);
response.Set("manufacturers", std::move(manufacturers_value));
ResolveJavascriptCallback(base::Value(callback_id), response);
}
void CupsPrintersHandler::ResolvePrintersDone(
const std::string& manufacturer,
const std::string& callback_id,
PpdProvider::CallbackResultCode result_code,
const PpdProvider::ResolvedPrintersList& printers) {
base::Value::List printers_value;
if (result_code == PpdProvider::SUCCESS) {
resolved_printers_[manufacturer] = printers;
for (const auto& printer : printers) {
printers_value.Append(printer.name);
}
}
base::Value::Dict response;
response.Set("success", result_code == PpdProvider::SUCCESS);
response.Set("models", std::move(printers_value));
ResolveJavascriptCallback(base::Value(callback_id), response);
}
void CupsPrintersHandler::FileSelected(const ui::SelectedFileInfo& file,
int index) {
DCHECK(!webui_callback_id_.empty());
select_file_dialog_ = nullptr;
// Load the beginning contents of |file| 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::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&ReadFileToStringWithMaxSize, file.path(),
kPpdMaxLineLength),
base::BindOnce(&CupsPrintersHandler::VerifyPpdContents,
weak_factory_.GetWeakPtr(), file.path()));
}
void CupsPrintersHandler::FileSelectionCanceled() {
select_file_dialog_ = nullptr;
}
void CupsPrintersHandler::VerifyPpdContents(const base::FilePath& path,
const std::string& contents) {
std::string result;
if (chromeos::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::Value::List& args) {
PRINTER_LOG(DEBUG) << "Start printer discovery";
AllowJavascript();
discovery_active_ = true;
OnPrintersChanged(PrinterClass::kAutomatic,
printers_manager_->GetPrinters(PrinterClass::kAutomatic));
OnPrintersChanged(PrinterClass::kDiscovered,
printers_manager_->GetPrinters(PrinterClass::kDiscovered));
base::UmaHistogramCounts100(kNearbyAutomaticPrintersHistogramName,
automatic_printers_.size());
base::UmaHistogramCounts100(kNearbyDiscoveredPrintersHistogramName,
discovered_printers_.size());
UMA_HISTOGRAM_COUNTS_100(
"Printing.CUPS.PrintersDiscovered",
discovered_printers_.size() + automatic_printers_.size());
printers_manager_->RecordNearbyNetworkPrinterCounts();
// Scan completes immediately right now. Emit done.
FireWebUIListener("on-printer-discovery-done");
}
void CupsPrintersHandler::HandleStopDiscovery(const base::Value::List& 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::Value::List& args) {
PRINTER_LOG(DEBUG) << "Printer setup cancelled";
const base::Value& printer_value = args[0];
CHECK(printer_value.is_dict());
std::unique_ptr<Printer> printer = DictToPrinter(printer_value.GetDict());
if (printer) {
printers_manager_->RecordSetupAbandoned(*printer);
}
}
void CupsPrintersHandler::OnPrintersChanged(
PrinterClass printer_class,
const std::vector<Printer>& printers) {
switch (printer_class) {
case PrinterClass::kAutomatic:
automatic_printers_ = printers;
UpdateDiscoveredPrinters();
break;
case PrinterClass::kDiscovered:
discovered_printers_ = printers;
UpdateDiscoveredPrinters();
break;
case PrinterClass::kSaved: {
FireWebUIListener("on-saved-printers-changed",
BuildCupsPrintersList(printers));
break;
}
case PrinterClass::kEnterprise:
FireWebUIListener("on-enterprise-printers-changed",
BuildCupsPrintersList(printers));
break;
}
}
void CupsPrintersHandler::OnLocalPrintersUpdated() {
const std::vector<chromeos::Printer> printers =
printers_manager_->GetPrinters(PrinterClass::kSaved);
base::Value::List printers_as_values =
base::Value::List::with_capacity(printers.size());
for (const auto& printer : printers) {
printers_as_values.Append(GetCupsPrinterInfo(printer));
}
FireWebUIListener("local-printers-updated", printers_as_values);
}
void CupsPrintersHandler::UpdateDiscoveredPrinters() {
if (!discovery_active_) {
PRINTER_LOG(DEBUG) << "Discovered printers update skipped";
return;
}
base::Value::List automatic_printers_list;
for (const Printer& printer : automatic_printers_) {
automatic_printers_list.Append(GetCupsPrinterInfo(printer));
}
base::Value::List discovered_printers_list;
for (const Printer& printer : discovered_printers_) {
discovered_printers_list.Append(GetCupsPrinterInfo(printer));
}
PRINTER_LOG(DEBUG) << "Discovered printers updating. Automatic: "
<< automatic_printers_list.size()
<< " Discovered: " << discovered_printers_list.size();
FireWebUIListener("on-nearby-printers-changed", automatic_printers_list,
discovered_printers_list);
}
void CupsPrintersHandler::HandleAddDiscoveredPrinter(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& printer_id = args[1].GetString();
PRINTER_LOG(USER) << "Adding discovered printer " << printer_id;
std::optional<Printer> printer = printers_manager_->GetPrinter(printer_id);
if (!printer) {
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.
ResolveJavascriptCallback(
base::Value(callback_id),
base::Value(static_cast<int>(PrinterSetupResult::kPrinterUnreachable)));
return;
}
if (!printer->HasUri()) {
PRINTER_LOG(DEBUG) << "Could not parse uri";
// The printer uri was not parsed successfully. Fail the add.
ResolveJavascriptCallback(
base::Value(callback_id),
base::Value(static_cast<int>(PrinterSetupResult::kPrinterUnreachable)));
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.
printers_manager_->SetUpPrinter(
*printer, /*is_automatic_installation=*/true,
base::BindOnce(&CupsPrintersHandler::OnAddedDiscoveredPrinter,
weak_factory_.GetWeakPtr(), callback_id, *printer));
return;
}
// We need a special case for USB printers here. We cannot query them
// directly, so we have to fall back to manual configuration here.
if (printer->IsUsbProtocol()) {
RejectJavascriptCallback(base::Value(callback_id),
GetCupsPrinterInfo(*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(callback_id, *printer,
PrinterSetupResult::kPrinterUnreachable);
return;
}
endpoint_resolver_->Start(
address, base::BindOnce(&CupsPrintersHandler::OnIpResolved,
weak_factory_.GetWeakPtr(), callback_id,
std::move(*printer)));
}
void CupsPrintersHandler::HandleGetPrinterPpdManufacturerAndModel(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& printer_id = args[1].GetString();
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::BindOnce(&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::Value::Dict info;
info.Set("ppdManufacturer", manufacturer);
info.Set("ppdModel", model);
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(std::move(info)));
}
void CupsPrintersHandler::HandleGetEulaUrl(const base::Value::List& args) {
CHECK_EQ(3U, args.size());
const std::string callback_id = args[0].GetString();
const std::string ppd_manufacturer = args[1].GetString();
const std::string ppd_model = args[2].GetString();
auto resolved_printers_it = resolved_printers_.find(ppd_manufacturer);
if (resolved_printers_it == resolved_printers_.end()) {
// Exit early if lookup for printers fails for |ppd_manufacturer|.
OnGetEulaUrl(callback_id, PpdProvider::CallbackResultCode::NOT_FOUND,
/*license=*/std::string());
return;
}
const PpdProvider::ResolvedPrintersList& printers_for_manufacturer =
resolved_printers_it->second;
auto printer_it =
base::ranges::find(printers_for_manufacturer, ppd_model,
&PpdProvider::ResolvedPpdReference::name);
if (printer_it == printers_for_manufacturer.end()) {
// Unable to find the PpdReference, resolve promise with empty string.
OnGetEulaUrl(callback_id, PpdProvider::CallbackResultCode::NOT_FOUND,
/*license=*/std::string());
return;
}
ppd_provider_->ResolvePpdLicense(
printer_it->ppd_ref.effective_make_and_model,
base::BindOnce(&CupsPrintersHandler::OnGetEulaUrl,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::OnGetEulaUrl(const std::string& callback_id,
PpdProvider::CallbackResultCode result,
const std::string& license) {
if (result != PpdProvider::SUCCESS || license.empty()) {
ResolveJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
GURL eula_url = PrinterConfigurer::GeneratePrinterEulaUrl(license);
ResolveJavascriptCallback(
base::Value(callback_id),
eula_url.is_valid() ? base::Value(eula_url.spec()) : base::Value());
}
void CupsPrintersHandler::OnIpResolved(const std::string& callback_id,
const 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(callback_id, printer,
PrinterSetupResult::kPrinterUnreachable);
return;
}
PRINTER_LOG(EVENT) << printer.make_and_model() << " IP Resolution succeeded";
const Uri uri = printer.ReplaceHostAndPort(endpoint);
if (IsIppUri(uri)) {
PRINTER_LOG(EVENT) << "Query printer for IPP attributes";
QueryAutoconf(
uri, base::BindOnce(&CupsPrintersHandler::OnAutoconfQueriedDiscovered,
weak_factory_.GetWeakPtr(), callback_id, printer));
return;
}
PRINTER_LOG(EVENT) << "Request make and model from user";
// If it's not an IPP printer, the user must choose a PPD.
RejectJavascriptCallback(base::Value(callback_id),
GetCupsPrinterInfo(printer));
}
void CupsPrintersHandler::HandleQueryPrintServer(
const base::Value::List& args) {
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& server_url = args[1].GetString();
std::optional<GURL> converted_server_url =
GenerateServerPrinterUrlWithValidScheme(server_url);
if (!converted_server_url) {
RejectJavascriptCallback(
base::Value(callback_id),
base::Value(PrintServerQueryResult::kIncorrectUrl));
return;
}
// Use fallback only if HasValidServerPrinterScheme is false.
QueryPrintServer(callback_id, converted_server_url.value(),
!HasValidServerPrinterScheme(GURL(server_url)));
}
void CupsPrintersHandler::QueryPrintServer(const std::string& callback_id,
const GURL& server_url,
bool should_fallback) {
server_printers_fetcher_ = std::make_unique<ServerPrintersFetcher>(
profile_, server_url, "(from user)",
base::BindRepeating(&CupsPrintersHandler::OnQueryPrintServerCompleted,
weak_factory_.GetWeakPtr(), callback_id,
should_fallback));
}
void CupsPrintersHandler::OnQueryPrintServerCompleted(
const std::string& callback_id,
bool should_fallback,
const ServerPrintersFetcher* sender,
const GURL& server_url,
std::vector<PrinterDetector::DetectedPrinter>&& returned_printers) {
const PrintServerQueryResult result = sender->GetLastError();
if (result != PrintServerQueryResult::kNoErrors) {
if (should_fallback) {
// Apply the fallback query.
QueryPrintServer(callback_id, GenerateHttpCupsServerUrl(server_url),
/*should_fallback=*/false);
return;
}
RejectJavascriptCallback(base::Value(callback_id), base::Value(result));
return;
}
// Get all "saved" printers and organize them according to their URL.
const std::vector<Printer> saved_printers =
printers_manager_->GetPrinters(PrinterClass::kSaved);
std::set<GURL> known_printers;
for (const Printer& printer : saved_printers) {
std::optional<GURL> gurl =
GenerateServerPrinterUrlWithValidScheme(printer.uri().GetNormalized());
if (gurl) {
known_printers.insert(gurl.value());
}
}
// Built final list of printers and a list of current names. If "current name"
// is a null value, then a corresponding printer is not saved in the profile
// (it can be added).
std::vector<Printer> printers;
printers.reserve(returned_printers.size());
for (PrinterDetector::DetectedPrinter& printer : returned_printers) {
printers.push_back(std::move(printer.printer));
std::optional<GURL> printer_gurl = GenerateServerPrinterUrlWithValidScheme(
printers.back().uri().GetNormalized());
if (printer_gurl && known_printers.count(printer_gurl.value())) {
printers.pop_back();
}
}
// Delete fetcher object.
server_printers_fetcher_.reset();
// Create result value and finish the callback.
ResolveJavascriptCallback(base::Value(callback_id),
BuildCupsPrintersList(printers));
}
void CupsPrintersHandler::HandleOpenPrintManagementApp(
const base::Value::List& args) {
DCHECK(args.empty());
chrome::ShowPrintManagementApp(profile_);
}
void CupsPrintersHandler::HandleOpenScanningApp(const base::Value::List& args) {
DCHECK(args.empty());
chrome::ShowScanningApp(profile_);
}
void CupsPrintersHandler::HandleRequestPrinterStatus(
const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& printer_id = args[1].GetString();
printers_manager_->FetchPrinterStatus(
printer_id, base::BindOnce(&CupsPrintersHandler::OnPrinterStatusReceived,
weak_factory_.GetWeakPtr(), callback_id));
}
void CupsPrintersHandler::OnPrinterStatusReceived(
const std::string& callback_id,
const chromeos::CupsPrinterStatus& printer_status) {
if (!IsJavascriptAllowed()) {
return;
}
ResolveJavascriptCallback(base::Value(callback_id),
printer_status.ConvertToValue());
}
} // namespace ash::settings