// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Generating a fingerprint consists of two major steps:
//   (1) Gather all the necessary data.
//   (2) Write it into a protocol buffer.
//
// Step (2) is as simple as it sounds -- it's really just a matter of copying
// data.  Step (1) requires waiting on several asynchronous callbacks, which are
// managed by the FingerprintDataLoader class.

#include "components/autofill/content/browser/risk/fingerprint.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/cpu.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "components/autofill/content/browser/risk/proto/fingerprint.pb.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/font_list_async.h"
#include "content/public/browser/geolocation_provider.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/content_client.h"
#include "content/public/common/geoposition.h"
#include "content/public/common/webplugininfo.h"
#include "gpu/config/gpu_info.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/WebScreenInfo.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/screen.h"

using blink::WebScreenInfo;

namespace autofill {
namespace risk {

namespace {

const int32 kFingerprinterVersion = 1;

// Maximum amount of time, in seconds, to wait for loading asynchronous
// fingerprint data.
const int kTimeoutSeconds = 4;

// Returns the delta between the local timezone and UTC.
base::TimeDelta GetTimezoneOffset() {
  const base::Time utc = base::Time::Now();

  base::Time::Exploded local;
  utc.LocalExplode(&local);

  return base::Time::FromUTCExploded(local) - utc;
}

// Returns the concatenation of the operating system name and version, e.g.
// "Mac OS X 10.6.8".
std::string GetOperatingSystemVersion() {
  return base::SysInfo::OperatingSystemName() + " " +
      base::SysInfo::OperatingSystemVersion();
}

// Adds the list of |fonts| to the |machine|.
void AddFontsToFingerprint(const base::ListValue& fonts,
                           Fingerprint::MachineCharacteristics* machine) {
  for (base::ListValue::const_iterator it = fonts.begin();
       it != fonts.end(); ++it) {
    // Each item in the list is a two-element list such that the first element
    // is the font family and the second is the font name.
    const base::ListValue* font_description = NULL;
    bool success = (*it)->GetAsList(&font_description);
    DCHECK(success);

    std::string font_name;
    success = font_description->GetString(1, &font_name);
    DCHECK(success);

    machine->add_font(font_name);
  }
}

// Adds the list of |plugins| to the |machine|.
void AddPluginsToFingerprint(const std::vector<content::WebPluginInfo>& plugins,
                             Fingerprint::MachineCharacteristics* machine) {
  for (std::vector<content::WebPluginInfo>::const_iterator it = plugins.begin();
       it != plugins.end(); ++it) {
    Fingerprint::MachineCharacteristics::Plugin* plugin =
        machine->add_plugin();
    plugin->set_name(UTF16ToUTF8(it->name));
    plugin->set_description(UTF16ToUTF8(it->desc));
    for (std::vector<content::WebPluginMimeType>::const_iterator mime_type =
             it->mime_types.begin();
         mime_type != it->mime_types.end(); ++mime_type) {
      plugin->add_mime_type(mime_type->mime_type);
    }
    plugin->set_version(UTF16ToUTF8(it->version));
  }
}

// Adds the list of HTTP accept languages to the |machine|.
void AddAcceptLanguagesToFingerprint(
    const std::string& accept_languages_str,
    Fingerprint::MachineCharacteristics* machine) {
  std::vector<std::string> accept_languages;
  base::SplitString(accept_languages_str, ',', &accept_languages);
  for (std::vector<std::string>::const_iterator it = accept_languages.begin();
       it != accept_languages.end(); ++it) {
    machine->add_requested_language(*it);
  }
}

// This function writes
//   (a) the number of screens,
//   (b) the primary display's screen size,
//   (c) the screen's color depth, and
//   (d) the size of the screen unavailable to web page content,
//       i.e. the Taskbar size on Windows
// into the |machine|.
void AddScreenInfoToFingerprint(const WebScreenInfo& screen_info,
                                Fingerprint::MachineCharacteristics* machine) {
  // TODO(scottmg): NativeScreen maybe wrong. http://crbug.com/133312
  machine->set_screen_count(
      gfx::Screen::GetNativeScreen()->GetNumDisplays());

  const gfx::Size screen_size =
      gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().GetSizeInPixel();
  machine->mutable_screen_size()->set_width(screen_size.width());
  machine->mutable_screen_size()->set_height(screen_size.height());

  machine->set_screen_color_depth(screen_info.depth);

  const gfx::Rect screen_rect(screen_info.rect);
  const gfx::Rect available_rect(screen_info.availableRect);
  const gfx::Rect unavailable_rect =
      gfx::SubtractRects(screen_rect, available_rect);
  machine->mutable_unavailable_screen_size()->set_width(
      unavailable_rect.width());
  machine->mutable_unavailable_screen_size()->set_height(
      unavailable_rect.height());
}

// Writes info about the machine's CPU into the |machine|.
void AddCpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) {
  base::CPU cpu;
  machine->mutable_cpu()->set_vendor_name(cpu.vendor_name());
  machine->mutable_cpu()->set_brand(cpu.cpu_brand());
}

// Writes info about the machine's GPU into the |machine|.
void AddGpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) {
  const gpu::GPUInfo& gpu_info =
      content::GpuDataManager::GetInstance()->GetGPUInfo();
  if (!gpu_info.finalized)
    return;

  Fingerprint::MachineCharacteristics::Graphics* graphics =
      machine->mutable_graphics_card();
  graphics->set_vendor_id(gpu_info.gpu.vendor_id);
  graphics->set_device_id(gpu_info.gpu.device_id);
  graphics->set_driver_version(gpu_info.driver_version);
  graphics->set_driver_date(gpu_info.driver_date);

  Fingerprint::MachineCharacteristics::Graphics::PerformanceStatistics*
      gpu_performance = graphics->mutable_performance_statistics();
  gpu_performance->set_graphics_score(gpu_info.performance_stats.graphics);
  gpu_performance->set_gaming_score(gpu_info.performance_stats.gaming);
  gpu_performance->set_overall_score(gpu_info.performance_stats.overall);
}

// Waits for geoposition data to be loaded.  Lives on the IO thread.
class GeopositionLoader {
 public:
  // |callback_| will be called on the UI thread with the loaded geoposition,
  // once it is available.
  GeopositionLoader(
      const base::TimeDelta& timeout,
      const base::Callback<void(const content::Geoposition&)>& callback);
  ~GeopositionLoader() {}

 private:
  // Methods to communicate with the GeolocationProvider.
  void OnGotGeoposition(const content::Geoposition& geoposition);

  // The callback that will be called once the geoposition is available.
  // Will be called on the UI thread.
  const base::Callback<void(const content::Geoposition&)> callback_;

  // The callback used as an "observer" of the GeolocationProvider.
  content::GeolocationProvider::LocationUpdateCallback geolocation_callback_;

  // Timer to enforce a maximum timeout before the |callback_| is called, even
  // if the geoposition has not been loaded.
  base::OneShotTimer<GeopositionLoader> timeout_timer_;
};

GeopositionLoader::GeopositionLoader(
    const base::TimeDelta& timeout,
    const base::Callback<void(const content::Geoposition&)>& callback)
  : callback_(callback) {
  timeout_timer_.Start(FROM_HERE, timeout,
                       base::Bind(&GeopositionLoader::OnGotGeoposition,
                                  base::Unretained(this),
                                  content::Geoposition()));

  geolocation_callback_ =
      base::Bind(&GeopositionLoader::OnGotGeoposition, base::Unretained(this));
  content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback(
      geolocation_callback_, false);
}

void GeopositionLoader::OnGotGeoposition(
    const content::Geoposition& geoposition) {
  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
                                   base::Bind(callback_, geoposition));

  // Unregister as an observer, since this class instance might be destroyed
  // after this callback.  Note: It's important to unregister *after* posting
  // the task above.  Unregistering as an observer can have the side-effect of
  // modifying the value of |geoposition|.
  bool removed =
      content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback(
          geolocation_callback_);
  DCHECK(removed);

  delete this;
}

// Asynchronously loads the user's current geoposition and calls |callback_| on
// the UI thread with the loaded geoposition, once it is available. Expected to
// be called on the IO thread.
void LoadGeoposition(
    const base::TimeDelta& timeout,
    const base::Callback<void(const content::Geoposition&)>& callback) {
  // The loader is responsible for freeing its own memory.
  new GeopositionLoader(timeout, callback);
}

// Waits for all asynchronous data required for the fingerprint to be loaded,
// then fills out the fingerprint.
class FingerprintDataLoader : public content::GpuDataManagerObserver {
 public:
  FingerprintDataLoader(
      uint64 obfuscated_gaia_id,
      const gfx::Rect& window_bounds,
      const gfx::Rect& content_bounds,
      const WebScreenInfo& screen_info,
      const std::string& version,
      const std::string& charset,
      const std::string& accept_languages,
      const base::Time& install_time,
      const std::string& app_locale,
      const base::TimeDelta& timeout,
      const base::Callback<void(scoped_ptr<Fingerprint>)>& callback);

 private:
  virtual ~FingerprintDataLoader() {}

  // content::GpuDataManagerObserver:
  virtual void OnGpuInfoUpdate() OVERRIDE;

  // Callbacks for asynchronously loaded data.
  void OnGotFonts(scoped_ptr<base::ListValue> fonts);
  void OnGotPlugins(const std::vector<content::WebPluginInfo>& plugins);
  void OnGotGeoposition(const content::Geoposition& geoposition);

  // If all of the asynchronous data has been loaded, calls |callback_| with
  // the fingerprint data.
  void MaybeFillFingerprint();

  // Calls |callback_| with the fingerprint data.
  void FillFingerprint();

  // The GPU data provider.
  // Weak reference because the GpuDataManager class is a singleton.
  content::GpuDataManager* const gpu_data_manager_;

  // Ensures that any observer registrations for the GPU data are cleaned up by
  // the time this object is destroyed.
  ScopedObserver<content::GpuDataManager, FingerprintDataLoader> gpu_observer_;

  // Data that will be passed on to the next loading phase.  See the comment for
  // GetFingerprint() for a description of these variables.
  const uint64 obfuscated_gaia_id_;
  const gfx::Rect window_bounds_;
  const gfx::Rect content_bounds_;
  const WebScreenInfo screen_info_;
  const std::string version_;
  const std::string charset_;
  const std::string accept_languages_;
  const base::Time install_time_;

  // Data that will be loaded asynchronously.
  scoped_ptr<base::ListValue> fonts_;
  std::vector<content::WebPluginInfo> plugins_;
  bool waiting_on_plugins_;
  content::Geoposition geoposition_;

  // Timer to enforce a maximum timeout before the |callback_| is called, even
  // if not all asynchronous data has been loaded.
  base::OneShotTimer<FingerprintDataLoader> timeout_timer_;

  // For invalidating asynchronous callbacks that might arrive after |this|
  // instance is destroyed.
  base::WeakPtrFactory<FingerprintDataLoader> weak_ptr_factory_;

  // The current application locale.
  std::string app_locale_;

  // The callback that will be called once all the data is available.
  base::Callback<void(scoped_ptr<Fingerprint>)> callback_;

  DISALLOW_COPY_AND_ASSIGN(FingerprintDataLoader);
};

FingerprintDataLoader::FingerprintDataLoader(
    uint64 obfuscated_gaia_id,
    const gfx::Rect& window_bounds,
    const gfx::Rect& content_bounds,
    const WebScreenInfo& screen_info,
    const std::string& version,
    const std::string& charset,
    const std::string& accept_languages,
    const base::Time& install_time,
    const std::string& app_locale,
    const base::TimeDelta& timeout,
    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback)
    : gpu_data_manager_(content::GpuDataManager::GetInstance()),
      gpu_observer_(this),
      obfuscated_gaia_id_(obfuscated_gaia_id),
      window_bounds_(window_bounds),
      content_bounds_(content_bounds),
      screen_info_(screen_info),
      version_(version),
      charset_(charset),
      accept_languages_(accept_languages),
      install_time_(install_time),
      waiting_on_plugins_(true),
      weak_ptr_factory_(this),
      callback_(callback) {
  DCHECK(!install_time_.is_null());

  timeout_timer_.Start(FROM_HERE, timeout,
                       base::Bind(&FingerprintDataLoader::MaybeFillFingerprint,
                                  weak_ptr_factory_.GetWeakPtr()));

  // Load GPU data if needed.
  if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) {
    gpu_observer_.Add(gpu_data_manager_);
    gpu_data_manager_->RequestCompleteGpuInfoIfNeeded();
  }

#if defined(ENABLE_PLUGINS)
  // Load plugin data.
  content::PluginService::GetInstance()->GetPlugins(
      base::Bind(&FingerprintDataLoader::OnGotPlugins,
                 weak_ptr_factory_.GetWeakPtr()));
#else
  waiting_on_plugins_ = false;
#endif

  // Load font data.
  content::GetFontListAsync(
      base::Bind(&FingerprintDataLoader::OnGotFonts,
                 weak_ptr_factory_.GetWeakPtr()));

  // Load geolocation data.
  content::BrowserThread::PostTask(
      content::BrowserThread::IO, FROM_HERE,
      base::Bind(&LoadGeoposition,
                 timeout,
                 base::Bind(&FingerprintDataLoader::OnGotGeoposition,
                            weak_ptr_factory_.GetWeakPtr())));
}

void FingerprintDataLoader::OnGpuInfoUpdate() {
  if (!gpu_data_manager_->IsCompleteGpuInfoAvailable())
    return;

  gpu_observer_.Remove(gpu_data_manager_);
  MaybeFillFingerprint();
}

void FingerprintDataLoader::OnGotFonts(scoped_ptr<base::ListValue> fonts) {
  DCHECK(!fonts_);
  fonts_.reset(fonts.release());
  MaybeFillFingerprint();
}

void FingerprintDataLoader::OnGotPlugins(
    const std::vector<content::WebPluginInfo>& plugins) {
  DCHECK(waiting_on_plugins_);
  waiting_on_plugins_ = false;
  plugins_ = plugins;
  MaybeFillFingerprint();
}

void FingerprintDataLoader::OnGotGeoposition(
    const content::Geoposition& geoposition) {
  DCHECK(!geoposition_.Validate());

  geoposition_ = geoposition;
  DCHECK(geoposition_.Validate() ||
         geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE);

  MaybeFillFingerprint();
}

void FingerprintDataLoader::MaybeFillFingerprint() {
  // If all of the data has been loaded, or if the |timeout_timer_| has expired,
  // fill the fingerprint and clean up.
  if (!timeout_timer_.IsRunning() ||
      (gpu_data_manager_->IsCompleteGpuInfoAvailable() &&
       fonts_ &&
       !waiting_on_plugins_ &&
       (geoposition_.Validate() ||
        geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE))) {
    FillFingerprint();
    delete this;
  }
}

void FingerprintDataLoader::FillFingerprint() {
  scoped_ptr<Fingerprint> fingerprint(new Fingerprint);
  Fingerprint::MachineCharacteristics* machine =
      fingerprint->mutable_machine_characteristics();

  machine->set_operating_system_build(GetOperatingSystemVersion());
  // We use the delta between the install time and the Unix epoch, in hours.
  machine->set_browser_install_time_hours(
      (install_time_ - base::Time::UnixEpoch()).InHours());
  machine->set_utc_offset_ms(GetTimezoneOffset().InMilliseconds());
  machine->set_browser_language(app_locale_);
  machine->set_charset(charset_);
  machine->set_user_agent(content::GetUserAgent(GURL()));
  machine->set_ram(base::SysInfo::AmountOfPhysicalMemory());
  machine->set_browser_build(version_);
  machine->set_browser_feature(
      Fingerprint::MachineCharacteristics::FEATURE_REQUEST_AUTOCOMPLETE);
  if (fonts_)
    AddFontsToFingerprint(*fonts_, machine);
  AddPluginsToFingerprint(plugins_, machine);
  AddAcceptLanguagesToFingerprint(accept_languages_, machine);
  AddScreenInfoToFingerprint(screen_info_, machine);
  AddCpuInfoToFingerprint(machine);
  AddGpuInfoToFingerprint(machine);

  // TODO(isherman): Record the user_and_device_name_hash.
  // TODO(isherman): Record the partition size of the hard drives?

  Fingerprint::TransientState* transient_state =
      fingerprint->mutable_transient_state();
  Fingerprint::Dimension* inner_window_size =
      transient_state->mutable_inner_window_size();
  inner_window_size->set_width(content_bounds_.width());
  inner_window_size->set_height(content_bounds_.height());
  Fingerprint::Dimension* outer_window_size =
      transient_state->mutable_outer_window_size();
  outer_window_size->set_width(window_bounds_.width());
  outer_window_size->set_height(window_bounds_.height());

  // TODO(isherman): Record network performance data, which is theoretically
  // available to JS.

  // TODO(isherman): Record more user behavior data.
  if (geoposition_.Validate() &&
      geoposition_.error_code == content::Geoposition::ERROR_CODE_NONE) {
    Fingerprint::UserCharacteristics::Location* location =
        fingerprint->mutable_user_characteristics()->mutable_location();
    location->set_altitude(geoposition_.altitude);
    location->set_latitude(geoposition_.latitude);
    location->set_longitude(geoposition_.longitude);
    location->set_accuracy(geoposition_.accuracy);
    location->set_time_in_ms(
        (geoposition_.timestamp - base::Time::UnixEpoch()).InMilliseconds());
  }

  Fingerprint::Metadata* metadata = fingerprint->mutable_metadata();
  metadata->set_timestamp_ms(
      (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds());
  metadata->set_obfuscated_gaia_id(obfuscated_gaia_id_);
  metadata->set_fingerprinter_version(kFingerprinterVersion);

  callback_.Run(fingerprint.Pass());
}

}  // namespace

namespace internal {

void GetFingerprintInternal(
    uint64 obfuscated_gaia_id,
    const gfx::Rect& window_bounds,
    const gfx::Rect& content_bounds,
    const blink::WebScreenInfo& screen_info,
    const std::string& version,
    const std::string& charset,
    const std::string& accept_languages,
    const base::Time& install_time,
    const std::string& app_locale,
    const base::TimeDelta& timeout,
    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) {
  // Begin loading all of the data that we need to load asynchronously.
  // This class is responsible for freeing its own memory.
  new FingerprintDataLoader(obfuscated_gaia_id, window_bounds, content_bounds,
                            screen_info, version, charset, accept_languages,
                            install_time, app_locale, timeout, callback);
}

}  // namespace internal

void GetFingerprint(
    uint64 obfuscated_gaia_id,
    const gfx::Rect& window_bounds,
    const content::WebContents& web_contents,
    const std::string& version,
    const std::string& charset,
    const std::string& accept_languages,
    const base::Time& install_time,
    const std::string& app_locale,
    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) {
  gfx::Rect content_bounds;
  web_contents.GetView()->GetContainerBounds(&content_bounds);

  blink::WebScreenInfo screen_info;
  content::RenderWidgetHostView* host_view =
      web_contents.GetRenderWidgetHostView();
  if (host_view)
    host_view->GetRenderWidgetHost()->GetWebScreenInfo(&screen_info);

  internal::GetFingerprintInternal(
      obfuscated_gaia_id, window_bounds, content_bounds, screen_info, version,
      charset, accept_languages, install_time, app_locale,
      base::TimeDelta::FromSeconds(kTimeoutSeconds), callback);
}

}  // namespace risk
}  // namespace autofill