1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome_frame/simple_resource_loader.h"
6
7#include <atlbase.h>
8
9#include <algorithm>
10
11#include "base/base_paths.h"
12#include "base/file_util.h"
13#include "base/files/file_path.h"
14#include "base/i18n/rtl.h"
15#include "base/memory/singleton.h"
16#include "base/path_service.h"
17#include "base/strings/string_util.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/win/i18n.h"
20#include "base/win/windows_version.h"
21#include "chrome_frame/policy_settings.h"
22#include "ui/base/resource/data_pack.h"
23
24namespace {
25
26const wchar_t kLocalesDirName[] = L"Locales";
27
28bool IsInvalidTagCharacter(wchar_t tag_character) {
29  return !(L'-' == tag_character ||
30           IsAsciiDigit(tag_character) ||
31           IsAsciiAlpha(tag_character));
32}
33
34// A helper function object that performs a lower-case ASCII comparison between
35// two strings.
36class CompareInsensitiveASCII
37    : public std::unary_function<const std::wstring&, bool> {
38 public:
39  explicit CompareInsensitiveASCII(const std::wstring& value)
40      : value_lowered_(WideToASCII(value)) {
41    StringToLowerASCII(&value_lowered_);
42  }
43  bool operator()(const std::wstring& comparand) {
44    return LowerCaseEqualsASCII(comparand, value_lowered_.c_str());
45  }
46
47 private:
48  std::string value_lowered_;
49};
50
51// Returns true if the value was added.
52bool PushBackIfAbsent(
53    const std::wstring& value,
54    std::vector<std::wstring>* collection) {
55  if (collection->end() ==
56      std::find_if(collection->begin(), collection->end(),
57                   CompareInsensitiveASCII(value))) {
58    collection->push_back(value);
59    return true;
60  }
61  return false;
62}
63
64// Returns true if the collection is modified.
65bool PushBackWithFallbackIfAbsent(
66    const std::wstring& language,
67    std::vector<std::wstring>* collection) {
68  bool modified = false;
69
70  if (!language.empty()) {
71    // Try adding the language itself.
72    modified = PushBackIfAbsent(language, collection);
73
74    // Now try adding its fallback, if it has one.
75    std::wstring::size_type dash_pos = language.find(L'-');
76    if (0 < dash_pos && language.size() - 1 > dash_pos)
77      modified |= PushBackIfAbsent(language.substr(0, dash_pos), collection);
78  }
79
80  return modified;
81}
82
83}  // namespace
84
85SimpleResourceLoader::SimpleResourceLoader()
86    : data_pack_(NULL),
87      locale_dll_handle_(NULL) {
88  // Find and load the resource DLL.
89  std::vector<std::wstring> language_tags;
90
91  // First, try the locale dictated by policy and its fallback.
92  PushBackWithFallbackIfAbsent(
93      PolicySettings::GetInstance()->ApplicationLocale(),
94      &language_tags);
95
96  // Next, try the thread, process, user, system languages.
97  GetPreferredLanguages(&language_tags);
98
99  // Finally, fall-back on "en-US" (which may already be present in the vector,
100  // but that's okay since we'll exit with success when the first is tried).
101  language_tags.push_back(L"en-US");
102
103  base::FilePath locales_path;
104
105  DetermineLocalesDirectory(&locales_path);
106  if (!LoadLocalePack(language_tags, locales_path, &locale_dll_handle_,
107                      &data_pack_, &language_)) {
108    NOTREACHED() << "Failed loading any resource dll (even \"en-US\").";
109  }
110}
111
112SimpleResourceLoader::~SimpleResourceLoader() {
113  delete data_pack_;
114}
115
116// static
117SimpleResourceLoader* SimpleResourceLoader::GetInstance() {
118  return Singleton<SimpleResourceLoader>::get();
119}
120
121// static
122void SimpleResourceLoader::GetPreferredLanguages(
123    std::vector<std::wstring>* language_tags) {
124  DCHECK(language_tags);
125  // The full set of preferred languages and their fallbacks are given priority.
126  std::vector<std::wstring> languages;
127  if (base::win::i18n::GetThreadPreferredUILanguageList(&languages)) {
128    for (std::vector<std::wstring>::const_iterator scan = languages.begin(),
129             end = languages.end(); scan != end; ++scan) {
130      PushBackIfAbsent(*scan, language_tags);
131    }
132  }
133  // Use the base i18n routines (i.e., ICU) as a last, best hope for something
134  // meaningful for the user.
135  PushBackWithFallbackIfAbsent(ASCIIToWide(base::i18n::GetConfiguredLocale()),
136                               language_tags);
137}
138
139// static
140void SimpleResourceLoader::DetermineLocalesDirectory(
141    base::FilePath* locales_path) {
142  DCHECK(locales_path);
143
144  base::FilePath module_path;
145  PathService::Get(base::DIR_MODULE, &module_path);
146  *locales_path = module_path.Append(kLocalesDirName);
147
148  // We may be residing in the "locales" directory's parent, or we might be
149  // in a sibling directory. Move up one and look for Locales again in the
150  // latter case.
151  if (!base::DirectoryExists(*locales_path)) {
152    *locales_path = module_path.DirName();
153    *locales_path = locales_path->Append(kLocalesDirName);
154  }
155
156  // Don't make a second check to see if the dir is in the parent.  We'll notice
157  // and log that in LoadLocaleDll when we actually try loading DLLs.
158}
159
160// static
161bool SimpleResourceLoader::IsValidLanguageTag(
162    const std::wstring& language_tag) {
163  // "[a-zA-Z]+(-[a-zA-Z0-9]+)*" is a simplification, but better than nothing.
164  // Rather than pick up the weight of a regex processor, just search for a
165  // character that isn't in the above set.  This will at least weed out
166  // attempts at "../../EvilBinary".
167  return language_tag.end() == std::find_if(language_tag.begin(),
168                                            language_tag.end(),
169                                            &IsInvalidTagCharacter);
170}
171
172// static
173bool SimpleResourceLoader::LoadLocalePack(
174    const std::vector<std::wstring>& language_tags,
175    const base::FilePath& locales_path,
176    HMODULE* dll_handle,
177    ui::DataPack** data_pack,
178    std::wstring* language) {
179  DCHECK(language);
180
181  // The dll should only have resources, not executable code.
182  const DWORD load_flags =
183      (base::win::GetVersion() >= base::win::VERSION_VISTA ?
184          LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE :
185          DONT_RESOLVE_DLL_REFERENCES);
186
187  const std::wstring dll_suffix(L".dll");
188  const std::wstring pack_suffix(L".pak");
189
190  bool found_pack = false;
191
192  for (std::vector<std::wstring>::const_iterator scan = language_tags.begin(),
193         end = language_tags.end();
194       scan != end;
195       ++scan) {
196    if (!IsValidLanguageTag(*scan)) {
197      LOG(WARNING) << "Invalid language tag supplied while locating resources:"
198                      " \"" << *scan << "\"";
199      continue;
200    }
201
202    // Attempt to load both the resource pack and the dll. We return success
203    // only we load both.
204    base::FilePath resource_pack_path =
205        locales_path.Append(*scan + pack_suffix);
206    base::FilePath dll_path = locales_path.Append(*scan + dll_suffix);
207
208    if (base::PathExists(resource_pack_path) &&
209        base::PathExists(dll_path)) {
210      scoped_ptr<ui::DataPack> cur_data_pack(
211          new ui::DataPack(ui::SCALE_FACTOR_100P));
212      if (!cur_data_pack->LoadFromPath(resource_pack_path))
213        continue;
214
215      HMODULE locale_dll_handle = LoadLibraryEx(dll_path.value().c_str(), NULL,
216                                                load_flags);
217      if (locale_dll_handle) {
218        *dll_handle = locale_dll_handle;
219        *language = dll_path.BaseName().RemoveExtension().value();
220        *data_pack = cur_data_pack.release();
221        found_pack = true;
222        break;
223      } else {
224        *data_pack = NULL;
225      }
226    }
227  }
228  DCHECK(found_pack || base::DirectoryExists(locales_path))
229      << "Could not locate locales DLL directory.";
230  return found_pack;
231}
232
233std::wstring SimpleResourceLoader::GetLocalizedResource(int message_id) {
234  if (!data_pack_) {
235    DLOG(ERROR) << "locale resources are not loaded";
236    return std::wstring();
237  }
238
239  DCHECK(IS_INTRESOURCE(message_id));
240
241  base::StringPiece data;
242  if (!data_pack_->GetStringPiece(message_id, &data)) {
243    DLOG(ERROR) << "Unable to find string for resource id:" << message_id;
244    return std::wstring();
245  }
246
247  // Data pack encodes strings as either UTF8 or UTF16.
248  string16 msg;
249  if (data_pack_->GetTextEncodingType() == ui::DataPack::UTF16) {
250    msg = string16(reinterpret_cast<const char16*>(data.data()),
251                   data.length() / 2);
252  } else if (data_pack_->GetTextEncodingType() == ui::DataPack::UTF8) {
253    msg = UTF8ToUTF16(data);
254  }
255  return msg;
256}
257
258// static
259std::wstring SimpleResourceLoader::GetLanguage() {
260  return SimpleResourceLoader::GetInstance()->language_;
261}
262
263// static
264std::wstring SimpleResourceLoader::Get(int message_id) {
265  SimpleResourceLoader* loader = SimpleResourceLoader::GetInstance();
266  return loader->GetLocalizedResource(message_id);
267}
268
269HMODULE SimpleResourceLoader::GetResourceModuleHandle() {
270  return locale_dll_handle_;
271}
272