chromium/chrome/browser/ash/smb_client/smb_share_finder.cc

// Copyright 2018 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/smb_client/smb_share_finder.h"

#include <vector>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/ash/smb_client/discovery/mdns_host_locator.h"
#include "chrome/browser/ash/smb_client/smb_constants.h"
#include "chrome/browser/ash/smb_client/smb_errors.h"

namespace ash::smb_client {

SmbShareFinder::SmbShareFinder(SmbProviderClient* client) : client_(client) {}
SmbShareFinder::~SmbShareFinder() = default;

void SmbShareFinder::GatherSharesInNetwork(
    HostDiscoveryResponse discovery_callback,
    GatherSharesInNetworkResponse shares_callback) {
  const bool is_host_discovery_pending = !discovery_callbacks_.empty();
  const bool is_share_discovery_pending = !share_callbacks_.empty();

  if (is_host_discovery_pending) {
    // Host discovery is currently running, add both |discovery_callback| and
    // |share_callback| to their respective vectors.
    InsertDiscoveryAndShareCallbacks(std::move(discovery_callback),
                                     std::move(shares_callback));
    return;
  }

  if (is_share_discovery_pending) {
    // Host discovery is complete but there are still share callbacks pending.
    // Run |discovery_callback| because pending share discoveries and no pending
    // host discoveries indicate that a host discovery must have recently
    // completed.
    std::move(discovery_callback).Run();
    InsertShareCallback(std::move(shares_callback));
    return;
  }

  // No host discovery or share discovery in progress. This is only because
  // GatherSharesInNetwork has not been called yet or the previous host
  // discovery has been fully completed.
  InsertDiscoveryAndShareCallbacks(std::move(discovery_callback),
                                   std::move(shares_callback));
  scanner_.FindHostsInNetwork(base::BindOnce(&SmbShareFinder::OnHostsFound,
                                             weak_ptr_factory_.GetWeakPtr()));
}

void SmbShareFinder::DiscoverHostsInNetwork(
    HostDiscoveryResponse discovery_callback) {
  const bool is_host_discovery_pending = !discovery_callbacks_.empty();
  const bool is_share_discovery_pending = !share_callbacks_.empty();

  if (is_host_discovery_pending) {
    // Host discovery is currently running, add |discovery_callback| to
    // |discovery_callbacks|.
    InsertDiscoveryCallback(std::move(discovery_callback));
    return;
  }

  if (is_share_discovery_pending) {
    // Host discovery is complete but there are still share callbacks pending.
    // Run |discovery_callback| because pending share discoveries and no pending
    // host discoveries indicate that a host discovery must have recently
    // completed.
    std::move(discovery_callback).Run();
    return;
  }

  // No host discovery or share discovery in progress. This is only because
  // GatherSharesInNetwork has not been called yet or the previous host
  // discovery has been fully completed.
  InsertDiscoveryCallback(std::move(discovery_callback));
  scanner_.FindHostsInNetwork(base::BindOnce(&SmbShareFinder::OnHostsFound,
                                             weak_ptr_factory_.GetWeakPtr()));
}

void SmbShareFinder::RegisterHostLocator(std::unique_ptr<HostLocator> locator) {
  scanner_.RegisterHostLocator(std::move(locator));
}

SmbUrl SmbShareFinder::GetResolvedUrl(const SmbUrl& url) const {
  DCHECK(url.IsValid());

  const std::string ip_address = scanner_.ResolveHost(url.GetHost()).ToString();
  // Return the original URL if the resolved host cannot be found or if there is
  // no change in the resolved IP address.
  if (ip_address.empty() || ip_address == url.GetHost()) {
    return url;
  }

  return url.ReplaceHost(ip_address);
}

net::IPAddress SmbShareFinder::GetResolvedHost(const std::string& host) const {
  DCHECK(!host.empty());
  return scanner_.ResolveHost(host);
}

bool SmbShareFinder::TryResolveUrl(const SmbUrl& url,
                                   SmbUrl* updated_url) const {
  *updated_url = GetResolvedUrl(url);
  return updated_url->ToString() != url.ToString();
}

void SmbShareFinder::OnHostsFound(bool success, const HostMap& hosts) {
  DCHECK_EQ(0u, host_counter_);

  RunDiscoveryCallbacks();

  if (!success) {
    LOG(ERROR) << "SmbShareFinder failed to find hosts";
    RunEmptySharesCallbacks();
    return;
  }

  if (hosts.empty()) {
    RunEmptySharesCallbacks();
    return;
  }

  host_counter_ = hosts.size();
  for (const auto& host : hosts) {
    const std::string& host_name = host.first;
    const std::string resolved_address = host.second.ToString();
    const base::FilePath server_url(kSmbSchemePrefix + resolved_address);

    client_->GetShares(
        server_url, base::BindOnce(&SmbShareFinder::OnSharesFound,
                                   weak_ptr_factory_.GetWeakPtr(), host_name));
  }
}

void SmbShareFinder::OnSharesFound(
    const std::string& host_name,
    smbprovider::ErrorType error,
    const smbprovider::DirectoryEntryListProto& entries) {
  DCHECK_GT(host_counter_, 0u);
  --host_counter_;

  UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.GetSharesResult",
                            TranslateErrorToMountResult(error));

  if (error != smbprovider::ErrorType::ERROR_OK) {
    LOG(ERROR) << "Error finding shares: " << error;
    // Don't early out here because this could be the last host being queried,
    // and the final share discovery callback may need to run.
  }

  for (const smbprovider::DirectoryEntryProto& entry : entries.entries()) {
    SmbUrl url(kSmbSchemePrefix + host_name + "/" + entry.name());
    if (url.IsValid()) {
      shares_.push_back(std::move(url));
    } else {
      LOG(WARNING) << "URL found is not valid";
    }
  }

  if (host_counter_ == 0) {
    RunSharesCallbacks(shares_);
  }
}

void SmbShareFinder::RunDiscoveryCallbacks() {
  for (auto& callback : discovery_callbacks_) {
    std::move(callback).Run();
  }
  discovery_callbacks_.clear();
}

void SmbShareFinder::RunSharesCallbacks(const std::vector<SmbUrl>& shares) {
  for (auto& share_callback : share_callbacks_) {
    std::move(share_callback).Run(shares);
  }
  share_callbacks_.clear();
  shares_.clear();
}

void SmbShareFinder::RunEmptySharesCallbacks() {
  RunSharesCallbacks(std::vector<SmbUrl>());
}

void SmbShareFinder::InsertDiscoveryAndShareCallbacks(
    HostDiscoveryResponse discovery_callback,
    GatherSharesInNetworkResponse share_callback) {
  InsertDiscoveryCallback(std::move(discovery_callback));
  InsertShareCallback(std::move(share_callback));
}

void SmbShareFinder::InsertShareCallback(
    GatherSharesInNetworkResponse share_callback) {
  share_callbacks_.push_back(std::move(share_callback));
}

void SmbShareFinder::InsertDiscoveryCallback(
    HostDiscoveryResponse discovery_callback) {
  discovery_callbacks_.push_back(std::move(discovery_callback));
}

}  // namespace ash::smb_client