text: // Copyright 2017 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 "extensions/browser/api/feedback_private/log_source_access_manager.h"
  
  #include <algorithm>
  #include <utility>
  
  #include "base/bind.h"
  #include "base/strings/string_split.h"
  #include "base/task/post_task.h"
  #include "base/task/task_runner_util.h"
  #include "base/task/thread_pool.h"
  #include "base/time/default_tick_clock.h"
  #include "extensions/browser/api/api_resource_manager.h"
  #include "extensions/browser/api/extensions_api_client.h"
  #include "extensions/browser/api/feedback_private/feedback_private_delegate.h"
  #include "extensions/browser/api/feedback_private/log_source_resource.h"
  
  namespace extensions {
  
  namespace {
  
  using LogSource = api::feedback_private::LogSource;
  using ReadLogSourceParams = api::feedback_private::ReadLogSourceParams;
  using ReadLogSourceResult = api::feedback_private::ReadLogSourceResult;
  using SystemLogsResponse = system_logs::SystemLogsResponse;
  
  // Default value of |g_max_num_burst_accesses|.
  constexpr int kDefaultMaxNumBurstAccesses = 10;
  
  // The minimum time between consecutive reads of a log source by a particular
  // extension.
  constexpr base::TimeDelta kDefaultRateLimitingTimeout =
      base::Milliseconds(1000);
  
  // The maximum number of accesses on a single log source that can be allowed
  // before the next recharge increment. See access_rate_limiter.h for more info.
  int g_max_num_burst_accesses = kDefaultMaxNumBurstAccesses;
  
  // If this is null, then |kDefaultRateLimitingTimeoutMs| is used as the timeout.
  const base::TimeDelta* g_rate_limiting_timeout = nullptr;
  
  base::TimeDelta GetMinTimeBetweenReads() {
    return g_rate_limiting_timeout ? *g_rate_limiting_timeout
                                   : kDefaultRateLimitingTimeout;
  }
  
  // SystemLogsResponse is a map of strings -> strings. The map value has the
  // actual log contents, a string containing all lines, separated by newlines.
  // This function extracts the individual lines and converts them into a vector
  // of strings, each string containing a single line.
  void GetLogLinesFromSystemLogsResponse(const SystemLogsResponse& response,
                                         std::vector<std::string>* log_lines) {
    for (const std::pair<const std::string, std::string>& pair : response) {
      std::vector<std::string> new_lines = base::SplitString(
          pair.second, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
      log_lines->reserve(log_lines->size() + new_lines.size());
      log_lines->insert(log_lines->end(), new_lines.begin(), new_lines.end());
    }
  }
  
  // Redacts the strings in |result|.
  void RedactResults(
      scoped_refptr<feedback::RedactionToolContainer> redactor_container,
      ReadLogSourceResult* result) {
    feedback::RedactionTool* redactor = redactor_container->Get();
    for (std::string& line : result->log_lines)
      line = redactor->Redact(line);
  }
  
  }  // namespace
  
  LogSourceAccessManager::LogSourceAccessManager(content::BrowserContext* context)
      : context_(context),
        tick_clock_(base::DefaultTickClock::GetInstance()),
        task_runner_for_redactor_(base::ThreadPool::CreateSequencedTaskRunner(
            // User visible as the feedback_api is used by the Chrome (OS)
            // feedback extension while the user may be looking at a spinner.
            {base::TaskPriority::USER_VISIBLE,
             base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
        redactor_container_(
            base::MakeRefCounted<feedback::RedactionToolContainer>(
                task_runner_for_redactor_,
                /* first_party_extension_ids= */ nullptr)) {}
  
  LogSourceAccessManager::~LogSourceAccessManager() {}
  
  // static
  void LogSourceAccessManager::SetMaxNumBurstAccessesForTesting(
      int num_accesses) {
    g_max_num_burst_accesses = num_accesses;
  }
  
  // static
  void LogSourceAccessManager::SetRateLimitingTimeoutForTesting(
      const base::TimeDelta* timeout) {
    g_rate_limiting_timeout = timeout;
  }
  
  bool LogSourceAccessManager::FetchFromSource(const ReadLogSourceParams& params,
                                               const std::string& extension_id,
                                               ReadLogSourceCallback callback) {
    int requested_resource_id =
        params.reader_id ? *params.reader_id : kInvalidResourceId;
    ApiResourceManager<LogSourceResource>* resource_manager =
        ApiResourceManager<LogSourceResource>::Get(context_);
  
    const ResourceId resource_id =
        requested_resource_id != kInvalidResourceId
            ? requested_resource_id
            : CreateResource(params.source, extension_id);
    if (resource_id == kInvalidResourceId)
      return false;
  
    LogSourceResource* resource =
        resource_manager->Get(extension_id, resource_id);
    if (!resource)
      return false;
  
    // Enforce the rules: rate-limit access to the source from the current reader
    // handle. If not enough time has elapsed since the last access, do not read
    // from the source, but instead return an empty response. From the caller's
    // perspective, there is no new data. There is no need for the caller to keep
    // track of the time since last access.
    if (!UpdateSourceAccessTime(resource_id)) {
      std::move(callback).Run(
          std::make_unique<api::feedback_private::ReadLogSourceResult>());
      return true;
    }
  
    // If the API call requested a non-incremental access, clean up the
    // SingleLogSource by removing its API resource. Even if the existing source
    // were originally created as incremental, passing in incremental=false on a
    // later access indicates that the source should be closed afterwards.
    const bool delete_resource_when_done = !params.incremental;
  
    resource->GetLogSource()->Fetch(
        base::BindOnce(&LogSourceAccessManager::OnFetchComplete,
                       weak_factory_.GetWeakPtr(), extension_id, resource_id,
                       delete_resource_when_done, std::move(callback)));
    return true;
  }
  
  void LogSourceAccessManager::OnFetchComplete(
      const std::string& extension_id,
      ResourceId resource_id,
      bool delete_resource,
      ReadLogSourceCallback callback,
      std::unique_ptr<SystemLogsResponse> response) {
    auto result = std::make_unique<ReadLogSourceResult>();
  
    // Always return invalid resource ID if there is a cleanup.
    result->reader_id = delete_resource ? kInvalidResourceId : resource_id;
  
    GetLogLinesFromSystemLogsResponse(*response, &result->log_lines);
  
    // Retrieve result pointer before the PostTaskAndReply to fix issues with
    // an undefined execution order of arguments in a function call
    // (std::move(result) being executed before result.get()).
    ReadLogSourceResult* result_ptr = result.get();
    task_runner_for_redactor_->PostTaskAndReply(
        FROM_HERE,
        base::BindOnce(RedactResults, redactor_container_,
                       base::Unretained(result_ptr)),
        base::BindOnce(std::move(callback), std::move(result)));
  
    if (delete_resource) {
      // This should also remove the entry from |sources_|.
      ApiResourceManager<LogSourceResource>::Get(context_)->Remove(extension_id,
                                                                   resource_id);
    }
  }
  
  void LogSourceAccessManager::RemoveHandle(ResourceId id) {
    auto iter = open_handles_.find(id);
    if (iter == open_handles_.end())
      return;
    const SourceAndExtension& source_and_extension = *iter->second;
    const LogSource source = source_and_extension.source;
    if (num_readers_per_source_.find(source) != num_readers_per_source_.end())
      num_readers_per_source_[source]--;
  
    open_handles_.erase(id);
  }
  
  LogSourceAccessManager::SourceAndExtension::SourceAndExtension(
      LogSource source,
      const std::string& extension_id)
      : source(source), extension_id(extension_id) {}
  
  LogSourceAccessManager::ResourceId LogSourceAccessManager::CreateResource(
      LogSource source,
      const std::string& extension_id) {
    // Enforce the rules: Do not create too many SingleLogSource objects to read
    // from a source, even if they are from different extensions.
    if (GetNumActiveResourcesForSource(source) >= kMaxReadersPerSource)
      return kInvalidResourceId;
  
    auto new_resource = std::make_unique<LogSourceResource>(
        extension_id, ExtensionsAPIClient::Get()
                          ->GetFeedbackPrivateDelegate()
                          ->CreateSingleLogSource(source));
  
    auto* resource_manager = ApiResourceManager<LogSourceResource>::Get(context_);
    // Create an ID for the resource using the API Resource Manager, but don't
    // release ownership of it until a valid ID has been secured.
    ResourceId resource_id = resource_manager->Add(new_resource.get());
    if (resource_id == kInvalidResourceId)
      return kInvalidResourceId;
  
    if (open_handles_.find(resource_id) != open_handles_.end())
      return kInvalidResourceId;
  
    // Now that |resource_id| has been determined to be valid, release ownership
    // of the LogSourceResource, which is now owned by the API resource manager.
    new_resource.release();
  
    // The resource ID isn't known until |new_resource| is added to the API
    // Resource Manager, but it needs to be passed into the resource afterward, so
    // that the resource can unregister itself from LogSourceAccessManager. It is
    // passed in as part of a callback.
    resource_manager->Get(extension_id, resource_id)
        ->set_unregister_callback(
            base::BindOnce(&LogSourceAccessManager::RemoveHandle,
                           weak_factory_.GetWeakPtr(), resource_id));
  
    open_handles_.emplace(
        resource_id, std::make_unique<SourceAndExtension>(source, extension_id));
    num_readers_per_source_[source]++;
  
    return resource_id;
  }
  
  bool LogSourceAccessManager::UpdateSourceAccessTime(ResourceId id) {
    auto iter = open_handles_.find(id);
    if (iter == open_handles_.end())
      return false;
  
    const SourceAndExtension& key = *iter->second;
    if (rate_limiters_.find(key) == rate_limiters_.end()) {
      rate_limiters_.emplace(key, std::make_unique<AccessRateLimiter>(
                                      g_max_num_burst_accesses,
                                      GetMinTimeBetweenReads(), tick_clock_));
    }
    return rate_limiters_[key]->AttemptAccess();
  }
  
  size_t LogSourceAccessManager::GetNumActiveResourcesForSource(
      LogSource source) const {
    auto iter = num_readers_per_source_.find(source);
    if (iter == num_readers_per_source_.end())
      return 0;
    return iter->second;
  }
  
  }  // namespace extensions
  