#include "extensions/browser/api/feedback_private/log_source_access_manager.h"
#include <algorithm>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.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"
#include "extensions/common/extension_id.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;
constexpr int kDefaultMaxNumBurstAccesses = 10;
constexpr base::TimeDelta kDefaultRateLimitingTimeout =
base::Milliseconds(1000);
constexpr int kMaxLogLineLength = 8100;
constexpr int kMaxLogFetchLength = 2000000;
int g_max_num_burst_accesses = kDefaultMaxNumBurstAccesses;
const base::TimeDelta* g_rate_limiting_timeout = nullptr;
base::TimeDelta GetMinTimeBetweenReads() {
return g_rate_limiting_timeout ? *g_rate_limiting_timeout
: kDefaultRateLimitingTimeout;
}
void GetLogLinesFromSystemLogsResponse(const SystemLogsResponse& response,
std::vector<std::string>* log_lines) {
const int kTimestampPrefixLength = 27;
for (const std::pair<const std::string, std::string>& pair : response) {
if (pair.second.size() > kMaxLogFetchLength) {
const std::string error_message =
pair.second.substr(0, kTimestampPrefixLength) +
base::StringPrintf(
"WARNING extensions::LogSourceAccessManager::OnFetchComplete: "
"Dropped lines from log source %s totaling %d chars.",
pair.first.c_str(), pair.second.size());
log_lines->emplace_back(error_message);
continue;
}
std::vector<std::string_view> new_lines = base::SplitStringPiece(
pair.second, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
log_lines->reserve(log_lines->size() + new_lines.size());
for (std::string_view& line : new_lines) {
if (line.size() < kMaxLogLineLength) {
log_lines->emplace_back(line);
} else {
log_lines->emplace_back(std::string(line.substr(0, kMaxLogLineLength)) +
" (Truncated)");
}
}
}
}
void RedactResults(
scoped_refptr<redaction::RedactionToolContainer> redactor_container,
ReadLogSourceResult* result) {
redaction::RedactionTool* redactor = redactor_container->Get();
for (std::string& line : result->log_lines)
line = redactor->Redact(line);
}
}
LogSourceAccessManager::LogSourceAccessManager(content::BrowserContext* context)
: context_(context),
tick_clock_(base::DefaultTickClock::GetInstance()),
task_runner_for_redactor_(base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
redactor_container_(
base::MakeRefCounted<redaction::RedactionToolContainer>(
task_runner_for_redactor_,
nullptr)) {}
LogSourceAccessManager::~LogSourceAccessManager() = default;
void LogSourceAccessManager::SetMaxNumBurstAccessesForTesting(
int num_accesses) {
g_max_num_burst_accesses = num_accesses;
}
void LogSourceAccessManager::SetRateLimitingTimeoutForTesting(
const base::TimeDelta* timeout) {
g_rate_limiting_timeout = timeout;
}
bool LogSourceAccessManager::FetchFromSource(const ReadLogSourceParams& params,
const ExtensionId& 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;
if (!UpdateSourceAccessTime(resource_id)) {
std::move(callback).Run(
std::make_unique<api::feedback_private::ReadLogSourceResult>());
return true;
}
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 ExtensionId& extension_id,
ResourceId resource_id,
bool delete_resource,
ReadLogSourceCallback callback,
std::unique_ptr<SystemLogsResponse> response) {
auto result = std::make_unique<ReadLogSourceResult>();
result->reader_id = delete_resource ? kInvalidResourceId : resource_id;
GetLogLinesFromSystemLogsResponse(*response, &result->log_lines);
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) {
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 ExtensionId& extension_id)
: source(source), extension_id(extension_id) {}
LogSourceAccessManager::ResourceId LogSourceAccessManager::CreateResource(
LogSource source,
const ExtensionId& extension_id) {
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_);
ResourceId resource_id = resource_manager->Add(new_resource.get());
if (resource_id == kInvalidResourceId)
return kInvalidResourceId;
if (base::Contains(open_handles_, resource_id)) {
return kInvalidResourceId;
}
new_resource.release();
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;
}
}