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
26namespace {
27
28const char kVersionAttr[] = "version";
29const char kDefaultAttr[] = "default";
30const char kInitialLocaleAttr[] = "initial_locale";
31const char kInitialTimezoneAttr[] = "initial_timezone";
32const char kKeyboardLayoutAttr[] = "keyboard_layout";
33const char kRegistrationUrlAttr[] = "registration_url";
34const char kHwidMapAttr[] = "hwid_map";
35const char kHwidMaskAttr[] = "hwid_mask";
36const char kSetupContentAttr[] = "setup_content";
37const char kHelpPageAttr[] = "help_page";
38const char kEulaPageAttr[] = "eula_page";
39const char kAppContentAttr[] = "app_content";
40const char kInitialStartPageAttr[] = "initial_start_page";
41const char kSupportPageAttr[] = "support_page";
42
43const char kAcceptedManifestVersion[] = "1.0";
44
45const char kHwid[] = "hwid";
46
47// Carrier deals attributes.
48const char kCarrierDealsAttr[] = "carrier_deals";
49const char kDealLocaleAttr[] = "deal_locale";
50const char kTopUpURLAttr[] = "top_up_url";
51const char kNotificationCountAttr[] = "notification_count";
52const char kDealExpireDateAttr[] = "expire_date";
53const char kLocalizedContentAttr[] = "localized_content";
54const char kNotificationTextAttr[] = "notification_text";
55
56// Path to OEM partner startup customization manifest.
57const char kStartupCustomizationManifestPath[] =
58    "/opt/oem/etc/startup_manifest.json";
59
60// URL where to fetch OEM services customization manifest from.
61const 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.
66const char kServicesCustomizationAppliedPref[] = "ServicesCustomizationApplied";
67
68// Maximum number of retries to fetch file if network is not available.
69const int kMaxFetchRetries = 3;
70
71// Delay between file fetch retries if network is not available.
72const int kRetriesDelayInSec = 2;
73
74}  // anonymous namespace
75
76DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::ServicesCustomizationDocument);
77
78namespace chromeos {
79
80// CustomizationDocument implementation. ---------------------------------------
81
82bool 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
90bool 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
110std::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
138StartupCustomizationDocument::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
148StartupCustomizationDocument::StartupCustomizationDocument(
149    SystemAccess* system_access, const std::string& manifest) {
150  LoadManifestFromString(manifest);
151  Init(system_access);
152}
153
154StartupCustomizationDocument* StartupCustomizationDocument::GetInstance() {
155  return Singleton<StartupCustomizationDocument,
156      DefaultSingletonTraits<StartupCustomizationDocument> >::get();
157}
158
159void 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, &registration_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
205std::string StartupCustomizationDocument::GetHelpPage(
206    const std::string& locale) const {
207  return GetLocaleSpecificString(locale, kSetupContentAttr, kHelpPageAttr);
208}
209
210std::string StartupCustomizationDocument::GetEULAPage(
211    const std::string& locale) const {
212  return GetLocaleSpecificString(locale, kSetupContentAttr, kEulaPageAttr);
213}
214
215// ServicesCustomizationDocument implementation. -------------------------------
216
217ServicesCustomizationDocument::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, &notification_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
232std::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
248ServicesCustomizationDocument::ServicesCustomizationDocument()
249  : url_(kServicesCustomizationManifestUrl),
250    initial_locale_(WizardController::GetInitialLocale()) {
251}
252
253ServicesCustomizationDocument::ServicesCustomizationDocument(
254    const std::string& manifest, const std::string& initial_locale)
255    : initial_locale_(initial_locale) {
256  LoadManifestFromString(manifest);
257}
258
259// static
260ServicesCustomizationDocument* ServicesCustomizationDocument::GetInstance() {
261  return Singleton<ServicesCustomizationDocument,
262      DefaultSingletonTraits<ServicesCustomizationDocument> >::get();
263}
264
265// static
266void ServicesCustomizationDocument::RegisterPrefs(PrefService* local_state) {
267  local_state->RegisterBooleanPref(kServicesCustomizationAppliedPref, false);
268}
269
270// static
271bool ServicesCustomizationDocument::WasApplied() {
272  PrefService* prefs = g_browser_process->local_state();
273  return prefs->GetBoolean(kServicesCustomizationAppliedPref);
274}
275
276// static
277void ServicesCustomizationDocument::SetApplied(bool val) {
278  PrefService* prefs = g_browser_process->local_state();
279  prefs->SetBoolean(kServicesCustomizationAppliedPref, val);
280}
281
282void 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
293void 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
309void 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
317void 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
340bool ServicesCustomizationDocument::ApplyCustomization() {
341  // TODO(dpolukhin): apply customized apps, exts and support page.
342  SetApplied(true);
343  return true;
344}
345
346std::string ServicesCustomizationDocument::GetInitialStartPage(
347    const std::string& locale) const {
348  return GetLocaleSpecificString(
349      locale, kAppContentAttr, kInitialStartPageAttr);
350}
351
352std::string ServicesCustomizationDocument::GetSupportPage(
353    const std::string& locale) const {
354  return GetLocaleSpecificString(
355      locale, kAppContentAttr, kSupportPageAttr);
356}
357
358const ServicesCustomizationDocument::CarrierDeal*
359ServicesCustomizationDocument::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
381bool 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