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/chromeos/system/statistics_provider.h" 21 #include "chrome/browser/profiles/profile_manager.h" 22 #include "chromeos/network/network_state.h" 23 #include "chromeos/network/network_state_handler.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 74 CustomizationDocument::CustomizationDocument( 75 const std::string& accepted_version) 76 : accepted_version_(accepted_version) {} 77 78 CustomizationDocument::~CustomizationDocument() {} 79 80 bool CustomizationDocument::LoadManifestFromFile( 81 const base::FilePath& manifest_path) { 82 std::string manifest; 83 if (!file_util::ReadFileToString(manifest_path, &manifest)) 84 return false; 85 return LoadManifestFromString(manifest); 86 } 87 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 113 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 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 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 160 StartupCustomizationDocument::~StartupCustomizationDocument() {} 161 162 StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() { 163 return Singleton<StartupCustomizationDocument, 164 DefaultSingletonTraits<StartupCustomizationDocument> >::get(); 165 } 166 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::kHardwareClass, &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 218 std::string StartupCustomizationDocument::GetHelpPage( 219 const std::string& locale) const { 220 return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr); 221 } 222 223 std::string StartupCustomizationDocument::GetEULAPage( 224 const std::string& locale) const { 225 return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr); 226 } 227 228 // ServicesCustomizationDocument implementation. ------------------------------- 229 230 ServicesCustomizationDocument::ServicesCustomizationDocument() 231 : CustomizationDocument(kAcceptedManifestVersion), 232 url_(kServicesCustomizationManifestUrl) { 233 } 234 235 ServicesCustomizationDocument::ServicesCustomizationDocument( 236 const std::string& manifest) 237 : CustomizationDocument(kAcceptedManifestVersion) { 238 LoadManifestFromString(manifest); 239 } 240 241 ServicesCustomizationDocument::~ServicesCustomizationDocument() {} 242 243 // static 244 ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() { 245 return Singleton<ServicesCustomizationDocument, 246 DefaultSingletonTraits<ServicesCustomizationDocument> >::get(); 247 } 248 249 // static 250 void ServicesCustomizationDocument::RegisterPrefs( 251 PrefRegistrySimple* registry) { 252 registry->RegisterBooleanPref(kServicesCustomizationAppliedPref, false); 253 } 254 255 // static 256 bool ServicesCustomizationDocument::WasApplied() { 257 PrefService* prefs = g_browser_process->local_state(); 258 return prefs->GetBoolean(kServicesCustomizationAppliedPref); 259 } 260 261 // static 262 void ServicesCustomizationDocument::SetApplied(bool val) { 263 PrefService* prefs = g_browser_process->local_state(); 264 prefs->SetBoolean(kServicesCustomizationAppliedPref, val); 265 } 266 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 278 void ServicesCustomizationDocument::ReadFileInBackground( 279 const base::FilePath& file) { 280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 281 282 std::string manifest; 283 if (file_util::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 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 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 328 bool ServicesCustomizationDocument::ApplyCustomization() { 329 // TODO(dpolukhin): apply customized apps, exts and support page. 330 SetApplied(true); 331 return true; 332 } 333 334 std::string ServicesCustomizationDocument::GetInitialStartPage( 335 const std::string& locale) const { 336 return GetLocaleSpecificString( 337 locale, kAppContentAttr, kInitialStartPageAttr); 338 } 339 340 std::string ServicesCustomizationDocument::GetSupportPage( 341 const std::string& locale) const { 342 return GetLocaleSpecificString( 343 locale, kAppContentAttr, kSupportPageAttr); 344 } 345 346 } // namespace chromeos 347