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
6#include "chrome/browser/content_settings/content_settings_notification_provider.h"
7
8#include "base/string_util.h"
9#include "chrome/browser/notifications/desktop_notification_service_factory.h"
10#include "chrome/browser/notifications/notification.h"
11#include "chrome/browser/notifications/notifications_prefs_cache.h"
12#include "chrome/browser/notifications/notification_ui_manager.h"
13#include "chrome/browser/prefs/pref_service.h"
14#include "chrome/browser/prefs/scoped_user_pref_update.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/common/content_settings_types.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/common/url_constants.h"
19#include "content/common/notification_service.h"
20#include "content/common/notification_type.h"
21#include "googleurl/src/gurl.h"
22
23namespace {
24
25const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK;
26
27}  // namespace
28
29namespace content_settings {
30
31// ////////////////////////////////////////////////////////////////////////////
32// NotificationProvider
33//
34
35// static
36void NotificationProvider::RegisterUserPrefs(PrefService* user_prefs) {
37  if (!user_prefs->FindPreference(prefs::kDesktopNotificationAllowedOrigins))
38    user_prefs->RegisterListPref(prefs::kDesktopNotificationAllowedOrigins);
39  if (!user_prefs->FindPreference(prefs::kDesktopNotificationDeniedOrigins))
40    user_prefs->RegisterListPref(prefs::kDesktopNotificationDeniedOrigins);
41}
42
43// TODO(markusheintz): Re-factoring in progress. Do not move or touch the
44// following two static methods as you might cause trouble. Thanks!
45
46// static
47ContentSettingsPattern NotificationProvider::ToContentSettingsPattern(
48    const GURL& origin) {
49  // Fix empty GURLs.
50  if (origin.spec().empty()) {
51    std::string pattern_spec(chrome::kFileScheme);
52    pattern_spec += chrome::kStandardSchemeSeparator;
53    return ContentSettingsPattern(pattern_spec);
54  }
55  return ContentSettingsPattern::FromURLNoWildcard(origin);
56}
57
58// static
59GURL NotificationProvider::ToGURL(const ContentSettingsPattern& pattern) {
60  std::string pattern_spec(pattern.AsString());
61
62  if (pattern_spec.empty() ||
63      StartsWithASCII(pattern_spec,
64                      std::string(ContentSettingsPattern::kDomainWildcard),
65                      true)) {
66    NOTREACHED();
67  }
68
69  std::string url_spec("");
70  if (StartsWithASCII(pattern_spec, std::string(chrome::kFileScheme), false)) {
71    url_spec += pattern_spec;
72  } else if (!pattern.scheme().empty()) {
73    url_spec += pattern.scheme();
74    url_spec += chrome::kStandardSchemeSeparator;
75    url_spec += pattern_spec;
76  }
77
78  return GURL(url_spec);
79}
80
81NotificationProvider::NotificationProvider(
82    Profile* profile)
83    : profile_(profile) {
84  prefs_registrar_.Init(profile_->GetPrefs());
85  StartObserving();
86}
87
88NotificationProvider::~NotificationProvider() {
89  StopObserving();
90}
91
92bool NotificationProvider::ContentSettingsTypeIsManaged(
93      ContentSettingsType content_type) {
94  return false;
95}
96
97ContentSetting NotificationProvider::GetContentSetting(
98      const GURL& requesting_url,
99      const GURL& embedding_url,
100      ContentSettingsType content_type,
101      const ResourceIdentifier& resource_identifier) const {
102  if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
103    return CONTENT_SETTING_DEFAULT;
104
105  return GetContentSetting(requesting_url);
106}
107
108void NotificationProvider::SetContentSetting(
109      const ContentSettingsPattern& requesting_url_pattern,
110      const ContentSettingsPattern& embedding_url_pattern,
111      ContentSettingsType content_type,
112      const ResourceIdentifier& resource_identifier,
113      ContentSetting content_setting) {
114  if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
115    return;
116
117  GURL origin = ToGURL(requesting_url_pattern);
118  if (CONTENT_SETTING_ALLOW == content_setting) {
119    GrantPermission(origin);
120  } else if (CONTENT_SETTING_BLOCK == content_setting) {
121    DenyPermission(origin);
122  } else if (CONTENT_SETTING_DEFAULT == content_setting) {
123    ContentSetting current_setting = GetContentSetting(origin);
124    if (CONTENT_SETTING_ALLOW == current_setting) {
125      ResetAllowedOrigin(origin);
126    } else if (CONTENT_SETTING_BLOCK == current_setting) {
127      ResetBlockedOrigin(origin);
128    } else {
129      NOTREACHED();
130    }
131  } else {
132      NOTREACHED();
133  }
134}
135
136void NotificationProvider::GetAllContentSettingsRules(
137      ContentSettingsType content_type,
138      const ResourceIdentifier& resource_identifier,
139      Rules* content_setting_rules) const {
140  if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
141    return;
142
143  std::vector<GURL> allowed_origins = GetAllowedOrigins();
144  std::vector<GURL> denied_origins = GetBlockedOrigins();
145
146  for (std::vector<GURL>::iterator url = allowed_origins.begin();
147       url != allowed_origins.end();
148       ++url) {
149    ContentSettingsPattern pattern =
150        ContentSettingsPattern::FromURLNoWildcard(*url);
151    content_setting_rules->push_back(Rule(
152        pattern,
153        pattern,
154        CONTENT_SETTING_ALLOW));
155  }
156  for (std::vector<GURL>::iterator url = denied_origins.begin();
157       url != denied_origins.end();
158       ++url) {
159    ContentSettingsPattern pattern =
160        ContentSettingsPattern::FromURLNoWildcard(*url);
161    content_setting_rules->push_back(Rule(
162        pattern,
163        pattern,
164        CONTENT_SETTING_BLOCK));
165  }
166}
167
168void NotificationProvider::ClearAllContentSettingsRules(
169      ContentSettingsType content_type) {
170  if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
171    ResetAllOrigins();
172}
173
174void NotificationProvider::ResetToDefaults() {
175  ResetAllOrigins();
176}
177
178void NotificationProvider::Observe(NotificationType type,
179                                   const NotificationSource& source,
180                                   const NotificationDetails& details) {
181  if (NotificationType::PREF_CHANGED == type) {
182    const std::string& name = *Details<std::string>(details).ptr();
183    OnPrefsChanged(name);
184  } else if (NotificationType::PROFILE_DESTROYED == type) {
185    StopObserving();
186  }
187}
188
189/////////////////////////////////////////////////////////////////////
190// Private
191//
192
193void NotificationProvider::StartObserving() {
194  if (!profile_->IsOffTheRecord()) {
195    prefs_registrar_.Add(prefs::kDesktopNotificationDefaultContentSetting,
196                         this);
197    prefs_registrar_.Add(prefs::kDesktopNotificationAllowedOrigins, this);
198    prefs_registrar_.Add(prefs::kDesktopNotificationDeniedOrigins, this);
199
200    notification_registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
201                                NotificationService::AllSources());
202  }
203
204  notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
205                              Source<Profile>(profile_));
206}
207
208void NotificationProvider::StopObserving() {
209  if (!profile_->IsOffTheRecord()) {
210    prefs_registrar_.RemoveAll();
211  }
212  notification_registrar_.RemoveAll();
213}
214
215void NotificationProvider::OnPrefsChanged(const std::string& pref_name) {
216  if (pref_name == prefs::kDesktopNotificationAllowedOrigins) {
217    NotifySettingsChange();
218  } else if (pref_name == prefs::kDesktopNotificationDeniedOrigins) {
219    NotifySettingsChange();
220  }
221}
222
223void NotificationProvider::NotifySettingsChange() {
224  // TODO(markusheintz): Re-factoring work in progress: Replace the
225  // DESKTOP_NOTIFICATION_SETTINGS_CHANGED with a CONTENT_SETTINGS_CHANGED
226  // notification, and use the HostContentSettingsMap as source once this
227  // content settings provider in integrated in the HostContentSetttingsMap.
228  NotificationService::current()->Notify(
229      NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
230      Source<DesktopNotificationService>(
231          DesktopNotificationServiceFactory::GetForProfile(profile_)),
232      NotificationService::NoDetails());
233}
234
235std::vector<GURL> NotificationProvider::GetAllowedOrigins() const {
236  std::vector<GURL> allowed_origins;
237  PrefService* prefs = profile_->GetPrefs();
238  const ListValue* allowed_sites =
239      prefs->GetList(prefs::kDesktopNotificationAllowedOrigins);
240  if (allowed_sites) {
241    // TODO(markusheintz): Remove dependency to PrefsCache
242    NotificationsPrefsCache::ListValueToGurlVector(*allowed_sites,
243                                                   &allowed_origins);
244  }
245  return allowed_origins;
246}
247
248std::vector<GURL> NotificationProvider::GetBlockedOrigins() const {
249  std::vector<GURL> denied_origins;
250  PrefService* prefs = profile_->GetPrefs();
251  const ListValue* denied_sites =
252      prefs->GetList(prefs::kDesktopNotificationDeniedOrigins);
253  if (denied_sites) {
254     // TODO(markusheintz): Remove dependency to PrefsCache
255    NotificationsPrefsCache::ListValueToGurlVector(*denied_sites,
256                                                   &denied_origins);
257  }
258  return denied_origins;
259}
260
261void NotificationProvider::GrantPermission(const GURL& origin) {
262  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
263  PersistPermissionChange(origin, true);
264  NotifySettingsChange();
265}
266
267void NotificationProvider::DenyPermission(const GURL& origin) {
268  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
269  PersistPermissionChange(origin, false);
270  NotifySettingsChange();
271}
272
273void NotificationProvider::PersistPermissionChange(
274    const GURL& origin, bool is_allowed) {
275  // Don't persist changes when incognito.
276  if (profile_->IsOffTheRecord())
277    return;
278  PrefService* prefs = profile_->GetPrefs();
279
280  // |Observe()| updates the whole permission set in the cache, but only a
281  // single origin has changed. Hence, callers of this method manually
282  // schedule a task to update the prefs cache, and the prefs observer is
283  // disabled while the update runs.
284  StopObserving();
285
286  bool allowed_changed = false;
287  bool denied_changed = false;
288
289  {
290    ListPrefUpdate update_allowed_sites(
291        prefs, prefs::kDesktopNotificationAllowedOrigins);
292    ListPrefUpdate update_denied_sites(
293        prefs, prefs::kDesktopNotificationDeniedOrigins);
294    ListValue* allowed_sites = update_allowed_sites.Get();
295    ListValue* denied_sites = update_denied_sites.Get();
296    // |value| is passed to the preferences list, or deleted.
297    StringValue* value = new StringValue(origin.spec());
298
299    // Remove from one list and add to the other.
300    if (is_allowed) {
301      // Remove from the denied list.
302      if (denied_sites->Remove(*value) != -1)
303        denied_changed = true;
304
305      // Add to the allowed list.
306      if (allowed_sites->AppendIfNotPresent(value))
307        allowed_changed = true;
308    } else {
309      // Remove from the allowed list.
310      if (allowed_sites->Remove(*value) != -1)
311        allowed_changed = true;
312
313      // Add to the denied list.
314      if (denied_sites->AppendIfNotPresent(value))
315        denied_changed = true;
316    }
317  }
318
319  // Persist the pref if anthing changed, but only send updates for the
320  // list that changed.
321  if (allowed_changed || denied_changed)
322    prefs->ScheduleSavePersistentPrefs();
323  StartObserving();
324}
325
326ContentSetting NotificationProvider::GetContentSetting(
327    const GURL& origin) const {
328  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
329
330  if (profile_->IsOffTheRecord())
331    return kDefaultSetting;
332
333  std::vector<GURL> allowed_origins(GetAllowedOrigins());
334  if (std::find(allowed_origins.begin(), allowed_origins.end(), origin) !=
335      allowed_origins.end())
336    return CONTENT_SETTING_ALLOW;
337
338  std::vector<GURL> denied_origins(GetBlockedOrigins());
339  if (std::find(denied_origins.begin(), denied_origins.end(), origin) !=
340      denied_origins.end())
341    return CONTENT_SETTING_BLOCK;
342
343  return CONTENT_SETTING_DEFAULT;
344}
345
346void NotificationProvider::ResetAllowedOrigin(const GURL& origin) {
347  if (profile_->IsOffTheRecord())
348    return;
349
350  // Since this isn't called often, let the normal observer behavior update the
351  // cache in this case.
352  PrefService* prefs = profile_->GetPrefs();
353  {
354    ListPrefUpdate update(prefs, prefs::kDesktopNotificationAllowedOrigins);
355    ListValue* allowed_sites = update.Get();
356    StringValue value(origin.spec());
357    int removed_index = allowed_sites->Remove(value);
358    DCHECK_NE(-1, removed_index) << origin << " was not allowed";
359  }
360  prefs->ScheduleSavePersistentPrefs();
361}
362
363void NotificationProvider::ResetBlockedOrigin(const GURL& origin) {
364  if (profile_->IsOffTheRecord())
365    return;
366
367  // Since this isn't called often, let the normal observer behavior update the
368  // cache in this case.
369  PrefService* prefs = profile_->GetPrefs();
370  {
371    ListPrefUpdate update(prefs, prefs::kDesktopNotificationDeniedOrigins);
372    ListValue* denied_sites = update.Get();
373    StringValue value(origin.spec());
374    int removed_index = denied_sites->Remove(value);
375    DCHECK_NE(-1, removed_index) << origin << " was not blocked";
376  }
377  prefs->ScheduleSavePersistentPrefs();
378}
379
380void NotificationProvider::ResetAllOrigins() {
381  PrefService* prefs = profile_->GetPrefs();
382  prefs->ClearPref(prefs::kDesktopNotificationAllowedOrigins);
383  prefs->ClearPref(prefs::kDesktopNotificationDeniedOrigins);
384}
385
386}  // namespace content_settings
387