1 // Copyright (c) 2011 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/file_path.h" 8 #include "base/file_util.h" 9 #include "base/json/json_reader.h" 10 #include "base/logging.h" 11 #include "base/string_tokenizer.h" 12 #include "base/string_util.h" 13 #include "base/time.h" 14 #include "base/utf_string_conversions.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/chromeos/cros/cros_library.h" 17 #include "chrome/browser/chromeos/cros/network_library.h" 18 #include "chrome/browser/chromeos/login/wizard_controller.h" 19 #include "chrome/browser/chromeos/system_access.h" 20 #include "chrome/browser/prefs/pref_service.h" 21 #include "chrome/browser/profiles/profile_manager.h" 22 #include "content/browser/browser_thread.h" 23 24 // Manifest attributes names. 25 26 namespace { 27 28 const char kVersionAttr[] = "version"; 29 const char kDefaultAttr[] = "default"; 30 const char kInitialLocaleAttr[] = "initial_locale"; 31 const char kInitialTimezoneAttr[] = "initial_timezone"; 32 const char kKeyboardLayoutAttr[] = "keyboard_layout"; 33 const char kRegistrationUrlAttr[] = "registration_url"; 34 const char kHwidMapAttr[] = "hwid_map"; 35 const char kHwidMaskAttr[] = "hwid_mask"; 36 const char kSetupContentAttr[] = "setup_content"; 37 const char kHelpPageAttr[] = "help_page"; 38 const char kEulaPageAttr[] = "eula_page"; 39 const char kAppContentAttr[] = "app_content"; 40 const char kInitialStartPageAttr[] = "initial_start_page"; 41 const char kSupportPageAttr[] = "support_page"; 42 43 const char kAcceptedManifestVersion[] = "1.0"; 44 45 const char kHwid[] = "hwid"; 46 47 // Carrier deals attributes. 48 const char kCarrierDealsAttr[] = "carrier_deals"; 49 const char kDealLocaleAttr[] = "deal_locale"; 50 const char kTopUpURLAttr[] = "top_up_url"; 51 const char kNotificationCountAttr[] = "notification_count"; 52 const char kDealExpireDateAttr[] = "expire_date"; 53 const char kLocalizedContentAttr[] = "localized_content"; 54 const char kNotificationTextAttr[] = "notification_text"; 55 56 // Path to OEM partner startup customization manifest. 57 const char kStartupCustomizationManifestPath[] = 58 "/opt/oem/etc/startup_manifest.json"; 59 60 // URL where to fetch OEM services customization manifest from. 61 const char kServicesCustomizationManifestUrl[] = 62 "file:///opt/oem/etc/services_manifest.json"; 63 64 // Name of local state option that tracks if services customization has been 65 // applied. 66 const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied"; 67 68 // Maximum number of retries to fetch file if network is not available. 69 const int kMaxFetchRetries = 3; 70 71 // Delay between file fetch retries if network is not available. 72 const int kRetriesDelayInSec = 2; 73 74 } // anonymous namespace 75 76 DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::ServicesCustomizationDocument); 77 78 namespace chromeos { 79 80 // CustomizationDocument implementation. --------------------------------------- 81 82 bool CustomizationDocument::LoadManifestFromFile( 83 const FilePath& manifest_path) { 84 std::string manifest; 85 if (!file_util::ReadFileToString(manifest_path, &manifest)) 86 return false; 87 return LoadManifestFromString(manifest); 88 } 89 90 bool CustomizationDocument::LoadManifestFromString( 91 const std::string& manifest) { 92 scoped_ptr<Value> root(base::JSONReader::Read(manifest, true)); 93 DCHECK(root.get() != NULL); 94 if (root.get() == NULL) 95 return false; 96 DCHECK(root->GetType() == Value::TYPE_DICTIONARY); 97 if (root->GetType() == Value::TYPE_DICTIONARY) { 98 root_.reset(static_cast<DictionaryValue*>(root.release())); 99 std::string result; 100 if (root_->GetString(kVersionAttr, &result) && 101 result == kAcceptedManifestVersion) 102 return true; 103 104 LOG(ERROR) << "Wrong customization manifest version"; 105 root_.reset(NULL); 106 } 107 return false; 108 } 109 110 std::string CustomizationDocument::GetLocaleSpecificString( 111 const std::string& locale, 112 const std::string& dictionary_name, 113 const std::string& entry_name) const { 114 DictionaryValue* dictionary_content = NULL; 115 if (!root_.get() || 116 !root_->GetDictionary(dictionary_name, &dictionary_content)) 117 return std::string(); 118 119 DictionaryValue* locale_dictionary = NULL; 120 if (dictionary_content->GetDictionary(locale, &locale_dictionary)) { 121 std::string result; 122 if (locale_dictionary->GetString(entry_name, &result)) 123 return result; 124 } 125 126 DictionaryValue* default_dictionary = NULL; 127 if (dictionary_content->GetDictionary(kDefaultAttr, &default_dictionary)) { 128 std::string result; 129 if (default_dictionary->GetString(entry_name, &result)) 130 return result; 131 } 132 133 return std::string(); 134 } 135 136 // StartupCustomizationDocument implementation. -------------------------------- 137 138 StartupCustomizationDocument::StartupCustomizationDocument() { 139 { 140 // Loading manifest causes us to do blocking IO on UI thread. 141 // Temporarily allow it until we fix http://crosbug.com/11103 142 base::ThreadRestrictions::ScopedAllowIO allow_io; 143 LoadManifestFromFile(FilePath(kStartupCustomizationManifestPath)); 144 } 145 Init(SystemAccess::GetInstance()); 146 } 147 148 StartupCustomizationDocument::StartupCustomizationDocument( 149 SystemAccess* system_access, const std::string& manifest) { 150 LoadManifestFromString(manifest); 151 Init(system_access); 152 } 153 154 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() { 155 return Singleton<StartupCustomizationDocument, 156 DefaultSingletonTraits<StartupCustomizationDocument> >::get(); 157 } 158 159 void StartupCustomizationDocument::Init(SystemAccess* system_access) { 160 if (!IsReady()) 161 return; 162 163 root_->GetString(kInitialLocaleAttr, &initial_locale_); 164 root_->GetString(kInitialTimezoneAttr, &initial_timezone_); 165 root_->GetString(kKeyboardLayoutAttr, &keyboard_layout_); 166 root_->GetString(kRegistrationUrlAttr, ®istration_url_); 167 168 std::string hwid; 169 if (system_access->GetMachineStatistic(kHwid, &hwid)) { 170 ListValue* hwid_list = NULL; 171 if (root_->GetList(kHwidMapAttr, &hwid_list)) { 172 for (size_t i = 0; i < hwid_list->GetSize(); ++i) { 173 DictionaryValue* hwid_dictionary = NULL; 174 std::string hwid_mask; 175 if (hwid_list->GetDictionary(i, &hwid_dictionary) && 176 hwid_dictionary->GetString(kHwidMaskAttr, &hwid_mask)) { 177 if (MatchPattern(hwid, hwid_mask)) { 178 // If HWID for this machine matches some mask, use HWID specific 179 // settings. 180 std::string result; 181 if (hwid_dictionary->GetString(kInitialLocaleAttr, &result)) 182 initial_locale_ = result; 183 184 if (hwid_dictionary->GetString(kInitialTimezoneAttr, &result)) 185 initial_timezone_ = result; 186 187 if (hwid_dictionary->GetString(kKeyboardLayoutAttr, &result)) 188 keyboard_layout_ = result; 189 } 190 // Don't break here to allow other entires to be applied if match. 191 } else { 192 LOG(ERROR) << "Syntax error in customization manifest"; 193 } 194 } 195 } 196 } else { 197 LOG(ERROR) << "HWID is missing in machine statistics"; 198 } 199 200 system_access->GetMachineStatistic(kInitialLocaleAttr, &initial_locale_); 201 system_access->GetMachineStatistic(kInitialTimezoneAttr, &initial_timezone_); 202 system_access->GetMachineStatistic(kKeyboardLayoutAttr, &keyboard_layout_); 203 } 204 205 std::string StartupCustomizationDocument::GetHelpPage( 206 const std::string& locale) const { 207 return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr); 208 } 209 210 std::string StartupCustomizationDocument::GetEULAPage( 211 const std::string& locale) const { 212 return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr); 213 } 214 215 // ServicesCustomizationDocument implementation. ------------------------------- 216 217 ServicesCustomizationDocument::CarrierDeal::CarrierDeal( 218 DictionaryValue* deal_dict) 219 : notification_count(0), 220 localized_strings(NULL) { 221 deal_dict->GetString(kDealLocaleAttr, &deal_locale); 222 deal_dict->GetString(kTopUpURLAttr, &top_up_url); 223 deal_dict->GetInteger(kNotificationCountAttr, ¬ification_count); 224 std::string date_string; 225 if (deal_dict->GetString(kDealExpireDateAttr, &date_string)) { 226 if (!base::Time::FromString(ASCIIToWide(date_string).c_str(), &expire_date)) 227 LOG(ERROR) << "Error parsing deal_expire_date: " << date_string; 228 } 229 deal_dict->GetDictionary(kLocalizedContentAttr, &localized_strings); 230 } 231 232 std::string ServicesCustomizationDocument::CarrierDeal::GetLocalizedString( 233 const std::string& locale, const std::string& id) const { 234 std::string result; 235 if (localized_strings) { 236 DictionaryValue* locale_dict = NULL; 237 if (localized_strings->GetDictionary(locale, &locale_dict) && 238 locale_dict->GetString(id, &result)) { 239 return result; 240 } else if (localized_strings->GetDictionary(kDefaultAttr, &locale_dict) && 241 locale_dict->GetString(id, &result)) { 242 return result; 243 } 244 } 245 return result; 246 } 247 248 ServicesCustomizationDocument::ServicesCustomizationDocument() 249 : url_(kServicesCustomizationManifestUrl), 250 initial_locale_(WizardController::GetInitialLocale()) { 251 } 252 253 ServicesCustomizationDocument::ServicesCustomizationDocument( 254 const std::string& manifest, const std::string& initial_locale) 255 : initial_locale_(initial_locale) { 256 LoadManifestFromString(manifest); 257 } 258 259 // static 260 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() { 261 return Singleton<ServicesCustomizationDocument, 262 DefaultSingletonTraits<ServicesCustomizationDocument> >::get(); 263 } 264 265 // static 266 void ServicesCustomizationDocument::RegisterPrefs(PrefService* local_state) { 267 local_state->RegisterBooleanPref(kServicesCustomizationAppliedPref, false); 268 } 269 270 // static 271 bool ServicesCustomizationDocument::WasApplied() { 272 PrefService* prefs = g_browser_process->local_state(); 273 return prefs->GetBoolean(kServicesCustomizationAppliedPref); 274 } 275 276 // static 277 void ServicesCustomizationDocument::SetApplied(bool val) { 278 PrefService* prefs = g_browser_process->local_state(); 279 prefs->SetBoolean(kServicesCustomizationAppliedPref, val); 280 } 281 282 void ServicesCustomizationDocument::StartFetching() { 283 if (url_.SchemeIsFile()) { 284 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 285 NewRunnableMethod(this, 286 &ServicesCustomizationDocument::ReadFileInBackground, 287 FilePath(url_.path()))); 288 } else { 289 StartFileFetch(); 290 } 291 } 292 293 void ServicesCustomizationDocument::ReadFileInBackground(const FilePath& file) { 294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 295 296 std::string manifest; 297 if (file_util::ReadFileToString(file, &manifest)) { 298 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 299 NewRunnableMethod( 300 this, 301 &ServicesCustomizationDocument::LoadManifestFromString, 302 manifest)); 303 } else { 304 VLOG(1) << "Failed to load services customization manifest from: " 305 << file.value(); 306 } 307 } 308 309 void ServicesCustomizationDocument::StartFileFetch() { 310 DCHECK(url_.is_valid()); 311 url_fetcher_.reset(new URLFetcher(url_, URLFetcher::GET, this)); 312 url_fetcher_->set_request_context( 313 ProfileManager::GetDefaultProfile()->GetRequestContext()); 314 url_fetcher_->Start(); 315 } 316 317 void ServicesCustomizationDocument::OnURLFetchComplete( 318 const URLFetcher* source, 319 const GURL& url, 320 const net::URLRequestStatus& status, 321 int response_code, 322 const ResponseCookies& cookies, 323 const std::string& data) { 324 if (response_code == 200) { 325 LoadManifestFromString(data); 326 } else { 327 NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary(); 328 if (!network->Connected() && num_retries_ < kMaxFetchRetries) { 329 num_retries_++; 330 retry_timer_.Start(base::TimeDelta::FromSeconds(kRetriesDelayInSec), 331 this, &ServicesCustomizationDocument::StartFileFetch); 332 return; 333 } 334 LOG(ERROR) << "URL fetch for services customization failed:" 335 << " response code = " << response_code 336 << " URL = " << url.spec(); 337 } 338 } 339 340 bool ServicesCustomizationDocument::ApplyCustomization() { 341 // TODO(dpolukhin): apply customized apps, exts and support page. 342 SetApplied(true); 343 return true; 344 } 345 346 std::string ServicesCustomizationDocument::GetInitialStartPage( 347 const std::string& locale) const { 348 return GetLocaleSpecificString( 349 locale, kAppContentAttr, kInitialStartPageAttr); 350 } 351 352 std::string ServicesCustomizationDocument::GetSupportPage( 353 const std::string& locale) const { 354 return GetLocaleSpecificString( 355 locale, kAppContentAttr, kSupportPageAttr); 356 } 357 358 const ServicesCustomizationDocument::CarrierDeal* 359 ServicesCustomizationDocument::GetCarrierDeal(const std::string& carrier_id, 360 bool check_restrictions) const { 361 CarrierDeals::const_iterator iter = carrier_deals_.find(carrier_id); 362 if (iter != carrier_deals_.end()) { 363 CarrierDeal* deal = iter->second; 364 if (check_restrictions) { 365 // Deal locale has to match initial_locale (= launch country). 366 if (initial_locale_ != deal->deal_locale) 367 return NULL; 368 // Make sure that deal is still active, 369 // i.e. if deal expire date is defined, check it. 370 if (!deal->expire_date.is_null() && 371 deal->expire_date <= base::Time::Now()) { 372 return NULL; 373 } 374 } 375 return deal; 376 } else { 377 return NULL; 378 } 379 } 380 381 bool ServicesCustomizationDocument::LoadManifestFromString( 382 const std::string& manifest) { 383 if (!CustomizationDocument::LoadManifestFromString(manifest)) 384 return false; 385 386 DictionaryValue* carriers = NULL; 387 if (root_.get() && root_->GetDictionary(kCarrierDealsAttr, &carriers)) { 388 for (DictionaryValue::key_iterator iter = carriers->begin_keys(); 389 iter != carriers->end_keys(); ++iter) { 390 DictionaryValue* carrier_deal = NULL; 391 if (carriers->GetDictionary(*iter, &carrier_deal)) { 392 carrier_deals_[*iter] = new CarrierDeal(carrier_deal); 393 } 394 } 395 } 396 return true; 397 } 398 399 } // namespace chromeos 400