| // Copyright 2021 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/support_tool/support_tool_handler.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/check.h" |
| #include "base/check_is_test.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/support_tool/data_collector.h" |
| #include "chrome/browser/support_tool/support_packet_metadata.h" |
| #include "components/feedback/redaction_tool/pii_types.h" |
| #include "components/feedback/redaction_tool/redaction_tool.h" |
| #include "data_collector_utils.h" |
| #include "third_party/zlib/google/zip.h" |
| |
| // Zip archieves the contents of `src_path` into `target_path`. Adds ".zip" |
| // extension to target file path. Returns the path of zip archive on success, an |
| // empty path otherwise. |
| base::FilePath ZipOutput(base::FilePath src_path, base::FilePath target_path) { |
| base::FilePath zip_path = target_path.AddExtension(FILE_PATH_LITERAL(".zip")); |
| if (!zip::Zip(src_path, zip_path, true)) { |
| LOG(ERROR) << "Couldn't zip files"; |
| return base::FilePath(); |
| } |
| return zip_path; |
| } |
| |
| // Creates a unique temp directory to store the output files. The caller is |
| // responsible for deleting the returned directory. Returns an empty FilePath in |
| // case of an error. |
| base::FilePath CreateTempDirForOutput() { |
| base::ScopedTempDir temp_dir; |
| if (!temp_dir.CreateUniqueTempDir()) { |
| LOG(ERROR) << "Unable to create temp dir."; |
| return base::FilePath{}; |
| } |
| return temp_dir.Take(); |
| } |
| |
| SupportToolHandler::SupportToolHandler() |
| : SupportToolHandler(/*case_id=*/std::string(), |
| /*email_address=*/std::string(), |
| /*issue_description=*/std::string(), |
| std::nullopt) {} |
| |
| SupportToolHandler::SupportToolHandler(std::string case_id, |
| std::string email_address, |
| std::string issue_description, |
| std::optional<std::string> upload_id) |
| : metadata_(case_id, email_address, issue_description, upload_id), |
| task_runner_for_redaction_tool_( |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})), |
| redaction_tool_container_( |
| base::MakeRefCounted<redaction::RedactionToolContainer>( |
| task_runner_for_redaction_tool_, |
| nullptr)) {} |
| |
| SupportToolHandler::~SupportToolHandler() { |
| CleanUp(); |
| } |
| |
| void SupportToolHandler::CleanUp() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Clean the temporary directory in a worker thread if it hasn't been removed |
| // yet. |
| if (!temp_dir_.empty()) { |
| base::ThreadPool::PostTask( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| base::GetDeletePathRecursivelyCallback(std::move(temp_dir_))); |
| temp_dir_.clear(); |
| } |
| } |
| |
| const std::string& SupportToolHandler::GetCaseId() { |
| return metadata_.GetCaseId(); |
| } |
| |
| const base::Time& SupportToolHandler::GetDataCollectionTimestamp() { |
| return data_collection_timestamp_; |
| } |
| |
| void SupportToolHandler::AddDataCollector( |
| std::unique_ptr<DataCollector> collector) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(collector); |
| data_collectors_.emplace_back(std::move(collector)); |
| } |
| |
| const std::vector<std::unique_ptr<DataCollector>>& |
| SupportToolHandler::GetDataCollectorsForTesting() { |
| CHECK_IS_TEST(); |
| return data_collectors_; |
| } |
| |
| void SupportToolHandler::CollectSupportData( |
| SupportToolDataCollectedCallback on_data_collection_done_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!on_data_collection_done_callback.is_null()); |
| DCHECK(!data_collectors_.empty()); |
| |
| on_data_collection_done_callback_ = |
| std::move(on_data_collection_done_callback); |
| |
| base::RepeatingClosure collect_data_barrier_closure = base::BarrierClosure( |
| data_collectors_.size(), |
| base::BindOnce(&SupportToolHandler::OnAllDataCollected, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| data_collection_timestamp_ = base::Time::NowFromSystemTime(); |
| |
| for (auto& data_collector : data_collectors_) { |
| // DataCollectors will use `redaction_tool_container_` on |
| // `task_runner_for_redaction_tool_` to redact PII from the collected logs. |
| // All DataCollectors will use the same RedactionTool instance on the same |
| // task runner as we need to replace the same PII data with the same |
| // place-holder strings (that are stored in RedactionTool instance's data |
| // member) in all collected logs to avoid confusing the reader. |
| data_collector->CollectDataAndDetectPII( |
| base::BindOnce(&SupportToolHandler::OnDataCollected, |
| weak_ptr_factory_.GetWeakPtr(), |
| collect_data_barrier_closure), |
| task_runner_for_redaction_tool_, redaction_tool_container_); |
| } |
| } |
| |
| void SupportToolHandler::OnDataCollected( |
| base::RepeatingClosure barrier_closure, |
| std::optional<SupportToolError> error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (error) { |
| collected_errors_.insert(error.value()); |
| } |
| std::move(barrier_closure).Run(); |
| } |
| |
| void SupportToolHandler::AddDetectedPII(const PIIMap& pii_map) { |
| MergePIIMaps(detected_pii_, pii_map); |
| } |
| |
| void SupportToolHandler::OnAllDataCollected() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| for (auto& data_collector : data_collectors_) { |
| AddDetectedPII(data_collector->GetDetectedPII()); |
| } |
| |
| metadata_.InsertErrors(collected_errors_); |
| |
| metadata_.PopulateMetadataContents( |
| data_collection_timestamp_, data_collectors_, |
| base::BindOnce(&SupportToolHandler::OnMetadataContentsPopulated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void SupportToolHandler::OnMetadataContentsPopulated() { |
| AddDetectedPII(metadata_.GetPII()); |
| std::move(on_data_collection_done_callback_) |
| .Run(detected_pii_, collected_errors_); |
| } |
| |
| void SupportToolHandler::ExportCollectedData( |
| std::set<redaction::PIIType> pii_types_to_keep, |
| base::FilePath target_path, |
| SupportToolDataExportedCallback on_data_exported_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Clear the set of previously collected errors. |
| collected_errors_.clear(); |
| |
| on_data_export_done_callback_ = std::move(on_data_exported_callback); |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, base::BindOnce(&CreateTempDirForOutput), |
| base::BindOnce(&SupportToolHandler::ExportIntoTempDir, |
| weak_ptr_factory_.GetWeakPtr(), pii_types_to_keep, |
| target_path)); |
| } |
| |
| void SupportToolHandler::ExportIntoTempDir( |
| std::set<redaction::PIIType> pii_types_to_keep, |
| base::FilePath target_path, |
| base::FilePath tmp_path) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (tmp_path.empty()) { |
| collected_errors_.insert( |
| {SupportToolErrorCode::kDataExportError, |
| "Failed to create temporary directory for output."}); |
| std::move(on_data_export_done_callback_) |
| .Run(base::FilePath(), collected_errors_); |
| return; |
| } |
| |
| temp_dir_ = tmp_path; |
| |
| base::RepeatingClosure export_data_barrier_closure = base::BarrierClosure( |
| data_collectors_.size(), |
| base::BindOnce(&SupportToolHandler::OnAllDataCollectorsDoneExporting, |
| weak_ptr_factory_.GetWeakPtr(), temp_dir_, target_path, |
| pii_types_to_keep)); |
| |
| for (auto& data_collector : data_collectors_) { |
| data_collector->ExportCollectedDataWithPII( |
| pii_types_to_keep, temp_dir_, task_runner_for_redaction_tool_, |
| redaction_tool_container_, |
| base::BindOnce(&SupportToolHandler::OnDataCollectorDoneExporting, |
| weak_ptr_factory_.GetWeakPtr(), |
| export_data_barrier_closure)); |
| } |
| } |
| |
| void SupportToolHandler::OnDataCollectorDoneExporting( |
| base::RepeatingClosure barrier_closure, |
| std::optional<SupportToolError> error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (error) { |
| collected_errors_.insert(error.value()); |
| } |
| std::move(barrier_closure).Run(); |
| } |
| |
| void SupportToolHandler::OnAllDataCollectorsDoneExporting( |
| base::FilePath tmp_path, |
| base::FilePath target_path, |
| std::set<redaction::PIIType> pii_types_to_keep) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| metadata_.InsertErrors(collected_errors_); |
| metadata_.WriteMetadataFile( |
| tmp_path, pii_types_to_keep, |
| base::BindOnce(&SupportToolHandler::OnMetadataFileWritten, |
| weak_ptr_factory_.GetWeakPtr(), tmp_path, target_path)); |
| } |
| |
| void SupportToolHandler::OnMetadataFileWritten(base::FilePath tmp_path, |
| base::FilePath target_path) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Archive the contents in the `tmp_path` into `target_path` in a zip file. |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&ZipOutput, tmp_path, target_path), |
| base::BindOnce(&SupportToolHandler::OnDataExportDone, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void SupportToolHandler::OnDataExportDone(base::FilePath exported_path) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Clean-up the temporary directory after exporting the data. |
| CleanUp(); |
| if (exported_path.empty()) { |
| collected_errors_.insert({SupportToolErrorCode::kDataExportError, |
| "Failed to archive the output files."}); |
| } |
| std::move(on_data_export_done_callback_) |
| .Run(exported_path, collected_errors_); |
| } |