// Copyright (c) 2011 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. #include "chrome/browser/content_settings/content_settings_notification_provider.h" #include "base/string_util.h" #include "chrome/browser/notifications/desktop_notification_service_factory.h" #include "chrome/browser/notifications/notification.h" #include "chrome/browser/notifications/notifications_prefs_cache.h" #include "chrome/browser/notifications/notification_ui_manager.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/content_settings_types.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" #include "googleurl/src/gurl.h" namespace { const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK; } // namespace namespace content_settings { // //////////////////////////////////////////////////////////////////////////// // NotificationProvider // // static void NotificationProvider::RegisterUserPrefs(PrefService* user_prefs) { if (!user_prefs->FindPreference(prefs::kDesktopNotificationAllowedOrigins)) user_prefs->RegisterListPref(prefs::kDesktopNotificationAllowedOrigins); if (!user_prefs->FindPreference(prefs::kDesktopNotificationDeniedOrigins)) user_prefs->RegisterListPref(prefs::kDesktopNotificationDeniedOrigins); } // TODO(markusheintz): Re-factoring in progress. Do not move or touch the // following two static methods as you might cause trouble. Thanks! // static ContentSettingsPattern NotificationProvider::ToContentSettingsPattern( const GURL& origin) { // Fix empty GURLs. if (origin.spec().empty()) { std::string pattern_spec(chrome::kFileScheme); pattern_spec += chrome::kStandardSchemeSeparator; return ContentSettingsPattern(pattern_spec); } return ContentSettingsPattern::FromURLNoWildcard(origin); } // static GURL NotificationProvider::ToGURL(const ContentSettingsPattern& pattern) { std::string pattern_spec(pattern.AsString()); if (pattern_spec.empty() || StartsWithASCII(pattern_spec, std::string(ContentSettingsPattern::kDomainWildcard), true)) { NOTREACHED(); } std::string url_spec(""); if (StartsWithASCII(pattern_spec, std::string(chrome::kFileScheme), false)) { url_spec += pattern_spec; } else if (!pattern.scheme().empty()) { url_spec += pattern.scheme(); url_spec += chrome::kStandardSchemeSeparator; url_spec += pattern_spec; } return GURL(url_spec); } NotificationProvider::NotificationProvider( Profile* profile) : profile_(profile) { prefs_registrar_.Init(profile_->GetPrefs()); StartObserving(); } NotificationProvider::~NotificationProvider() { StopObserving(); } bool NotificationProvider::ContentSettingsTypeIsManaged( ContentSettingsType content_type) { return false; } ContentSetting NotificationProvider::GetContentSetting( const GURL& requesting_url, const GURL& embedding_url, ContentSettingsType content_type, const ResourceIdentifier& resource_identifier) const { if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS) return CONTENT_SETTING_DEFAULT; return GetContentSetting(requesting_url); } void NotificationProvider::SetContentSetting( const ContentSettingsPattern& requesting_url_pattern, const ContentSettingsPattern& embedding_url_pattern, ContentSettingsType content_type, const ResourceIdentifier& resource_identifier, ContentSetting content_setting) { if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS) return; GURL origin = ToGURL(requesting_url_pattern); if (CONTENT_SETTING_ALLOW == content_setting) { GrantPermission(origin); } else if (CONTENT_SETTING_BLOCK == content_setting) { DenyPermission(origin); } else if (CONTENT_SETTING_DEFAULT == content_setting) { ContentSetting current_setting = GetContentSetting(origin); if (CONTENT_SETTING_ALLOW == current_setting) { ResetAllowedOrigin(origin); } else if (CONTENT_SETTING_BLOCK == current_setting) { ResetBlockedOrigin(origin); } else { NOTREACHED(); } } else { NOTREACHED(); } } void NotificationProvider::GetAllContentSettingsRules( ContentSettingsType content_type, const ResourceIdentifier& resource_identifier, Rules* content_setting_rules) const { if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS) return; std::vector<GURL> allowed_origins = GetAllowedOrigins(); std::vector<GURL> denied_origins = GetBlockedOrigins(); for (std::vector<GURL>::iterator url = allowed_origins.begin(); url != allowed_origins.end(); ++url) { ContentSettingsPattern pattern = ContentSettingsPattern::FromURLNoWildcard(*url); content_setting_rules->push_back(Rule( pattern, pattern, CONTENT_SETTING_ALLOW)); } for (std::vector<GURL>::iterator url = denied_origins.begin(); url != denied_origins.end(); ++url) { ContentSettingsPattern pattern = ContentSettingsPattern::FromURLNoWildcard(*url); content_setting_rules->push_back(Rule( pattern, pattern, CONTENT_SETTING_BLOCK)); } } void NotificationProvider::ClearAllContentSettingsRules( ContentSettingsType content_type) { if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) ResetAllOrigins(); } void NotificationProvider::ResetToDefaults() { ResetAllOrigins(); } void NotificationProvider::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (NotificationType::PREF_CHANGED == type) { const std::string& name = *Details<std::string>(details).ptr(); OnPrefsChanged(name); } else if (NotificationType::PROFILE_DESTROYED == type) { StopObserving(); } } ///////////////////////////////////////////////////////////////////// // Private // void NotificationProvider::StartObserving() { if (!profile_->IsOffTheRecord()) { prefs_registrar_.Add(prefs::kDesktopNotificationDefaultContentSetting, this); prefs_registrar_.Add(prefs::kDesktopNotificationAllowedOrigins, this); prefs_registrar_.Add(prefs::kDesktopNotificationDeniedOrigins, this); notification_registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, NotificationService::AllSources()); } notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED, Source<Profile>(profile_)); } void NotificationProvider::StopObserving() { if (!profile_->IsOffTheRecord()) { prefs_registrar_.RemoveAll(); } notification_registrar_.RemoveAll(); } void NotificationProvider::OnPrefsChanged(const std::string& pref_name) { if (pref_name == prefs::kDesktopNotificationAllowedOrigins) { NotifySettingsChange(); } else if (pref_name == prefs::kDesktopNotificationDeniedOrigins) { NotifySettingsChange(); } } void NotificationProvider::NotifySettingsChange() { // TODO(markusheintz): Re-factoring work in progress: Replace the // DESKTOP_NOTIFICATION_SETTINGS_CHANGED with a CONTENT_SETTINGS_CHANGED // notification, and use the HostContentSettingsMap as source once this // content settings provider in integrated in the HostContentSetttingsMap. NotificationService::current()->Notify( NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED, Source<DesktopNotificationService>( DesktopNotificationServiceFactory::GetForProfile(profile_)), NotificationService::NoDetails()); } std::vector<GURL> NotificationProvider::GetAllowedOrigins() const { std::vector<GURL> allowed_origins; PrefService* prefs = profile_->GetPrefs(); const ListValue* allowed_sites = prefs->GetList(prefs::kDesktopNotificationAllowedOrigins); if (allowed_sites) { // TODO(markusheintz): Remove dependency to PrefsCache NotificationsPrefsCache::ListValueToGurlVector(*allowed_sites, &allowed_origins); } return allowed_origins; } std::vector<GURL> NotificationProvider::GetBlockedOrigins() const { std::vector<GURL> denied_origins; PrefService* prefs = profile_->GetPrefs(); const ListValue* denied_sites = prefs->GetList(prefs::kDesktopNotificationDeniedOrigins); if (denied_sites) { // TODO(markusheintz): Remove dependency to PrefsCache NotificationsPrefsCache::ListValueToGurlVector(*denied_sites, &denied_origins); } return denied_origins; } void NotificationProvider::GrantPermission(const GURL& origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); PersistPermissionChange(origin, true); NotifySettingsChange(); } void NotificationProvider::DenyPermission(const GURL& origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); PersistPermissionChange(origin, false); NotifySettingsChange(); } void NotificationProvider::PersistPermissionChange( const GURL& origin, bool is_allowed) { // Don't persist changes when incognito. if (profile_->IsOffTheRecord()) return; PrefService* prefs = profile_->GetPrefs(); // |Observe()| updates the whole permission set in the cache, but only a // single origin has changed. Hence, callers of this method manually // schedule a task to update the prefs cache, and the prefs observer is // disabled while the update runs. StopObserving(); bool allowed_changed = false; bool denied_changed = false; { ListPrefUpdate update_allowed_sites( prefs, prefs::kDesktopNotificationAllowedOrigins); ListPrefUpdate update_denied_sites( prefs, prefs::kDesktopNotificationDeniedOrigins); ListValue* allowed_sites = update_allowed_sites.Get(); ListValue* denied_sites = update_denied_sites.Get(); // |value| is passed to the preferences list, or deleted. StringValue* value = new StringValue(origin.spec()); // Remove from one list and add to the other. if (is_allowed) { // Remove from the denied list. if (denied_sites->Remove(*value) != -1) denied_changed = true; // Add to the allowed list. if (allowed_sites->AppendIfNotPresent(value)) allowed_changed = true; } else { // Remove from the allowed list. if (allowed_sites->Remove(*value) != -1) allowed_changed = true; // Add to the denied list. if (denied_sites->AppendIfNotPresent(value)) denied_changed = true; } } // Persist the pref if anthing changed, but only send updates for the // list that changed. if (allowed_changed || denied_changed) prefs->ScheduleSavePersistentPrefs(); StartObserving(); } ContentSetting NotificationProvider::GetContentSetting( const GURL& origin) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (profile_->IsOffTheRecord()) return kDefaultSetting; std::vector<GURL> allowed_origins(GetAllowedOrigins()); if (std::find(allowed_origins.begin(), allowed_origins.end(), origin) != allowed_origins.end()) return CONTENT_SETTING_ALLOW; std::vector<GURL> denied_origins(GetBlockedOrigins()); if (std::find(denied_origins.begin(), denied_origins.end(), origin) != denied_origins.end()) return CONTENT_SETTING_BLOCK; return CONTENT_SETTING_DEFAULT; } void NotificationProvider::ResetAllowedOrigin(const GURL& origin) { if (profile_->IsOffTheRecord()) return; // Since this isn't called often, let the normal observer behavior update the // cache in this case. PrefService* prefs = profile_->GetPrefs(); { ListPrefUpdate update(prefs, prefs::kDesktopNotificationAllowedOrigins); ListValue* allowed_sites = update.Get(); StringValue value(origin.spec()); int removed_index = allowed_sites->Remove(value); DCHECK_NE(-1, removed_index) << origin << " was not allowed"; } prefs->ScheduleSavePersistentPrefs(); } void NotificationProvider::ResetBlockedOrigin(const GURL& origin) { if (profile_->IsOffTheRecord()) return; // Since this isn't called often, let the normal observer behavior update the // cache in this case. PrefService* prefs = profile_->GetPrefs(); { ListPrefUpdate update(prefs, prefs::kDesktopNotificationDeniedOrigins); ListValue* denied_sites = update.Get(); StringValue value(origin.spec()); int removed_index = denied_sites->Remove(value); DCHECK_NE(-1, removed_index) << origin << " was not blocked"; } prefs->ScheduleSavePersistentPrefs(); } void NotificationProvider::ResetAllOrigins() { PrefService* prefs = profile_->GetPrefs(); prefs->ClearPref(prefs::kDesktopNotificationAllowedOrigins); prefs->ClearPref(prefs::kDesktopNotificationDeniedOrigins); } } // namespace content_settings