chromium/chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.cc

// Copyright 2020 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/ash/scanning/zeroconf_scanner_detector_utils.h"

#include <string>

#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
#include "url/gurl.h"

namespace ash {

namespace {

// Sets the values of |scheme| and |protocol| based on |service_type|.
void SetSchemeAndProtocol(const std::string& service_type,
                          std::string& scheme_out,
                          ScanProtocol& protocol_out) {
  if (service_type == ZeroconfScannerDetector::kEsclsServiceType) {
    scheme_out = "https";
    protocol_out = ScanProtocol::kEscls;
  } else if (service_type == ZeroconfScannerDetector::kEsclServiceType) {
    scheme_out = "http";
    protocol_out = ScanProtocol::kEscl;
  } else if (service_type ==
             ZeroconfScannerDetector::kGenericScannerServiceType) {
    protocol_out = ScanProtocol::kLegacyNetwork;
  } else {
    NOTREACHED_IN_MIGRATION()
        << "Zeroconf scanner with unknown service type: " << service_type;
  }
}

// Creates a device name compatible with the given backend. Returns the
// name on success and an empty string on failure.
std::string CreateDeviceName(const std::string& name,
                             const std::string& scheme,
                             const std::optional<std::string>& rs,
                             const net::IPAddress& ip_address,
                             int port,
                             const std::string& backend_prefix) {
  std::string path;
  if (!rs.has_value()) {
    path = "eSCL/";
  } else if (!rs->empty()) {
    // Some scanners report rs=/eSCL instead of rs=eSCL. This causes problems
    // without our URL construction, so trim any leading slashes here. We also
    // trim trailing slashes in case any scanners report rs=eSCL/, since we
    // append a slash after.
    base::TrimString(rs.value(), "/", &path);
    path += "/";
  }

  // Replace colons in the instance name to prevent them from breaking
  // sane-airscan's device name parsing logic.
  std::string sanitized_name;
  base::ReplaceChars(name, ":", "-", &sanitized_name);

  const std::string ip_address_str =
      ip_address.IsIPv6()
          ? base::StringPrintf("[%s]", ip_address.ToString().c_str())
          : ip_address.ToString();
  GURL url(base::StringPrintf("%s://%s:%d/%s", scheme.c_str(),
                              ip_address_str.c_str(), port, path.c_str()));
  if (!url.is_valid()) {
    LOG(ERROR) << "Cannot create device name with invalid URL: "
               << url.possibly_invalid_spec();
    return "";
  }
  return base::StringPrintf("%s:%s:%s", backend_prefix.c_str(),
                            sanitized_name.c_str(), url.spec().c_str());
}

}  // namespace

std::optional<Scanner> CreateSaneScanner(const std::string& name,
                                         const std::string& service_type,
                                         const std::string& manufacturer,
                                         const std::string& model,
                                         const std::string& uuid,
                                         const std::optional<std::string>& rs,
                                         const std::vector<std::string>& pdl,
                                         const net::IPAddress& ip_address,
                                         int port,
                                         bool usable) {
  std::string scheme;
  ScanProtocol protocol = ScanProtocol::kUnknown;
  SetSchemeAndProtocol(service_type, scheme, protocol);
  std::string device_name = "";
  // If the name contains "EPSON" and is a generic service type, use the
  // "epsonds:net" backend.
  if (service_type == ZeroconfScannerDetector::kGenericScannerServiceType &&
      base::StartsWith(name, "EPSON")) {
    device_name = base::StringPrintf("%s:%s", "epsonds:net",
                                     ip_address.ToString().c_str());
  } else if (service_type !=
             ZeroconfScannerDetector::kGenericScannerServiceType) {
    device_name =
        CreateDeviceName(name, scheme, rs, ip_address, port, "airscan:escl");
  }
  if (device_name.empty()) {
    return std::nullopt;
  }

  Scanner scanner;
  scanner.display_name = name;
  scanner.manufacturer = manufacturer;
  scanner.model = model;
  scanner.uuid = uuid;
  scanner.pdl = pdl;
  scanner.device_names[protocol].emplace(
      ScannerDeviceName(device_name, usable));
  scanner.ip_addresses.insert(ip_address);
  return scanner;
}

}  // namespace ash