#include "ui/base/resource/data_pack.h"
#include <errno.h>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "base/sys_byteorder.h"
#include "build/build_config.h"
#include "net/filter/gzip_header.h"
#include "third_party/zlib/google/compression_utils.h"
#include "ui/base/resource/scoped_file_writer.h"
#ifdef OHOS_HAP_DECOMPRESSED
#include <unordered_map>
#include "base/command_line.h"
#include "content/public/common/content_switches.h"
#include "ohos_adapter_helper.h"
#endif
namespace {
static const uint32_t kFileFormatV4 = 4;
static const uint32_t kFileFormatV5 = 5;
static const size_t kHeaderLengthV4 = 2 * sizeof(uint32_t) + sizeof(uint8_t);
static const size_t kHeaderLengthV5 =
sizeof(uint32_t) + sizeof(uint8_t) * 4 + sizeof(uint16_t) * 2;
enum LoadErrors {
INIT_FAILED_OBSOLETE = 1,
BAD_VERSION,
INDEX_TRUNCATED,
ENTRY_NOT_FOUND,
HEADER_TRUNCATED,
WRONG_ENCODING,
INIT_FAILED_FROM_FILE,
UNZIP_FAILED,
OPEN_FAILED,
MAP_FAILED,
LOAD_ERRORS_COUNT,
};
void LogDataPackError(LoadErrors error) {
UMA_HISTOGRAM_ENUMERATION("DataPack.Load", error, LOAD_ERRORS_COUNT);
}
void MaybePrintResourceId(uint16_t resource_id) {
if (!base::CommandLine::InitializedForCurrentProcess())
return;
static bool print_resource_ids =
base::CommandLine::ForCurrentProcess()->HasSwitch("print-resource-ids");
if (!print_resource_ids)
return;
static std::set<uint16_t>* resource_ids_logged = new std::set<uint16_t>();
static base::Lock* lock = new base::Lock;
base::AutoLock auto_lock(*lock);
if (!base::Contains(*resource_ids_logged, resource_id)) {
printf("Resource=%d\n", resource_id);
resource_ids_logged->insert(resource_id);
}
}
bool MmapHasGzipHeader(const base::MemoryMappedFile* mmap) {
net::GZipHeader header;
const char* header_end = nullptr;
net::GZipHeader::Status header_status = header.ReadMore(
reinterpret_cast<const char*>(mmap->data()), mmap->length(), &header_end);
return header_status == net::GZipHeader::COMPLETE_HEADER;
}
#ifdef OHOS_HAP_DECOMPRESSED || defined(OHOS_MEM)
std::unordered_map<ui::ResourceScaleFactor, std::string> kPakFileNameHapMap = {
{ui::ResourceScaleFactor::kScaleFactorNone,
"resources/rawfile/resources.pak"},
{ui::ResourceScaleFactor::k100Percent,
"resources/rawfile/chrome_100_percent.pak"},
{ui::ResourceScaleFactor::k200Percent,
"resources/rawfile/chrome_200_percent.pak"}};
bool GetPathFromHap(ui::ResourceScaleFactor factor,
const base::FilePath& path,
std::string& pathHap) {
auto iter = kPakFileNameHapMap.find(factor);
if (iter == kPakFileNameHapMap.end()) {
LOG(ERROR) << "kPakFileNameHapMap not find path: " << path;
return false;
}
std::string pathStr = path.MaybeAsASCII();
if (pathStr.find("zh-CN.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/zh-CN.pak";
} else if (pathStr.find("en-US.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/en-US.pak";
} else if (pathStr.find("bo-CN.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/bo-CN.pak";
} else if (pathStr.find("ug.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/ug.pak";
} else if (pathStr.find("zh-TW.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/zh-TW.pak";
} else if (pathStr.find("zh-HK.pak") != std::string::npos) {
pathHap = "resources/rawfile/locales/zh-HK.pak";
} else {
pathHap = iter->second;
}
return true;
}
#endif
}
namespace ui {
int DataPack::Entry::CompareById(const void* void_key, const void* void_entry) {
uint16_t key = *reinterpret_cast<const uint16_t*>(void_key);
const Entry* entry = reinterpret_cast<const Entry*>(void_entry);
return key - entry->resource_id;
}
int DataPack::Alias::CompareById(const void* void_key, const void* void_entry) {
uint16_t key = *reinterpret_cast<const uint16_t*>(void_key);
const Alias* entry = reinterpret_cast<const Alias*>(void_entry);
return key - entry->resource_id;
}
void DataPack::Iterator::UpdateResourceData() {
const Entry* next_entry = entry_ + 1;
base::StringPiece data;
GetStringPieceFromOffset(entry_->file_offset, next_entry->file_offset,
data_source_, &data);
resource_data_ = new ResourceData(entry_->resource_id, data);
}
DataPack::Iterator DataPack::begin() const {
return Iterator(data_source_->GetData(), &resource_table_[0]);
}
DataPack::Iterator DataPack::end() const {
return Iterator(data_source_->GetData(), &resource_table_[resource_count_]);
}
class DataPack::MemoryMappedDataSource : public DataPack::DataSource {
public:
explicit MemoryMappedDataSource(std::unique_ptr<base::MemoryMappedFile> mmap)
: mmap_(std::move(mmap)) {}
MemoryMappedDataSource(const MemoryMappedDataSource&) = delete;
MemoryMappedDataSource& operator=(const MemoryMappedDataSource&) = delete;
~MemoryMappedDataSource() override {}
size_t GetLength() const override { return mmap_->length(); }
const uint8_t* GetData() const override { return mmap_->data(); }
private:
std::unique_ptr<base::MemoryMappedFile> mmap_;
};
class DataPack::StringDataSource : public DataPack::DataSource {
public:
explicit StringDataSource(std::string&& data) : data_(std::move(data)) {}
StringDataSource(const StringDataSource&) = delete;
StringDataSource& operator=(const StringDataSource&) = delete;
~StringDataSource() override {}
size_t GetLength() const override { return data_.size(); }
const uint8_t* GetData() const override {
return reinterpret_cast<const uint8_t*>(data_.c_str());
}
private:
const std::string data_;
};
class DataPack::BufferDataSource : public DataPack::DataSource {
public:
explicit BufferDataSource(base::span<const uint8_t> buffer)
: buffer_(buffer) {}
BufferDataSource(const BufferDataSource&) = delete;
BufferDataSource& operator=(const BufferDataSource&) = delete;
~BufferDataSource() override {}
size_t GetLength() const override { return buffer_.size(); }
const uint8_t* GetData() const override { return buffer_.data(); }
private:
base::span<const uint8_t> buffer_;
};
DataPack::DataPack(ResourceScaleFactor resource_scale_factor)
: resource_table_(nullptr),
resource_count_(0),
alias_table_(nullptr),
alias_count_(0),
text_encoding_type_(BINARY),
resource_scale_factor_(resource_scale_factor) {
static_assert(sizeof(Entry) == 6, "size of Entry must be 6");
static_assert(sizeof(Alias) == 4, "size of Alias must be 4");
}
DataPack::~DataPack() {
}
std::unique_ptr<DataPack::DataSource> DataPack::LoadFromPathInternal(
const base::FilePath& path) {
std::unique_ptr<base::MemoryMappedFile> mmap =
std::make_unique<base::MemoryMappedFile>();
base::File data_file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE |
base::File::FLAG_WIN_SHARE_DELETE);
if (!data_file.IsValid()) {
DLOG(ERROR) << "Failed to open datapack with base::File::Error "
<< data_file.error_details();
LogDataPackError(OPEN_FAILED);
return nullptr;
}
if (!mmap->Initialize(std::move(data_file))) {
DLOG(ERROR) << "Failed to mmap datapack";
LogDataPackError(MAP_FAILED);
return nullptr;
}
if (MmapHasGzipHeader(mmap.get())) {
base::StringPiece compressed(reinterpret_cast<char*>(mmap->data()),
mmap->length());
std::string data;
if (!compression::GzipUncompress(compressed, &data)) {
LOG(ERROR) << "Failed to unzip compressed datapack: " << path;
LogDataPackError(UNZIP_FAILED);
return nullptr;
}
return std::make_unique<StringDataSource>(std::move(data));
}
return std::make_unique<MemoryMappedDataSource>(std::move(mmap));
}
bool DataPack::LoadFromPath(const base::FilePath& path) {
#ifdef OHOS_HAP_DECOMPRESSED
std::string pathHap;
if (path.empty() || !base::PathExists(path)) {
if (GetPathFromHap(resource_scale_factor_, path, pathHap)) {
auto resourceInstance =
OHOS::NWeb::OhosAdapterHelper::GetInstance().GetResourceAdapter();
std::shared_ptr<OHOS::NWeb::OhosFileMapper> fileMapper =
resourceInstance->GetRawFileMapper(pathHap, true);
if (!fileMapper) {
LOG(ERROR) << "DataPack::LoadFromPath couldn't data file: "
<< pathHap.c_str();
return false;
}
LOG(INFO) << "DataPack::LoadFromPath " << pathHap.c_str()
<< ", data file length: " << fileMapper->GetDataLen();
std::unique_ptr<base::MemoryMappedFile> mmap =
std::make_unique<base::MemoryMappedFile>();
mmap->SetOhosFileMapper(fileMapper);
if (MmapHasGzipHeader(mmap.get())) {
base::StringPiece compressed(reinterpret_cast<char*>(mmap->data()),
mmap->length());
std::string data;
if (!compression::GzipUncompress(compressed, &data)) {
LOG(ERROR) << "Failed to unzip compressed datapack: "
<< pathHap.c_str();
LogDataPackError(UNZIP_FAILED);
return false;
}
return LoadImpl(std::make_unique<StringDataSource>(std::move(data)));
}
return LoadImpl(
std::make_unique<MemoryMappedDataSource>(std::move(mmap)));
} else {
LOG(ERROR) << "LoadFromPath failed file not exist";
return false;
}
}
#endif
std::unique_ptr<DataSource> data_source = LoadFromPathInternal(path);
if (!data_source)
return false;
return LoadImpl(std::move(data_source));
}
bool DataPack::LoadFromFile(base::File file) {
return LoadFromFileRegion(std::move(file),
base::MemoryMappedFile::Region::kWholeFile);
}
bool DataPack::LoadFromFileRegion(
base::File file,
const base::MemoryMappedFile::Region& region) {
std::unique_ptr<base::MemoryMappedFile> mmap =
std::make_unique<base::MemoryMappedFile>();
if (!mmap->Initialize(std::move(file), region)) {
DLOG(ERROR) << "Failed to mmap datapack";
LogDataPackError(INIT_FAILED_FROM_FILE);
mmap.reset();
return false;
}
return LoadImpl(std::make_unique<MemoryMappedDataSource>(std::move(mmap)));
}
bool DataPack::LoadFromBuffer(base::span<const uint8_t> buffer) {
return LoadImpl(std::make_unique<BufferDataSource>(buffer));
}
bool DataPack::SanityCheckFileAndRegisterResources(size_t margin_to_skip,
const uint8_t* data,
size_t data_length) {
size_t resource_table_size = (resource_count_ + 1) * sizeof(Entry);
size_t alias_table_size = alias_count_ * sizeof(Alias);
if (margin_to_skip + resource_table_size + alias_table_size > data_length) {
LOG(ERROR) << "Data pack file corruption: "
<< "too short for number of entries. "
<< "data length is " << data_length
<< " bytes, expected longer than "
<< margin_to_skip + resource_table_size + alias_table_size
<< " bytes.";
LogDataPackError(INDEX_TRUNCATED);
return false;
}
resource_table_ = reinterpret_cast<const Entry*>(&data[margin_to_skip]);
alias_table_ = reinterpret_cast<const Alias*>(
&data[margin_to_skip + resource_table_size]);
for (size_t i = 0; i < resource_count_ + 1; ++i) {
if (resource_table_[i].file_offset > data_length) {
LOG(ERROR) << "Data pack file corruption: "
<< "Entry #" << i << " past end.";
LogDataPackError(ENTRY_NOT_FOUND);
return false;
}
}
for (size_t i = 0; i < resource_count_; ++i) {
if (resource_table_[i].file_offset > resource_table_[i + 1].file_offset) {
LOG(ERROR) << "Data pack file corruption: " << "Entry #" << i + 1
<< " before Entry #" << i << ".";
return false;
}
}
for (size_t i = 0; i < alias_count_; ++i) {
if (alias_table_[i].entry_index >= resource_count_) {
LOG(ERROR) << "Data pack file corruption: "
<< "Alias #" << i << " past end.";
LogDataPackError(ENTRY_NOT_FOUND);
return false;
}
}
return true;
}
bool DataPack::LoadImpl(std::unique_ptr<DataPack::DataSource> data_source) {
const uint8_t* data = data_source->GetData();
size_t data_length = data_source->GetLength();
uint32_t version = 0;
if (data_length > sizeof(version))
version = reinterpret_cast<const uint32_t*>(data)[0];
size_t header_length =
version == kFileFormatV4 ? kHeaderLengthV4 : kHeaderLengthV5;
if (version == 0 || data_length < header_length) {
DLOG(ERROR) << "Data pack file corruption: incomplete file header.";
LogDataPackError(HEADER_TRUNCATED);
return false;
}
if (version == kFileFormatV4) {
resource_count_ = reinterpret_cast<const uint32_t*>(data)[1];
alias_count_ = 0;
text_encoding_type_ = static_cast<TextEncodingType>(data[8]);
} else if (version == kFileFormatV5) {
text_encoding_type_ = static_cast<TextEncodingType>(data[4]);
resource_count_ = reinterpret_cast<const uint16_t*>(data)[4];
alias_count_ = reinterpret_cast<const uint16_t*>(data)[5];
} else {
LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
<< kFileFormatV4 << " or " << kFileFormatV5;
LogDataPackError(BAD_VERSION);
return false;
}
if (text_encoding_type_ != UTF8 && text_encoding_type_ != UTF16 &&
text_encoding_type_ != BINARY) {
LOG(ERROR) << "Bad data pack text encoding: got " << text_encoding_type_
<< ", expected between " << BINARY << " and " << UTF16;
LogDataPackError(WRONG_ENCODING);
return false;
}
if (!SanityCheckFileAndRegisterResources(header_length, data, data_length))
return false;
data_source_ = std::move(data_source);
return true;
}
const DataPack::Entry* DataPack::LookupEntryById(uint16_t resource_id) const {
const Entry* ret = reinterpret_cast<const Entry*>(
bsearch(&resource_id, resource_table_, resource_count_, sizeof(Entry),
Entry::CompareById));
if (ret == nullptr) {
const Alias* alias = reinterpret_cast<const Alias*>(
bsearch(&resource_id, alias_table_, alias_count_, sizeof(Alias),
Alias::CompareById));
if (alias != nullptr) {
ret = &resource_table_[alias->entry_index];
}
}
return ret;
}
bool DataPack::HasResource(uint16_t resource_id) const {
return !!LookupEntryById(resource_id);
}
void DataPack::GetStringPieceFromOffset(uint32_t target_offset,
uint32_t next_offset,
const uint8_t* data_source,
base::StringPiece* data) {
size_t length = next_offset - target_offset;
*data = base::StringPiece(
reinterpret_cast<const char*>(data_source + target_offset), length);
}
bool DataPack::GetStringPiece(uint16_t resource_id,
base::StringPiece* data) const {
#if !defined(ARCH_CPU_LITTLE_ENDIAN)
#error "datapack assumes little endian"
#endif
const Entry* target = LookupEntryById(resource_id);
if (!target)
return false;
const Entry* next_entry = target + 1;
size_t entry_offset =
reinterpret_cast<const uint8_t*>(next_entry) - data_source_->GetData();
size_t pak_size = data_source_->GetLength();
if (entry_offset > pak_size || next_entry->file_offset > pak_size) {
size_t entry_index = target - resource_table_;
LOG(ERROR) << "Entry #" << entry_index << " in data pack points off end "
<< "of file. This should have been caught when loading. Was the "
<< "file modified?";
return false;
}
if (target->file_offset > next_entry->file_offset) {
size_t entry_index = target - resource_table_;
size_t next_index = next_entry - resource_table_;
LOG(ERROR) << "Entry #" << next_index << " in data pack is before Entry #"
<< entry_index << ". This should have been caught when loading. "
<< "Was the file modified?";
return false;
}
MaybePrintResourceId(resource_id);
GetStringPieceFromOffset(target->file_offset, next_entry->file_offset,
data_source_->GetData(), data);
return true;
}
base::RefCountedStaticMemory* DataPack::GetStaticMemory(
uint16_t resource_id) const {
base::StringPiece piece;
if (!GetStringPiece(resource_id, &piece))
return NULL;
return new base::RefCountedStaticMemory(piece.data(), piece.length());
}
ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
return text_encoding_type_;
}
ResourceScaleFactor DataPack::GetResourceScaleFactor() const {
return resource_scale_factor_;
}
#if DCHECK_IS_ON()
void DataPack::CheckForDuplicateResources(
const std::vector<std::unique_ptr<ResourceHandle>>& packs) {
for (size_t i = 0; i < resource_count_ + 1; ++i) {
const uint16_t resource_id = resource_table_[i].resource_id;
const float resource_scale =
GetScaleForResourceScaleFactor(resource_scale_factor_);
for (const auto& handle : packs) {
if (GetScaleForResourceScaleFactor(handle->GetResourceScaleFactor()) !=
resource_scale)
continue;
DCHECK(!handle->HasResource(resource_id))
<< "Duplicate resource " << resource_id << " with scale "
<< resource_scale;
}
}
}
#endif
bool DataPack::WritePack(const base::FilePath& path,
const std::map<uint16_t, base::StringPiece>& resources,
TextEncodingType text_encoding_type) {
#if !defined(ARCH_CPU_LITTLE_ENDIAN)
#error "datapack assumes little endian"
#endif
if (text_encoding_type != UTF8 && text_encoding_type != UTF16 &&
text_encoding_type != BINARY) {
LOG(ERROR) << "Invalid text encoding type, got " << text_encoding_type
<< ", expected between " << BINARY << " and " << UTF16;
return false;
}
size_t resources_count = resources.size();
if (static_cast<uint16_t>(resources_count) != resources_count) {
LOG(ERROR) << "Too many resources (" << resources_count << ")";
return false;
}
ScopedFileWriter file(path);
if (!file.valid())
return false;
uint32_t encoding = static_cast<uint32_t>(text_encoding_type);
std::vector<uint16_t> resource_ids;
std::map<uint16_t, uint16_t> aliases;
if (resources_count > 0) {
std::map<base::StringPiece, uint16_t> rev_map;
for (const auto& entry : resources) {
auto it = rev_map.find(entry.second);
if (it != rev_map.end()) {
aliases.emplace(entry.first, it->second);
} else {
const auto entry_index = static_cast<uint16_t>(resource_ids.size());
rev_map.emplace(entry.second, entry_index);
resource_ids.push_back(entry.first);
}
}
}
DCHECK(std::is_sorted(resource_ids.begin(), resource_ids.end()));
const uint16_t alias_count = static_cast<uint16_t>(aliases.size());
const uint16_t entry_count = static_cast<uint16_t>(resource_ids.size());
DCHECK_EQ(static_cast<size_t>(entry_count) + static_cast<size_t>(alias_count),
resources_count);
file.Write(&kFileFormatV5, sizeof(kFileFormatV5));
file.Write(&encoding, sizeof(uint32_t));
file.Write(&entry_count, sizeof(entry_count));
file.Write(&alias_count, sizeof(alias_count));
const uint32_t index_length = (entry_count + 1) * sizeof(Entry);
const uint32_t alias_table_length = alias_count * sizeof(Alias);
uint32_t data_offset = kHeaderLengthV5 + index_length + alias_table_length;
for (const uint16_t resource_id : resource_ids) {
file.Write(&resource_id, sizeof(resource_id));
file.Write(&data_offset, sizeof(data_offset));
data_offset += resources.find(resource_id)->second.length();
}
const uint16_t extra_resource_id = 0;
file.Write(&extra_resource_id, sizeof(extra_resource_id));
file.Write(&data_offset, sizeof(data_offset));
for (const std::pair<const uint16_t, uint16_t>& alias : aliases) {
file.Write(&alias, sizeof(alias));
}
for (const auto& resource_id : resource_ids) {
const base::StringPiece data = resources.find(resource_id)->second;
file.Write(data.data(), data.length());
}
return file.Close();
}
}