// 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_; }