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// Implementation of the geolocation content settings map. Styled on
6// HostContentSettingsMap however unlike that class, this one does not hold
7// an additional in-memory copy of the settings as it does not need to support
8// thread safe synchronous access to the settings; all geolocation permissions
9// are read and written in the UI thread. (If in future this is no longer the
10// case, refer to http://codereview.chromium.org/1525018 for a previous version
11// with caching. Note that as we must observe the prefs store for settings
12// changes, e.g. coming from the sync engine, the simplest design would be to
13// always write-through changes straight to the prefs store, and rely on the
14// notification observer to subsequently update any cached copy).
15
16#include "chrome/browser/geolocation/geolocation_content_settings_map.h"
17
18#include <string>
19
20#include "base/string_piece.h"
21#include "base/utf_string_conversions.h"
22#include "chrome/browser/content_settings/content_settings_details.h"
23#include "chrome/browser/content_settings/content_settings_pattern.h"
24#include "chrome/browser/prefs/pref_service.h"
25#include "chrome/browser/prefs/scoped_user_pref_update.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/common/pref_names.h"
28#include "chrome/common/url_constants.h"
29#include "content/browser/browser_thread.h"
30#include "content/common/notification_service.h"
31#include "content/common/notification_source.h"
32#include "content/common/notification_type.h"
33#include "net/base/dns_util.h"
34#include "net/base/static_cookie_policy.h"
35
36// static
37const ContentSetting
38    GeolocationContentSettingsMap::kDefaultSetting = CONTENT_SETTING_ASK;
39
40GeolocationContentSettingsMap::GeolocationContentSettingsMap(Profile* profile)
41    : profile_(profile) {
42  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
43  prefs_registrar_.Init(profile_->GetPrefs());
44  prefs_registrar_.Add(prefs::kGeolocationDefaultContentSetting, this);
45  prefs_registrar_.Add(prefs::kGeolocationContentSettings, this);
46  notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
47                              Source<Profile>(profile_));
48}
49
50// static
51void GeolocationContentSettingsMap::RegisterUserPrefs(PrefService* prefs) {
52  prefs->RegisterIntegerPref(prefs::kGeolocationDefaultContentSetting,
53                             CONTENT_SETTING_ASK);
54  prefs->RegisterDictionaryPref(prefs::kGeolocationContentSettings);
55}
56
57ContentSetting GeolocationContentSettingsMap::GetDefaultContentSetting() const {
58  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
59  // If the profile is destroyed (and set to NULL) return CONTENT_SETTING_BLOCK.
60  if (!profile_)
61    return CONTENT_SETTING_BLOCK;
62  const PrefService* prefs = profile_->GetPrefs();
63  const ContentSetting default_content_setting = IntToContentSetting(
64      prefs->GetInteger(prefs::kGeolocationDefaultContentSetting));
65  return default_content_setting == CONTENT_SETTING_DEFAULT ?
66         kDefaultSetting : default_content_setting;
67}
68
69bool GeolocationContentSettingsMap::IsDefaultContentSettingManaged() const {
70  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
71  // If the profile is destroyed (and set to NULL) return true.
72  if (!profile_)
73    return true;
74  return profile_->GetPrefs()->IsManagedPreference(
75      prefs::kGeolocationDefaultContentSetting);
76}
77
78ContentSetting GeolocationContentSettingsMap::GetContentSetting(
79    const GURL& requesting_url,
80    const GURL& embedding_url) const {
81  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
82  DCHECK(requesting_url.is_valid() && embedding_url.is_valid());
83  GURL requesting_origin(requesting_url.GetOrigin());
84  GURL embedding_origin(embedding_url.GetOrigin());
85  DCHECK(requesting_origin.is_valid() && embedding_origin.is_valid());
86  // If the profile is destroyed (and set to NULL) return CONTENT_SETTING_BLOCK.
87  if (!profile_)
88    return CONTENT_SETTING_BLOCK;
89  const DictionaryValue* all_settings_dictionary =
90      profile_->GetPrefs()->GetDictionary(prefs::kGeolocationContentSettings);
91  // Careful: The returned value could be NULL if the pref has never been set.
92  if (all_settings_dictionary != NULL) {
93    DictionaryValue* requesting_origin_settings;
94    if (all_settings_dictionary->GetDictionaryWithoutPathExpansion(
95        requesting_origin.spec(), &requesting_origin_settings)) {
96      int setting;
97      if (requesting_origin_settings->GetIntegerWithoutPathExpansion(
98          embedding_origin.spec(), &setting))
99        return IntToContentSetting(setting);
100      // Check for any-embedder setting
101      if (requesting_origin != embedding_origin &&
102          requesting_origin_settings->GetIntegerWithoutPathExpansion(
103          "", &setting))
104        return IntToContentSetting(setting);
105    }
106  }
107  return GetDefaultContentSetting();
108}
109
110GeolocationContentSettingsMap::AllOriginsSettings
111    GeolocationContentSettingsMap::GetAllOriginsSettings() const {
112  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113  AllOriginsSettings content_settings;
114  const DictionaryValue* all_settings_dictionary =
115      profile_->GetPrefs()->GetDictionary(prefs::kGeolocationContentSettings);
116  // Careful: The returned value could be NULL if the pref has never been set.
117  if (all_settings_dictionary != NULL) {
118    for (DictionaryValue::key_iterator i(all_settings_dictionary->begin_keys());
119         i != all_settings_dictionary->end_keys(); ++i) {
120      const std::string& origin(*i);
121      GURL origin_as_url(origin);
122      if (!origin_as_url.is_valid())
123        continue;
124      DictionaryValue* requesting_origin_settings_dictionary = NULL;
125      bool found = all_settings_dictionary->GetDictionaryWithoutPathExpansion(
126          origin, &requesting_origin_settings_dictionary);
127      DCHECK(found);
128      if (!requesting_origin_settings_dictionary)
129        continue;
130      GetOneOriginSettingsFromDictionary(
131          requesting_origin_settings_dictionary,
132          &content_settings[origin_as_url]);
133    }
134  }
135  return content_settings;
136}
137
138void GeolocationContentSettingsMap::SetDefaultContentSetting(
139    ContentSetting setting) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141  if (!profile_)
142    return;
143  profile_->GetPrefs()->SetInteger(prefs::kGeolocationDefaultContentSetting,
144                                   setting == CONTENT_SETTING_DEFAULT ?
145                                       kDefaultSetting : setting);
146}
147
148void GeolocationContentSettingsMap::SetContentSetting(
149    const GURL& requesting_url,
150    const GURL& embedding_url,
151    ContentSetting setting) {
152  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
153  DCHECK(requesting_url.is_valid());
154  DCHECK(embedding_url.is_valid() || embedding_url.is_empty());
155  GURL requesting_origin(requesting_url.GetOrigin());
156  GURL embedding_origin(embedding_url.GetOrigin());
157  DCHECK(requesting_origin.is_valid());
158  DCHECK(embedding_origin.is_valid() || embedding_url.is_empty());
159  if (!profile_)
160    return;
161  PrefService* prefs = profile_->GetPrefs();
162
163  DictionaryPrefUpdate update(prefs, prefs::kGeolocationContentSettings);
164  DictionaryValue* all_settings_dictionary = update.Get();
165  DictionaryValue* requesting_origin_settings_dictionary = NULL;
166  all_settings_dictionary->GetDictionaryWithoutPathExpansion(
167      requesting_origin.spec(), &requesting_origin_settings_dictionary);
168  if (setting == CONTENT_SETTING_DEFAULT) {
169    if (requesting_origin_settings_dictionary) {
170      requesting_origin_settings_dictionary->RemoveWithoutPathExpansion(
171          embedding_origin.spec(), NULL);
172      if (requesting_origin_settings_dictionary->empty())
173        all_settings_dictionary->RemoveWithoutPathExpansion(
174            requesting_origin.spec(), NULL);
175    }
176  } else {
177    if (!requesting_origin_settings_dictionary) {
178      requesting_origin_settings_dictionary = new DictionaryValue;
179      all_settings_dictionary->SetWithoutPathExpansion(
180          requesting_origin.spec(), requesting_origin_settings_dictionary);
181    }
182    DCHECK(requesting_origin_settings_dictionary);
183    requesting_origin_settings_dictionary->SetWithoutPathExpansion(
184        embedding_origin.spec(), Value::CreateIntegerValue(setting));
185  }
186}
187
188void GeolocationContentSettingsMap::ResetToDefault() {
189  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190  if (!profile_)
191    return;
192  PrefService* prefs = profile_->GetPrefs();
193  prefs->ClearPref(prefs::kGeolocationDefaultContentSetting);
194  prefs->ClearPref(prefs::kGeolocationContentSettings);
195}
196
197void GeolocationContentSettingsMap::NotifyObservers(
198    const ContentSettingsDetails& details) {
199  NotificationService::current()->Notify(
200      NotificationType::GEOLOCATION_SETTINGS_CHANGED,
201      Source<GeolocationContentSettingsMap>(this),
202      Details<const ContentSettingsDetails>(&details));
203}
204
205void GeolocationContentSettingsMap::Observe(
206    NotificationType type,
207    const NotificationSource& source,
208    const NotificationDetails& details) {
209  if (type == NotificationType::PREF_CHANGED) {
210    const std::string& name = *Details<std::string>(details).ptr();
211    if (name == prefs::kGeolocationDefaultContentSetting) {
212      NotifyObservers(ContentSettingsDetails(
213      ContentSettingsPattern(),
214      CONTENT_SETTINGS_TYPE_DEFAULT,
215      ""));
216    }
217  } else if (NotificationType::PROFILE_DESTROYED == type) {
218    UnregisterObservers();
219  } else {
220    NOTREACHED();
221  }
222}
223
224void GeolocationContentSettingsMap::UnregisterObservers() {
225  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226  if (!profile_)
227    return;
228  prefs_registrar_.RemoveAll();
229  notification_registrar_.Remove(this, NotificationType::PROFILE_DESTROYED,
230                                 Source<Profile>(profile_));
231  profile_ = NULL;
232}
233
234GeolocationContentSettingsMap::~GeolocationContentSettingsMap() {
235  UnregisterObservers();
236}
237
238// static
239void GeolocationContentSettingsMap::GetOneOriginSettingsFromDictionary(
240    const DictionaryValue* dictionary,
241    OneOriginSettings* one_origin_settings) {
242  for (DictionaryValue::key_iterator i(dictionary->begin_keys());
243       i != dictionary->end_keys(); ++i) {
244    const std::string& target(*i);
245    int setting = kDefaultSetting;
246    bool found = dictionary->GetIntegerWithoutPathExpansion(target, &setting);
247    DCHECK(found);
248    GURL target_url(target);
249    // An empty URL has a special meaning (wildcard), so only accept invalid
250    // URLs if the original version was empty (avoids treating corrupted prefs
251    // as the wildcard entry; see http://crbug.com/39685)
252    if (target_url.is_valid() || target.empty())
253      (*one_origin_settings)[target_url] = IntToContentSetting(setting);
254  }
255}
256