blob: 258259bc15a2da63fffd5219c33c121df92f370a [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/services/cups_proxy/public/cpp/cups_util.h"
#include <queue>
#include <string>
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "chromeos/printing/printer_configuration.h"
#include "printing/backend/cups_jobs.h"
namespace cups_proxy {
namespace {
// This comparator defines a priority_queue of printers in descending order by
// display name.
class DisplayNameComparator {
public:
bool operator()(const chromeos::Printer& a, const chromeos::Printer& b) {
return a.display_name() < b.display_name();
}
};
// Return the top |k| printers from |printers| sorted alphabetically by display
// name.
std::vector<chromeos::Printer> GetFirstKPrinters(
const std::vector<chromeos::Printer>& printers,
size_t k) {
auto pq =
std::priority_queue<chromeos::Printer, std::vector<chromeos::Printer>,
DisplayNameComparator>();
// Filter through |printers|, only keeping the first |k| printers in the pq.
for (const chromeos::Printer& printer : printers) {
pq.push(printer);
if (pq.size() > k) {
pq.pop();
}
}
// We want the returned list in ascending order, so we assign to ret in
// reverse order.
std::vector<chromeos::Printer> ret;
ret.resize(pq.size());
for (int i = pq.size() - 1; i >= 0; --i) {
ret[i] = pq.top();
pq.pop();
}
return ret;
}
} // namespace
base::Optional<IppResponse> BuildGetDestsResponse(
const IppRequest& request,
const std::vector<chromeos::Printer>& printers) {
IppResponse ret;
// Standard OK status line.
ret.status_line = HttpStatusLine{"HTTP/1.1", "200", "OK"};
// Generic HTTP headers.
ret.headers = std::vector<ipp_converter::HttpHeader>{
{"Content-Language", "en"},
{"Content-Type", "application/ipp"},
{"Server", "CUPS/2.1 IPP/2.1"},
{"X-Frame-Options", "DENY"},
{"Content-Security-Policy", "frame-ancestors 'none'"}};
// Fill in IPP attributes.
ret.ipp = printing::WrapIpp(ippNewResponse(request.ipp.get()));
for (const auto& printer : printers) {
// Setting the printer-uri.
std::string printer_uri = printing::PrinterUriFromName(printer.id());
ippAddString(ret.ipp.get(), IPP_TAG_PRINTER, IPP_TAG_TEXT,
"printer-uri-supported", nullptr, printer_uri.c_str());
// Setting the printer uuid.
ippAddString(ret.ipp.get(), IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name",
nullptr, printer.id().c_str());
// Setting the display name.
ippAddString(ret.ipp.get(), IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
nullptr, printer.display_name().c_str());
// Optional setting of the make_and_model, if known.
if (!printer.make_and_model().empty()) {
ippAddString(ret.ipp.get(), IPP_TAG_PRINTER, IPP_TAG_TEXT,
"printer-make-and-model", nullptr,
printer.make_and_model().c_str());
}
ippAddSeparator(ret.ipp.get());
}
// Add the final content length into headers
const size_t ipp_metadata_sz = ippLength(ret.ipp.get());
ret.headers.push_back(
{"Content-Length", base::NumberToString(ipp_metadata_sz)});
// Build parsed response buffer
// Note: We are reusing the HTTP request building function since the responses
// have the exact same format.
auto response_buffer = ipp_converter::BuildIppRequest(
ret.status_line.http_version, ret.status_line.status_code,
ret.status_line.reason_phrase, ret.headers, ret.ipp.get(), ret.ipp_data);
if (!response_buffer) {
return base::nullopt;
}
ret.buffer = std::move(*response_buffer);
return ret;
}
base::Optional<std::string> GetPrinterId(ipp_t* ipp) {
// We expect the printer id to be embedded in the printer-uri.
ipp_attribute_t* printer_uri_attr =
ippFindAttribute(ipp, "printer-uri", IPP_TAG_URI);
if (!printer_uri_attr) {
return base::nullopt;
}
// Only care about the resource, throw everything else away
char resource[HTTP_MAX_URI], unwanted_buffer[HTTP_MAX_URI];
int unwanted_port;
std::string printer_uri = ippGetString(printer_uri_attr, 0, NULL);
httpSeparateURI(HTTP_URI_CODING_RESOURCE, printer_uri.data(), unwanted_buffer,
HTTP_MAX_URI, unwanted_buffer, HTTP_MAX_URI, unwanted_buffer,
HTTP_MAX_URI, &unwanted_port, resource, HTTP_MAX_URI);
// The printer id should be the last component of the resource.
base::StringPiece uuid(resource);
auto uuid_start = uuid.find_last_of('/');
if (uuid_start == base::StringPiece::npos || uuid_start + 1 >= uuid.size()) {
return base::nullopt;
}
return uuid.substr(uuid_start + 1).as_string();
}
base::Optional<std::string> ParseEndpointForPrinterId(
base::StringPiece endpoint) {
size_t last_path = endpoint.find_last_of('/');
if (last_path == base::StringPiece::npos ||
last_path + 1 >= endpoint.size()) {
return base::nullopt;
}
return endpoint.substr(last_path + 1).as_string();
}
std::vector<chromeos::Printer> FilterPrintersForPluginVm(
const std::vector<chromeos::Printer>& saved,
const std::vector<chromeos::Printer>& enterprise) {
if (saved.size() >= kPluginVmPrinterLimit) {
return std::vector<chromeos::Printer>(
saved.begin(), saved.begin() + kPluginVmPrinterLimit);
}
// Filter down enterprise printers to backfill.
size_t num_enterprise_printers = kPluginVmPrinterLimit - saved.size();
auto filtered_enterprise =
GetFirstKPrinters(enterprise, num_enterprise_printers);
// Concatenate saved printers and filtered_enterprise to return.
std::vector<chromeos::Printer> ret = saved;
ret.insert(ret.end(), filtered_enterprise.begin(), filtered_enterprise.end());
return ret;
}
} // namespace cups_proxy