blob: 3474f8f0da6ee690529a2c437c4bcc9027c17436 [file] [log] [blame] [edit]
// Copyright 2012 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/installer/util/lzma_util.h"
#include <windows.h>
#include <ntstatus.h>
#include <stddef.h>
#include <memory>
#include <set>
#include <utility>
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/types/expected_macros.h"
#include "chrome/installer/util/unbuffered_file_writer.h"
#include "third_party/lzma_sdk/google/seven_zip_reader.h"
namespace {
class SevenZipDelegateImpl : public seven_zip::Delegate {
public:
SevenZipDelegateImpl(const base::FilePath& location,
base::FilePath* output_file);
SevenZipDelegateImpl(const SevenZipDelegateImpl&) = delete;
SevenZipDelegateImpl& operator=(const SevenZipDelegateImpl&) = delete;
std::optional<DWORD> error_code() const { return error_code_; }
UnPackStatus unpack_error() const { return unpack_error_; }
// seven_zip::Delegate implementation:
void OnOpenError(seven_zip::Result result) override;
base::File OnTempFileRequest() override;
bool OnEntry(const seven_zip::EntryInfo& entry,
base::span<uint8_t>& temp_file) override;
bool OnDirectory(const seven_zip::EntryInfo& entry) override;
bool EntryDone(seven_zip::Result result,
const seven_zip::EntryInfo& entry) override;
private:
bool CreateDirectory(const base::FilePath& dir);
const base::FilePath location_;
const raw_ptr<base::FilePath> output_file_;
std::set<base::FilePath> directories_created_;
std::optional<DWORD> error_code_;
// The file to which the current entry will be written.
std::optional<installer::UnbufferedFileWriter> current_file_;
UnPackStatus unpack_error_ = UNPACK_NO_ERROR;
};
SevenZipDelegateImpl::SevenZipDelegateImpl(const base::FilePath& location,
base::FilePath* output_file)
: location_(location), output_file_(output_file) {}
bool SevenZipDelegateImpl::CreateDirectory(const base::FilePath& dir) {
bool result = true;
if (directories_created_.find(dir) == directories_created_.end()) {
result = base::CreateDirectory(dir);
if (result)
directories_created_.insert(dir);
}
return result;
}
void SevenZipDelegateImpl::OnOpenError(seven_zip::Result result) {
switch (result) {
case seven_zip::Result::kMalformedArchive:
case seven_zip::Result::kBadCrc:
case seven_zip::Result::kUnsupported: {
auto error_code = ::GetLastError();
if (error_code != ERROR_SUCCESS)
error_code_ = error_code;
unpack_error_ = UNPACK_SZAREX_OPEN_ERROR;
break;
}
case seven_zip::Result::kFailedToAllocate:
unpack_error_ = UNPACK_ALLOCATE_ERROR;
break;
case seven_zip::Result::kIoError:
unpack_error_ = UNPACK_IO_DEVICE_ERROR;
break;
case seven_zip::Result::kUnknownError:
case seven_zip::Result::kDiskFull:
case seven_zip::Result::kSuccess:
case seven_zip::Result::kMemoryMappingFailed:
case seven_zip::Result::kNoFilename:
case seven_zip::Result::kEncryptedHeaders:
NOTREACHED();
}
}
base::File SevenZipDelegateImpl::OnTempFileRequest() {
base::FilePath temp_path;
base::File temp_file;
if (base::CreateTemporaryFileInDir(location_, &temp_path)) {
temp_file.Initialize(
temp_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
base::File::FLAG_WRITE | base::File::FLAG_WIN_EXCLUSIVE_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE |
base::File::FLAG_WIN_TEMPORARY | base::File::FLAG_DELETE_ON_CLOSE |
base::File::FLAG_WIN_SHARE_DELETE);
}
if (!temp_file.IsValid()) {
error_code_ = ::GetLastError();
unpack_error_ = UNPACK_CREATE_FILE_ERROR;
}
return temp_file;
}
bool SevenZipDelegateImpl::OnDirectory(const seven_zip::EntryInfo& entry) {
if (!CreateDirectory(location_.Append(entry.file_path))) {
error_code_ = ::GetLastError();
unpack_error_ = UNPACK_CREATE_FILE_ERROR;
return false;
}
return true;
}
bool SevenZipDelegateImpl::OnEntry(const seven_zip::EntryInfo& entry,
base::span<uint8_t>& output) {
if (entry.file_path.ReferencesParent()) {
PLOG(ERROR) << "Path contains a parent directory traversal which is not "
"allowed because it could become a security issue: "
<< entry.file_path;
unpack_error_ = UNPACK_CREATE_FILE_ERROR;
return false;
}
base::FilePath file_path = location_.Append(entry.file_path);
if (output_file_)
*output_file_ = file_path;
CreateDirectory(file_path.DirName());
ASSIGN_OR_RETURN(
current_file_,
installer::UnbufferedFileWriter::Create(file_path, entry.file_size),
[this](DWORD error) {
error_code_ = error;
PLOG(ERROR) << "Invalid file";
unpack_error_ = UNPACK_CREATE_FILE_ERROR;
return false;
});
// Return a view into the writer's output buffer.
output = current_file_->write_buffer().first(entry.file_size);
// Clear the last error code before the entry is extracted to reduce the
// likelihood that it will hold an unrelated error code in case extraction
// fails.
::SetLastError(ERROR_SUCCESS);
return true;
}
bool SevenZipDelegateImpl::EntryDone(seven_zip::Result result,
const seven_zip::EntryInfo& entry) {
// Take ownership of `current_file_` so that it is always closed when this
// function exits
auto current_file = *std::move(current_file_);
if (result != seven_zip::Result::kSuccess) {
auto error_code = ::GetLastError();
if (error_code != ERROR_SUCCESS)
error_code_ = error_code;
switch (result) {
case seven_zip::Result::kSuccess:
NOTREACHED();
case seven_zip::Result::kFailedToAllocate:
unpack_error_ = UNPACK_ALLOCATE_ERROR;
break;
case seven_zip::Result::kIoError:
unpack_error_ = UNPACK_IO_DEVICE_ERROR;
break;
case seven_zip::Result::kDiskFull:
unpack_error_ = UNPACK_DISK_FULL;
break;
case seven_zip::Result::kNoFilename:
LOG(ERROR) << "Couldn't get file name";
unpack_error_ = UNPACK_NO_FILENAME_ERROR;
break;
case seven_zip::Result::kUnknownError:
case seven_zip::Result::kBadCrc:
case seven_zip::Result::kMemoryMappingFailed:
case seven_zip::Result::kMalformedArchive:
case seven_zip::Result::kUnsupported:
case seven_zip::Result::kEncryptedHeaders:
unpack_error_ = UNPACK_EXTRACT_ERROR;
break;
}
return false;
}
// The writer's buffer now holds the entire entry.
current_file.Advance(entry.file_size);
// Commit the file, which sizes it appropriately and sets the last-modified
// time.
// TODO(crbug.com/394631579): Monitor UnbufferedFileWriter error metrics to
// see if/what errors are happening in the field. Consider using a retry loop
// here based on the data.
RETURN_IF_ERROR(current_file.Commit(entry.last_modified_time.is_null()
? std::nullopt
: std::optional<base::Time>(
entry.last_modified_time)),
[this](DWORD error) {
error_code_ = error;
unpack_error_ = UNPACK_EXTRACT_ERROR;
return false;
});
return true;
}
} // namespace
UnPackStatus UnPackArchive(const base::FilePath& archive,
const base::FilePath& output_dir,
base::FilePath* output_file) {
VLOG(1) << "Opening archive " << archive.value();
LzmaUtilImpl lzma_util;
UnPackStatus status;
if ((status = lzma_util.OpenArchive(archive)) != UNPACK_NO_ERROR) {
PLOG(ERROR) << "Unable to open install archive: " << archive.value();
} else {
VLOG(1) << "Uncompressing archive to path " << output_dir.value();
if ((status = lzma_util.UnPack(output_dir, output_file)) != UNPACK_NO_ERROR)
PLOG(ERROR) << "Unable to uncompress archive: " << archive.value();
}
if (status != UNPACK_NO_ERROR) {
std::optional<DWORD> error_code = lzma_util.GetErrorCode();
if (error_code.value_or(ERROR_SUCCESS) == ERROR_DISK_FULL)
return UNPACK_DISK_FULL;
if (error_code.value_or(ERROR_SUCCESS) == ERROR_IO_DEVICE)
return UNPACK_IO_DEVICE_ERROR;
}
return status;
}
LzmaUtilImpl::LzmaUtilImpl() = default;
LzmaUtilImpl::~LzmaUtilImpl() = default;
UnPackStatus LzmaUtilImpl::OpenArchive(const base::FilePath& archivePath) {
// Make sure file is not already open.
CloseArchive();
archive_file_.Initialize(archivePath,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE |
base::File::FLAG_WIN_SHARE_DELETE);
if (archive_file_.IsValid())
return UNPACK_NO_ERROR;
error_code_ = ::GetLastError();
return archive_file_.error_details() == base::File::FILE_ERROR_NOT_FOUND
? UNPACK_ARCHIVE_NOT_FOUND
: UNPACK_ARCHIVE_CANNOT_OPEN;
}
UnPackStatus LzmaUtilImpl::UnPack(const base::FilePath& location) {
return UnPack(location, nullptr);
}
UnPackStatus LzmaUtilImpl::UnPack(const base::FilePath& location,
base::FilePath* output_file) {
DCHECK(archive_file_.IsValid());
SevenZipDelegateImpl delegate(location, output_file);
std::unique_ptr<seven_zip::SevenZipReader> reader =
seven_zip::SevenZipReader::Create(archive_file_.Duplicate(), delegate);
if (reader) {
reader->Extract();
}
error_code_ = delegate.error_code();
return delegate.unpack_error();
}
void LzmaUtilImpl::CloseArchive() {
archive_file_.Close();
error_code_ = std::nullopt;
}