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/ui/network_profile_bubble.h"
6
7#include <windows.h>
8
9#include <wtsapi32.h>
10// Make sure we link the wtsapi lib file in.
11#pragma comment(lib, "wtsapi32.lib")
12
13#include "base/bind.h"
14#include "base/command_line.h"
15#include "base/files/file_path.h"
16#include "base/files/file_util.h"
17#include "base/logging.h"
18#include "base/metrics/histogram.h"
19#include "base/prefs/pref_service.h"
20#include "base/time/time.h"
21#include "chrome/browser/browser_process.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/ui/browser_finder.h"
24#include "chrome/browser/ui/browser_list.h"
25#include "chrome/browser/ui/browser_list_observer.h"
26#include "chrome/common/chrome_switches.h"
27#include "chrome/common/pref_names.h"
28#include "components/pref_registry/pref_registry_syncable.h"
29#include "content/public/browser/browser_thread.h"
30
31namespace {
32
33// The duration of the silent period before we start nagging the user again.
34const int kSilenceDurationDays = 100;
35
36// The number of warnings to be shown on consequtive starts of Chrome before the
37// silent period starts.
38const int kMaxWarnings = 2;
39
40// Implementation of chrome::BrowserListObserver used to wait for a browser
41// window.
42class BrowserListObserver : public chrome::BrowserListObserver {
43 private:
44  virtual ~BrowserListObserver();
45
46  // Overridden from chrome::BrowserListObserver:
47  virtual void OnBrowserAdded(Browser* browser) OVERRIDE;
48  virtual void OnBrowserRemoved(Browser* browser) OVERRIDE;
49  virtual void OnBrowserSetLastActive(Browser* browser) OVERRIDE;
50};
51
52BrowserListObserver::~BrowserListObserver() {
53}
54
55void BrowserListObserver::OnBrowserAdded(Browser* browser) {
56}
57
58void BrowserListObserver::OnBrowserRemoved(Browser* browser) {
59}
60
61void BrowserListObserver::OnBrowserSetLastActive(Browser* browser) {
62  NetworkProfileBubble::ShowNotification(browser);
63  // No need to observe anymore.
64  BrowserList::RemoveObserver(this);
65  delete this;
66}
67
68// The name of the UMA histogram collecting our stats.
69const char kMetricNetworkedProfileCheck[] = "NetworkedProfile.Check";
70
71}  // namespace
72
73// static
74bool NetworkProfileBubble::notification_shown_ = false;
75
76// static
77bool NetworkProfileBubble::ShouldCheckNetworkProfile(Profile* profile) {
78  PrefService* prefs = profile->GetPrefs();
79  if (prefs->GetInteger(prefs::kNetworkProfileWarningsLeft))
80    return !notification_shown_;
81  int64 last_check = prefs->GetInt64(prefs::kNetworkProfileLastWarningTime);
82  base::TimeDelta time_since_last_check =
83      base::Time::Now() - base::Time::FromTimeT(last_check);
84  if (time_since_last_check.InDays() > kSilenceDurationDays) {
85    prefs->SetInteger(prefs::kNetworkProfileWarningsLeft, kMaxWarnings);
86    return !notification_shown_;
87  }
88  return false;
89}
90
91// static
92void NetworkProfileBubble::CheckNetworkProfile(
93    const base::FilePath& profile_folder) {
94  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
95  // On Windows notify the users if their profiles are located on a network
96  // share as we don't officially support this setup yet.
97  // However we don't want to bother users on Cytrix setups as those have no
98  // real choice and their admins must be well aware of the risks associated.
99  // Also the command line flag --no-network-profile-warning can stop this
100  // warning from popping up. In this case we can skip the check to make the
101  // start faster.
102  // Collect a lot of stats along the way to see which cases do occur in the
103  // wild often enough.
104  if (CommandLine::ForCurrentProcess()->HasSwitch(
105          switches::kNoNetworkProfileWarning)) {
106    RecordUmaEvent(METRIC_CHECK_SUPPRESSED);
107    return;
108  }
109
110  LPWSTR buffer = NULL;
111  DWORD buffer_length = 0;
112  // Checking for RDP is cheaper than checking for a network drive so do this
113  // one first.
114  if (!::WTSQuerySessionInformation(WTS_CURRENT_SERVER, WTS_CURRENT_SESSION,
115                                    WTSClientProtocolType,
116                                    &buffer, &buffer_length)) {
117    RecordUmaEvent(METRIC_CHECK_FAILED);
118    return;
119  }
120
121  unsigned short* type = reinterpret_cast<unsigned short*>(buffer);
122  // We should warn the users if they have their profile on a network share only
123  // if running on a local session.
124  if (*type == WTS_PROTOCOL_TYPE_CONSOLE) {
125    bool profile_on_network = false;
126    if (!profile_folder.empty()) {
127      base::FilePath temp_file;
128      // Try to create some non-empty temp file in the profile dir and use
129      // it to check if there is a reparse-point free path to it.
130      if (base::CreateTemporaryFileInDir(profile_folder, &temp_file) &&
131          (base::WriteFile(temp_file, ".", 1) == 1)) {
132        base::FilePath normalized_temp_file;
133        if (!base::NormalizeFilePath(temp_file, &normalized_temp_file))
134          profile_on_network = true;
135      } else {
136        RecordUmaEvent(METRIC_CHECK_IO_FAILED);
137      }
138      base::DeleteFile(temp_file, false);
139    }
140    if (profile_on_network) {
141      RecordUmaEvent(METRIC_PROFILE_ON_NETWORK);
142      content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
143          base::Bind(&NotifyNetworkProfileDetected));
144    } else {
145      RecordUmaEvent(METRIC_PROFILE_NOT_ON_NETWORK);
146    }
147  } else {
148    RecordUmaEvent(METRIC_REMOTE_SESSION);
149  }
150
151  ::WTSFreeMemory(buffer);
152}
153
154// static
155void NetworkProfileBubble::SetNotificationShown(bool shown) {
156  notification_shown_ = shown;
157}
158
159// static
160void NetworkProfileBubble::RegisterProfilePrefs(
161    user_prefs::PrefRegistrySyncable* registry) {
162  registry->RegisterIntegerPref(
163      prefs::kNetworkProfileWarningsLeft,
164      kMaxWarnings,
165      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
166  registry->RegisterInt64Pref(
167      prefs::kNetworkProfileLastWarningTime,
168      0,
169      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
170}
171
172// static
173void NetworkProfileBubble::RecordUmaEvent(MetricNetworkedProfileCheck event) {
174  UMA_HISTOGRAM_ENUMERATION(kMetricNetworkedProfileCheck,
175                            event,
176                            METRIC_NETWORKED_PROFILE_CHECK_SIZE);
177}
178
179// static
180void NetworkProfileBubble::NotifyNetworkProfileDetected() {
181  Browser* browser = chrome::FindLastActiveWithHostDesktopType(
182      chrome::GetActiveDesktop());
183
184  if (browser)
185    ShowNotification(browser);
186  else
187    BrowserList::AddObserver(new BrowserListObserver());
188}
189