// Copyright (c) 2012 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 "chrome_frame/simple_resource_loader.h"
#include <atlbase.h>
#include <algorithm>
#include "base/base_paths.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/i18n/rtl.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/i18n.h"
#include "base/win/windows_version.h"
#include "chrome_frame/policy_settings.h"
#include "ui/base/resource/data_pack.h"
namespace {
const wchar_t kLocalesDirName[] = L"Locales";
bool IsInvalidTagCharacter(wchar_t tag_character) {
return !(L'-' == tag_character ||
IsAsciiDigit(tag_character) ||
IsAsciiAlpha(tag_character));
}
// A helper function object that performs a lower-case ASCII comparison between
// two strings.
class CompareInsensitiveASCII
: public std::unary_function<const std::wstring&, bool> {
public:
explicit CompareInsensitiveASCII(const std::wstring& value)
: value_lowered_(WideToASCII(value)) {
StringToLowerASCII(&value_lowered_);
}
bool operator()(const std::wstring& comparand) {
return LowerCaseEqualsASCII(comparand, value_lowered_.c_str());
}
private:
std::string value_lowered_;
};
// Returns true if the value was added.
bool PushBackIfAbsent(
const std::wstring& value,
std::vector<std::wstring>* collection) {
if (collection->end() ==
std::find_if(collection->begin(), collection->end(),
CompareInsensitiveASCII(value))) {
collection->push_back(value);
return true;
}
return false;
}
// Returns true if the collection is modified.
bool PushBackWithFallbackIfAbsent(
const std::wstring& language,
std::vector<std::wstring>* collection) {
bool modified = false;
if (!language.empty()) {
// Try adding the language itself.
modified = PushBackIfAbsent(language, collection);
// Now try adding its fallback, if it has one.
std::wstring::size_type dash_pos = language.find(L'-');
if (0 < dash_pos && language.size() - 1 > dash_pos)
modified |= PushBackIfAbsent(language.substr(0, dash_pos), collection);
}
return modified;
}
} // namespace
SimpleResourceLoader::SimpleResourceLoader()
: data_pack_(NULL),
locale_dll_handle_(NULL) {
// Find and load the resource DLL.
std::vector<std::wstring> language_tags;
// First, try the locale dictated by policy and its fallback.
PushBackWithFallbackIfAbsent(
PolicySettings::GetInstance()->ApplicationLocale(),
&language_tags);
// Next, try the thread, process, user, system languages.
GetPreferredLanguages(&language_tags);
// Finally, fall-back on "en-US" (which may already be present in the vector,
// but that's okay since we'll exit with success when the first is tried).
language_tags.push_back(L"en-US");
base::FilePath locales_path;
DetermineLocalesDirectory(&locales_path);
if (!LoadLocalePack(language_tags, locales_path, &locale_dll_handle_,
&data_pack_, &language_)) {
NOTREACHED() << "Failed loading any resource dll (even \"en-US\").";
}
}
SimpleResourceLoader::~SimpleResourceLoader() {
delete data_pack_;
}
// static
SimpleResourceLoader* SimpleResourceLoader::GetInstance() {
return Singleton<SimpleResourceLoader>::get();
}
// static
void SimpleResourceLoader::GetPreferredLanguages(
std::vector<std::wstring>* language_tags) {
DCHECK(language_tags);
// The full set of preferred languages and their fallbacks are given priority.
std::vector<std::wstring> languages;
if (base::win::i18n::GetThreadPreferredUILanguageList(&languages)) {
for (std::vector<std::wstring>::const_iterator scan = languages.begin(),
end = languages.end(); scan != end; ++scan) {
PushBackIfAbsent(*scan, language_tags);
}
}
// Use the base i18n routines (i.e., ICU) as a last, best hope for something
// meaningful for the user.
PushBackWithFallbackIfAbsent(ASCIIToWide(base::i18n::GetConfiguredLocale()),
language_tags);
}
// static
void SimpleResourceLoader::DetermineLocalesDirectory(
base::FilePath* locales_path) {
DCHECK(locales_path);
base::FilePath module_path;
PathService::Get(base::DIR_MODULE, &module_path);
*locales_path = module_path.Append(kLocalesDirName);
// We may be residing in the "locales" directory's parent, or we might be
// in a sibling directory. Move up one and look for Locales again in the
// latter case.
if (!base::DirectoryExists(*locales_path)) {
*locales_path = module_path.DirName();
*locales_path = locales_path->Append(kLocalesDirName);
}
// Don't make a second check to see if the dir is in the parent. We'll notice
// and log that in LoadLocaleDll when we actually try loading DLLs.
}
// static
bool SimpleResourceLoader::IsValidLanguageTag(
const std::wstring& language_tag) {
// "[a-zA-Z]+(-[a-zA-Z0-9]+)*" is a simplification, but better than nothing.
// Rather than pick up the weight of a regex processor, just search for a
// character that isn't in the above set. This will at least weed out
// attempts at "../../EvilBinary".
return language_tag.end() == std::find_if(language_tag.begin(),
language_tag.end(),
&IsInvalidTagCharacter);
}
// static
bool SimpleResourceLoader::LoadLocalePack(
const std::vector<std::wstring>& language_tags,
const base::FilePath& locales_path,
HMODULE* dll_handle,
ui::DataPack** data_pack,
std::wstring* language) {
DCHECK(language);
// The dll should only have resources, not executable code.
const DWORD load_flags =
(base::win::GetVersion() >= base::win::VERSION_VISTA ?
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE :
DONT_RESOLVE_DLL_REFERENCES);
const std::wstring dll_suffix(L".dll");
const std::wstring pack_suffix(L".pak");
bool found_pack = false;
for (std::vector<std::wstring>::const_iterator scan = language_tags.begin(),
end = language_tags.end();
scan != end;
++scan) {
if (!IsValidLanguageTag(*scan)) {
LOG(WARNING) << "Invalid language tag supplied while locating resources:"
" \"" << *scan << "\"";
continue;
}
// Attempt to load both the resource pack and the dll. We return success
// only we load both.
base::FilePath resource_pack_path =
locales_path.Append(*scan + pack_suffix);
base::FilePath dll_path = locales_path.Append(*scan + dll_suffix);
if (base::PathExists(resource_pack_path) &&
base::PathExists(dll_path)) {
scoped_ptr<ui::DataPack> cur_data_pack(
new ui::DataPack(ui::SCALE_FACTOR_100P));
if (!cur_data_pack->LoadFromPath(resource_pack_path))
continue;
HMODULE locale_dll_handle = LoadLibraryEx(dll_path.value().c_str(), NULL,
load_flags);
if (locale_dll_handle) {
*dll_handle = locale_dll_handle;
*language = dll_path.BaseName().RemoveExtension().value();
*data_pack = cur_data_pack.release();
found_pack = true;
break;
} else {
*data_pack = NULL;
}
}
}
DCHECK(found_pack || base::DirectoryExists(locales_path))
<< "Could not locate locales DLL directory.";
return found_pack;
}
std::wstring SimpleResourceLoader::GetLocalizedResource(int message_id) {
if (!data_pack_) {
DLOG(ERROR) << "locale resources are not loaded";
return std::wstring();
}
DCHECK(IS_INTRESOURCE(message_id));
base::StringPiece data;
if (!data_pack_->GetStringPiece(message_id, &data)) {
DLOG(ERROR) << "Unable to find string for resource id:" << message_id;
return std::wstring();
}
// Data pack encodes strings as either UTF8 or UTF16.
string16 msg;
if (data_pack_->GetTextEncodingType() == ui::DataPack::UTF16) {
msg = string16(reinterpret_cast<const char16*>(data.data()),
data.length() / 2);
} else if (data_pack_->GetTextEncodingType() == ui::DataPack::UTF8) {
msg = UTF8ToUTF16(data);
}
return msg;
}
// static
std::wstring SimpleResourceLoader::GetLanguage() {
return SimpleResourceLoader::GetInstance()->language_;
}
// static
std::wstring SimpleResourceLoader::Get(int message_id) {
SimpleResourceLoader* loader = SimpleResourceLoader::GetInstance();
return loader->GetLocalizedResource(message_id);
}
HMODULE SimpleResourceLoader::GetResourceModuleHandle() {
return locale_dll_handle_;
}