blob: e727d4fbeb76be9c64ca3f0ff15ec682f7ec2f79 [file] [log] [blame]
// Copyright (c) 2013 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 "components/net_log/net_export_file_writer.h"
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/net_log/chrome_net_log.h"
namespace net_log {
namespace {
// Path of logs relative to default temporary directory given by
// base::GetTempDir(). Must be kept in sync with
// chrome/android/java/res/xml/file_paths.xml. Only used if not saving log file
// to a custom path.
const base::FilePath::CharType kLogRelativePath[] =
FILE_PATH_LITERAL("net-export/chrome-net-export-log.json");
// Contains file-related initialization tasks for NetExportFileWriter.
NetExportFileWriter::DefaultLogPathResults SetUpDefaultLogPath(
const NetExportFileWriter::DirectoryGetter& default_log_base_dir_getter) {
NetExportFileWriter::DefaultLogPathResults results;
results.default_log_path_success = false;
results.log_exists = false;
base::FilePath default_base_dir;
if (!default_log_base_dir_getter.Run(&default_base_dir))
return results;
results.default_log_path = default_base_dir.Append(kLogRelativePath);
if (!base::CreateDirectoryAndGetError(results.default_log_path.DirName(),
nullptr))
return results;
results.log_exists = base::PathExists(results.default_log_path);
results.default_log_path_success = true;
return results;
}
// If running on a POSIX OS, this will attempt to set all the permission flags
// of the file at |path| to 1. Will return |path| on success and the empty path
// on failure.
base::FilePath GetPathWithAllPermissions(const base::FilePath& path) {
if (!base::PathExists(path))
return base::FilePath();
#if defined(OS_POSIX)
return base::SetPosixFilePermissions(path, base::FILE_PERMISSION_MASK)
? path
: base::FilePath();
#else
return path;
#endif
}
scoped_refptr<base::SequencedTaskRunner> CreateFileTaskRunner() {
// The tasks posted to this sequenced task runner do synchronous File I/O for
// checking paths and setting permissions on files.
//
// These operations can be skipped on shutdown since FileNetLogObserver's API
// doesn't require things to have completed until notified of completion.
return base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}
} // namespace
NetExportFileWriter::NetExportFileWriter()
: state_(STATE_UNINITIALIZED),
log_exists_(false),
log_capture_mode_known_(false),
log_capture_mode_(net::NetLogCaptureMode::Default()),
default_log_base_dir_getter_(base::Bind(&base::GetTempDir)),
weak_ptr_factory_(this) {}
NetExportFileWriter::~NetExportFileWriter() {
if (net_log_exporter_) {
net_log_exporter_->Stop(base::Value(base::Value::Type::DICTIONARY),
base::DoNothing());
}
}
void NetExportFileWriter::AddObserver(StateObserver* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
state_observer_list_.AddObserver(observer);
}
void NetExportFileWriter::RemoveObserver(StateObserver* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
state_observer_list_.RemoveObserver(observer);
}
void NetExportFileWriter::Initialize() {
DCHECK(thread_checker_.CalledOnValidThread());
file_task_runner_ = CreateFileTaskRunner();
if (state_ != STATE_UNINITIALIZED)
return;
state_ = STATE_INITIALIZING;
NotifyStateObserversAsync();
base::PostTaskAndReplyWithResult(
file_task_runner_.get(), FROM_HERE,
base::Bind(&SetUpDefaultLogPath, default_log_base_dir_getter_),
base::Bind(&NetExportFileWriter::SetStateAfterSetUpDefaultLogPath,
weak_ptr_factory_.GetWeakPtr()));
}
void NetExportFileWriter::StartNetLog(
const base::FilePath& log_path,
net::NetLogCaptureMode capture_mode,
uint64_t max_file_size,
const base::CommandLine::StringType& command_line_string,
const std::string& channel_string,
network::mojom::NetworkContext* network_context) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(file_task_runner_);
if (state_ != STATE_NOT_LOGGING)
return;
if (!log_path.empty())
log_path_ = log_path;
DCHECK(!log_path_.empty());
state_ = STATE_STARTING_LOG;
NotifyStateObserversAsync();
network_context->CreateNetLogExporter(mojo::MakeRequest(&net_log_exporter_));
base::Value custom_constants = base::Value::FromUniquePtrValue(
ChromeNetLog::GetPlatformConstants(command_line_string, channel_string));
net_log_exporter_.set_connection_error_handler(base::BindOnce(
&NetExportFileWriter::OnConnectionError, base::Unretained(this)));
base::PostTaskAndReplyWithResult(
file_task_runner_.get(), FROM_HERE,
base::BindOnce(&NetExportFileWriter::CreateOutputFile, log_path_),
base::BindOnce(&NetExportFileWriter::StartNetLogAfterCreateFile,
weak_ptr_factory_.GetWeakPtr(), capture_mode,
max_file_size, std::move(custom_constants)));
}
void NetExportFileWriter::StartNetLogAfterCreateFile(
net::NetLogCaptureMode capture_mode,
uint64_t max_file_size,
base::Value custom_constants,
base::File output_file) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(STATE_STARTING_LOG, state_);
// TODO(morlovich): Communicate file open trouble better
// (https://crbug.com/838977)
if (!output_file.IsValid()) {
ResetExporterThenSetStateNotLogging();
return;
}
// It's possible that the network service crashed in the window between
// StartNetLog and here. In that case, OnConnectionError will have closed
// |net_log_exporter_|.
if (!net_log_exporter_)
return;
network::mojom::NetLogCaptureMode rpc_capture_mode =
network::mojom::NetLogCaptureMode::DEFAULT;
if (capture_mode.include_socket_bytes()) {
rpc_capture_mode = network::mojom::NetLogCaptureMode::INCLUDE_SOCKET_BYTES;
} else if (capture_mode.include_cookies_and_credentials()) {
rpc_capture_mode =
network::mojom::NetLogCaptureMode::INCLUDE_COOKIES_AND_CREDENTIALS;
}
// base::Unretained(this) is safe here since |net_log_exporter_| is owned by
// |this| and is a mojo InterfacePtr, which guarantees callback cancellation
// upon its destruction.
net_log_exporter_->Start(
std::move(output_file), std::move(custom_constants), rpc_capture_mode,
max_file_size,
base::BindOnce(&NetExportFileWriter::OnStartResult,
base::Unretained(this), capture_mode));
}
void NetExportFileWriter::OnStartResult(net::NetLogCaptureMode capture_mode,
int result) {
if (result == net::OK) {
state_ = STATE_LOGGING;
log_exists_ = true;
log_capture_mode_known_ = true;
log_capture_mode_ = capture_mode;
NotifyStateObservers();
} else {
ResetExporterThenSetStateNotLogging();
}
}
void NetExportFileWriter::StopNetLog(
std::unique_ptr<base::DictionaryValue> polled_data) {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != STATE_LOGGING)
return;
state_ = STATE_STOPPING_LOG;
NotifyStateObserversAsync();
base::Value polled_data_value(base::Value::Type::DICTIONARY);
if (polled_data)
polled_data_value = base::Value::FromUniquePtrValue(std::move(polled_data));
// base::Unretained(this) is safe here since |net_log_exporter_| is owned by
// |this| and is a mojo InterfacePtr, which guarantees callback cancellation
// upon its destruction.
net_log_exporter_->Stop(std::move(polled_data_value),
base::BindOnce(&NetExportFileWriter::OnStopResult,
base::Unretained(this)));
}
void NetExportFileWriter::OnStopResult(int result) {
ResetExporterThenSetStateNotLogging();
}
void NetExportFileWriter::OnConnectionError() {
ResetExporterThenSetStateNotLogging();
}
std::unique_ptr<base::DictionaryValue> NetExportFileWriter::GetState() const {
DCHECK(thread_checker_.CalledOnValidThread());
auto dict = std::make_unique<base::DictionaryValue>();
dict->SetString("file", log_path_.LossyDisplayName());
base::StringPiece state_string;
switch (state_) {
case STATE_UNINITIALIZED:
state_string = "UNINITIALIZED";
break;
case STATE_INITIALIZING:
state_string = "INITIALIZING";
break;
case STATE_NOT_LOGGING:
state_string = "NOT_LOGGING";
break;
case STATE_STARTING_LOG:
state_string = "STARTING_LOG";
break;
case STATE_LOGGING:
state_string = "LOGGING";
break;
case STATE_STOPPING_LOG:
state_string = "STOPPING_LOG";
break;
}
dict->SetString("state", state_string);
dict->SetBoolean("logExists", log_exists_);
dict->SetBoolean("logCaptureModeKnown", log_capture_mode_known_);
dict->SetString("captureMode", CaptureModeToString(log_capture_mode_));
return dict;
}
void NetExportFileWriter::GetFilePathToCompletedLog(
const FilePathCallback& path_callback) const {
DCHECK(thread_checker_.CalledOnValidThread());
if (!(log_exists_ && state_ == STATE_NOT_LOGGING)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(path_callback, base::FilePath()));
return;
}
DCHECK(file_task_runner_);
DCHECK(!log_path_.empty());
base::PostTaskAndReplyWithResult(
file_task_runner_.get(), FROM_HERE,
base::Bind(&GetPathWithAllPermissions, log_path_), path_callback);
}
std::string NetExportFileWriter::CaptureModeToString(
net::NetLogCaptureMode capture_mode) {
if (capture_mode == net::NetLogCaptureMode::Default())
return "STRIP_PRIVATE_DATA";
if (capture_mode == net::NetLogCaptureMode::IncludeCookiesAndCredentials())
return "NORMAL";
if (capture_mode == net::NetLogCaptureMode::IncludeSocketBytes())
return "LOG_BYTES";
NOTREACHED();
return "STRIP_PRIVATE_DATA";
}
net::NetLogCaptureMode NetExportFileWriter::CaptureModeFromString(
const std::string& capture_mode_string) {
if (capture_mode_string == "STRIP_PRIVATE_DATA")
return net::NetLogCaptureMode::Default();
if (capture_mode_string == "NORMAL")
return net::NetLogCaptureMode::IncludeCookiesAndCredentials();
if (capture_mode_string == "LOG_BYTES")
return net::NetLogCaptureMode::IncludeSocketBytes();
NOTREACHED();
return net::NetLogCaptureMode::Default();
}
void NetExportFileWriter::SetDefaultLogBaseDirectoryGetterForTest(
const DirectoryGetter& getter) {
default_log_base_dir_getter_ = getter;
}
void NetExportFileWriter::NotifyStateObservers() {
DCHECK(thread_checker_.CalledOnValidThread());
std::unique_ptr<base::DictionaryValue> state = GetState();
for (StateObserver& observer : state_observer_list_) {
observer.OnNewState(*state);
}
}
void NetExportFileWriter::NotifyStateObserversAsync() {
DCHECK(thread_checker_.CalledOnValidThread());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&NetExportFileWriter::NotifyStateObservers,
weak_ptr_factory_.GetWeakPtr()));
}
void NetExportFileWriter::SetStateAfterSetUpDefaultLogPath(
const DefaultLogPathResults& set_up_default_log_path_results) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(STATE_INITIALIZING, state_);
if (set_up_default_log_path_results.default_log_path_success) {
state_ = STATE_NOT_LOGGING;
log_path_ = set_up_default_log_path_results.default_log_path;
log_exists_ = set_up_default_log_path_results.log_exists;
DCHECK(!log_capture_mode_known_);
} else {
state_ = STATE_UNINITIALIZED;
}
NotifyStateObservers();
}
void NetExportFileWriter::ResetExporterThenSetStateNotLogging() {
DCHECK(thread_checker_.CalledOnValidThread());
net_log_exporter_.reset();
state_ = STATE_NOT_LOGGING;
NotifyStateObservers();
}
base::File NetExportFileWriter::CreateOutputFile(base::FilePath path) {
return base::File(path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
}
} // namespace net_log