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/browser/chromeos/customization_document.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/json/json_reader.h"
12 #include "base/logging.h"
13 #include "base/prefs/pref_registry_simple.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chromeos/login/wizard_controller.h"
20 #include "chrome/browser/profiles/profile_manager.h"
21 #include "chromeos/network/network_state.h"
22 #include "chromeos/network/network_state_handler.h"
23 #include "chromeos/system/statistics_provider.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "net/url_request/url_fetcher.h"
26
27 using content::BrowserThread;
28
29 // Manifest attributes names.
30
31 namespace {
32
33 const char kVersionAttr[] = "version";
34 const char kDefaultAttr[] = "default";
35 const char kInitialLocaleAttr[] = "initial_locale";
36 const char kInitialTimezoneAttr[] = "initial_timezone";
37 const char kKeyboardLayoutAttr[] = "keyboard_layout";
38 const char kRegistrationUrlAttr[] = "registration_url";
39 const char kHwidMapAttr[] = "hwid_map";
40 const char kHwidMaskAttr[] = "hwid_mask";
41 const char kSetupContentAttr[] = "setup_content";
42 const char kHelpPageAttr[] = "help_page";
43 const char kEulaPageAttr[] = "eula_page";
44 const char kAppContentAttr[] = "app_content";
45 const char kInitialStartPageAttr[] = "initial_start_page";
46 const char kSupportPageAttr[] = "support_page";
47
48 const char kAcceptedManifestVersion[] = "1.0";
49
50 // Path to OEM partner startup customization manifest.
51 const char kStartupCustomizationManifestPath[] =
52 "/opt/oem/etc/startup_manifest.json";
53
54 // URL where to fetch OEM services customization manifest from.
55 const char kServicesCustomizationManifestUrl[] =
56 "file:///opt/oem/etc/services_manifest.json";
57
58 // Name of local state option that tracks if services customization has been
59 // applied.
60 const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
61
62 // Maximum number of retries to fetch file if network is not available.
63 const int kMaxFetchRetries = 3;
64
65 // Delay between file fetch retries if network is not available.
66 const int kRetriesDelayInSec = 2;
67
68 } // anonymous namespace
69
70 namespace chromeos {
71
72 // CustomizationDocument implementation. ---------------------------------------
73
CustomizationDocument(const std::string & accepted_version)74 CustomizationDocument::CustomizationDocument(
75 const std::string& accepted_version)
76 : accepted_version_(accepted_version) {}
77
~CustomizationDocument()78 CustomizationDocument::~CustomizationDocument() {}
79
LoadManifestFromFile(const base::FilePath & manifest_path)80 bool CustomizationDocument::LoadManifestFromFile(
81 const base::FilePath& manifest_path) {
82 std::string manifest;
83 if (!base::ReadFileToString(manifest_path, &manifest))
84 return false;
85 return LoadManifestFromString(manifest);
86 }
87
LoadManifestFromString(const std::string & manifest)88 bool CustomizationDocument::LoadManifestFromString(
89 const std::string& manifest) {
90 int error_code = 0;
91 std::string error;
92 scoped_ptr<Value> root(base::JSONReader::ReadAndReturnError(manifest,
93 base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error));
94 if (error_code != base::JSONReader::JSON_NO_ERROR)
95 LOG(ERROR) << error;
96 DCHECK(root.get() != NULL);
97 if (root.get() == NULL)
98 return false;
99 DCHECK(root->GetType() == Value::TYPE_DICTIONARY);
100 if (root->GetType() == Value::TYPE_DICTIONARY) {
101 root_.reset(static_cast<DictionaryValue*>(root.release()));
102 std::string result;
103 if (root_->GetString(kVersionAttr, &result) &&
104 result == accepted_version_)
105 return true;
106
107 LOG(ERROR) << "Wrong customization manifest version";
108 root_.reset(NULL);
109 }
110 return false;
111 }
112
GetLocaleSpecificString(const std::string & locale,const std::string & dictionary_name,const std::string & entry_name) const113 std::string CustomizationDocument::GetLocaleSpecificString(
114 const std::string& locale,
115 const std::string& dictionary_name,
116 const std::string& entry_name) const {
117 DictionaryValue* dictionary_content = NULL;
118 if (!root_.get() ||
119 !root_->GetDictionary(dictionary_name, &dictionary_content))
120 return std::string();
121
122 DictionaryValue* locale_dictionary = NULL;
123 if (dictionary_content->GetDictionary(locale, &locale_dictionary)) {
124 std::string result;
125 if (locale_dictionary->GetString(entry_name, &result))
126 return result;
127 }
128
129 DictionaryValue* default_dictionary = NULL;
130 if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) {
131 std::string result;
132 if (default_dictionary->GetString(entry_name, &result))
133 return result;
134 }
135
136 return std::string();
137 }
138
139 // StartupCustomizationDocument implementation. --------------------------------
140
StartupCustomizationDocument()141 StartupCustomizationDocument::StartupCustomizationDocument()
142 : CustomizationDocument(kAcceptedManifestVersion) {
143 {
144 // Loading manifest causes us to do blocking IO on UI thread.
145 // Temporarily allow it until we fix http://crosbug.com/11103
146 base::ThreadRestrictions::ScopedAllowIO allow_io;
147 LoadManifestFromFile(base::FilePath(kStartupCustomizationManifestPath));
148 }
149 Init(chromeos::system::StatisticsProvider::GetInstance());
150 }
151
StartupCustomizationDocument(chromeos::system::StatisticsProvider * statistics_provider,const std::string & manifest)152 StartupCustomizationDocument::StartupCustomizationDocument(
153 chromeos::system::StatisticsProvider* statistics_provider,
154 const std::string& manifest)
155 : CustomizationDocument(kAcceptedManifestVersion) {
156 LoadManifestFromString(manifest);
157 Init(statistics_provider);
158 }
159
~StartupCustomizationDocument()160 StartupCustomizationDocument::~StartupCustomizationDocument() {}
161
GetInstance()162 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
163 return Singleton<StartupCustomizationDocument,
164 DefaultSingletonTraits<StartupCustomizationDocument> >::get();
165 }
166
Init(chromeos::system::StatisticsProvider * statistics_provider)167 void StartupCustomizationDocument::Init(
168 chromeos::system::StatisticsProvider* statistics_provider) {
169 if (IsReady()) {
170 root_->GetString(kInitialLocaleAttr, &initial_locale_);
171 root_->GetString(kInitialTimezoneAttr, &initial_timezone_);
172 root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_);
173 root_->GetString(kRegistrationUrlAttr, ®istration_url_);
174
175 std::string hwid;
176 if (statistics_provider->GetMachineStatistic(
177 chromeos::system::kHardwareClassKey, &hwid)) {
178 ListValue* hwid_list = NULL;
179 if (root_->GetList(kHwidMapAttr, &hwid_list)) {
180 for (size_t i = 0; i < hwid_list->GetSize(); ++i) {
181 DictionaryValue* hwid_dictionary = NULL;
182 std::string hwid_mask;
183 if (hwid_list->GetDictionary(i, &hwid_dictionary) &&
184 hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) {
185 if (MatchPattern(hwid, hwid_mask)) {
186 // If HWID for this machine matches some mask, use HWID specific
187 // settings.
188 std::string result;
189 if (hwid_dictionary->GetString(kInitialLocaleAttr, &result))
190 initial_locale_ = result;
191
192 if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result))
193 initial_timezone_ = result;
194
195 if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result))
196 keyboard_layout_ = result;
197 }
198 // Don't break here to allow other entires to be applied if match.
199 } else {
200 LOG(ERROR) << "Syntax error in customization manifest";
201 }
202 }
203 }
204 } else {
205 LOG(ERROR) << "HWID is missing in machine statistics";
206 }
207 }
208
209 // If manifest doesn't exist still apply values from VPD.
210 statistics_provider->GetMachineStatistic(kInitialLocaleAttr,
211 &initial_locale_);
212 statistics_provider->GetMachineStatistic(kInitialTimezoneAttr,
213 &initial_timezone_);
214 statistics_provider->GetMachineStatistic(kKeyboardLayoutAttr,
215 &keyboard_layout_);
216 }
217
GetHelpPage(const std::string & locale) const218 std::string StartupCustomizationDocument::GetHelpPage(
219 const std::string& locale) const {
220 return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr);
221 }
222
GetEULAPage(const std::string & locale) const223 std::string StartupCustomizationDocument::GetEULAPage(
224 const std::string& locale) const {
225 return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
226 }
227
228 // ServicesCustomizationDocument implementation. -------------------------------
229
ServicesCustomizationDocument()230 ServicesCustomizationDocument::ServicesCustomizationDocument()
231 : CustomizationDocument(kAcceptedManifestVersion),
232 url_(kServicesCustomizationManifestUrl) {
233 }
234
ServicesCustomizationDocument(const std::string & manifest)235 ServicesCustomizationDocument::ServicesCustomizationDocument(
236 const std::string& manifest)
237 : CustomizationDocument(kAcceptedManifestVersion) {
238 LoadManifestFromString(manifest);
239 }
240
~ServicesCustomizationDocument()241 ServicesCustomizationDocument::~ServicesCustomizationDocument() {}
242
243 // static
GetInstance()244 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
245 return Singleton<ServicesCustomizationDocument,
246 DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
247 }
248
249 // static
RegisterPrefs(PrefRegistrySimple * registry)250 void ServicesCustomizationDocument::RegisterPrefs(
251 PrefRegistrySimple* registry) {
252 registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
253 }
254
255 // static
WasApplied()256 bool ServicesCustomizationDocument::WasApplied() {
257 PrefService* prefs = g_browser_process->local_state();
258 return prefs->GetBoolean(kServicesCustomizationAppliedPref);
259 }
260
261 // static
SetApplied(bool val)262 void ServicesCustomizationDocument::SetApplied(bool val) {
263 PrefService* prefs = g_browser_process->local_state();
264 prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
265 }
266
StartFetching()267 void ServicesCustomizationDocument::StartFetching() {
268 if (url_.SchemeIsFile()) {
269 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
270 base::Bind(&ServicesCustomizationDocument::ReadFileInBackground,
271 base::Unretained(this), // this class is a singleton.
272 base::FilePath(url_.path())));
273 } else {
274 StartFileFetch();
275 }
276 }
277
ReadFileInBackground(const base::FilePath & file)278 void ServicesCustomizationDocument::ReadFileInBackground(
279 const base::FilePath& file) {
280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
281
282 std::string manifest;
283 if (base::ReadFileToString(file, &manifest)) {
284 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
285 base::Bind(
286 base::IgnoreResult(
287 &ServicesCustomizationDocument::LoadManifestFromString),
288 base::Unretained(this), // this class is a singleton.
289 manifest));
290 } else {
291 VLOG(1) << "Failed to load services customization manifest from: "
292 << file.value();
293 }
294 }
295
StartFileFetch()296 void ServicesCustomizationDocument::StartFileFetch() {
297 DCHECK(url_.is_valid());
298 url_fetcher_.reset(net::URLFetcher::Create(
299 url_, net::URLFetcher::GET, this));
300 url_fetcher_->SetRequestContext(
301 ProfileManager::GetDefaultProfile()->GetRequestContext());
302 url_fetcher_->Start();
303 }
304
OnURLFetchComplete(const net::URLFetcher * source)305 void ServicesCustomizationDocument::OnURLFetchComplete(
306 const net::URLFetcher* source) {
307 if (source->GetResponseCode() == 200) {
308 std::string data;
309 source->GetResponseAsString(&data);
310 LoadManifestFromString(data);
311 } else {
312 const NetworkState* default_network =
313 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
314 if (default_network && default_network->IsConnectedState() &&
315 num_retries_ < kMaxFetchRetries) {
316 num_retries_++;
317 retry_timer_.Start(FROM_HERE,
318 base::TimeDelta::FromSeconds(kRetriesDelayInSec),
319 this, &ServicesCustomizationDocument::StartFileFetch);
320 return;
321 }
322 LOG(ERROR) << "URL fetch for services customization failed:"
323 << " response code = " << source->GetResponseCode()
324 << " URL = " << source->GetURL().spec();
325 }
326 }
327
ApplyCustomization()328 bool ServicesCustomizationDocument::ApplyCustomization() {
329 // TODO(dpolukhin): apply customized apps, exts and support page.
330 SetApplied(true);
331 return true;
332 }
333
GetInitialStartPage(const std::string & locale) const334 std::string ServicesCustomizationDocument::GetInitialStartPage(
335 const std::string& locale) const {
336 return GetLocaleSpecificString(
337 locale, kAppContentAttr, kInitialStartPageAttr);
338 }
339
GetSupportPage(const std::string & locale) const340 std::string ServicesCustomizationDocument::GetSupportPage(
341 const std::string& locale) const {
342 return GetLocaleSpecificString(
343 locale, kAppContentAttr, kSupportPageAttr);
344 }
345
346 } // namespace chromeos
347