Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: chrome/browser/content_settings/content_settings_policy_provider.cc

Issue 670953006: Componentize HostContentSettingsMap and content settings providers. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: win64 suppress warnings Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/content_settings/content_settings_policy_provider.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/json/json_reader.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/values.h"
14 #include "chrome/browser/content_settings/content_settings_utils.h"
15 #include "chrome/common/pref_names.h"
16 #include "components/content_settings/core/browser/content_settings_rule.h"
17 #include "components/content_settings/core/common/content_settings_pattern.h"
18 #include "components/pref_registry/pref_registry_syncable.h"
19
20 namespace {
21
22 // The preferences used to manage ContentSettingsTypes.
23 const char* kPrefToManageType[] = {
24 prefs::kManagedDefaultCookiesSetting,
25 prefs::kManagedDefaultImagesSetting,
26 prefs::kManagedDefaultJavaScriptSetting,
27 prefs::kManagedDefaultPluginsSetting,
28 prefs::kManagedDefaultPopupsSetting,
29 prefs::kManagedDefaultGeolocationSetting,
30 prefs::kManagedDefaultNotificationsSetting,
31 NULL, // No policy for default value of content type auto-select-certificate
32 NULL, // No policy for default value of fullscreen requests
33 NULL, // No policy for default value of mouse lock requests
34 NULL, // No policy for default value of mixed script blocking
35 prefs::kManagedDefaultMediaStreamSetting,
36 NULL, // No policy for default value of media stream mic
37 NULL, // No policy for default value of media stream camera
38 NULL, // No policy for default value of protocol handlers
39 NULL, // No policy for default value of PPAPI broker
40 NULL, // No policy for default value of multiple automatic downloads
41 NULL, // No policy for default value of MIDI system exclusive requests
42 NULL, // No policy for default value of push messaging requests
43 NULL, // No policy for default value of SSL certificate decisions
44 #if defined(OS_WIN)
45 NULL, // No policy for default value of "switch to desktop"
46 #elif defined(OS_ANDROID) || defined(OS_CHROMEOS)
47 NULL, // No policy for default value of protected media identifier
48 #endif
49 #if defined(OS_ANDROID)
50 NULL, // No policy for default value of app banners
51 #endif
52 };
53 COMPILE_ASSERT(arraysize(kPrefToManageType) == CONTENT_SETTINGS_NUM_TYPES,
54 managed_content_settings_pref_names_array_size_incorrect);
55
56 struct PrefsForManagedContentSettingsMapEntry {
57 const char* pref_name;
58 ContentSettingsType content_type;
59 ContentSetting setting;
60 };
61
62 const PrefsForManagedContentSettingsMapEntry
63 kPrefsForManagedContentSettingsMap[] = {
64 {
65 prefs::kManagedCookiesAllowedForUrls,
66 CONTENT_SETTINGS_TYPE_COOKIES,
67 CONTENT_SETTING_ALLOW
68 }, {
69 prefs::kManagedCookiesSessionOnlyForUrls,
70 CONTENT_SETTINGS_TYPE_COOKIES,
71 CONTENT_SETTING_SESSION_ONLY
72 }, {
73 prefs::kManagedCookiesBlockedForUrls,
74 CONTENT_SETTINGS_TYPE_COOKIES,
75 CONTENT_SETTING_BLOCK
76 }, {
77 prefs::kManagedImagesAllowedForUrls,
78 CONTENT_SETTINGS_TYPE_IMAGES,
79 CONTENT_SETTING_ALLOW
80 }, {
81 prefs::kManagedImagesBlockedForUrls,
82 CONTENT_SETTINGS_TYPE_IMAGES,
83 CONTENT_SETTING_BLOCK
84 }, {
85 prefs::kManagedJavaScriptAllowedForUrls,
86 CONTENT_SETTINGS_TYPE_JAVASCRIPT,
87 CONTENT_SETTING_ALLOW
88 }, {
89 prefs::kManagedJavaScriptBlockedForUrls,
90 CONTENT_SETTINGS_TYPE_JAVASCRIPT,
91 CONTENT_SETTING_BLOCK
92 }, {
93 prefs::kManagedPluginsAllowedForUrls,
94 CONTENT_SETTINGS_TYPE_PLUGINS,
95 CONTENT_SETTING_ALLOW
96 }, {
97 prefs::kManagedPluginsBlockedForUrls,
98 CONTENT_SETTINGS_TYPE_PLUGINS,
99 CONTENT_SETTING_BLOCK
100 }, {
101 prefs::kManagedPopupsAllowedForUrls,
102 CONTENT_SETTINGS_TYPE_POPUPS,
103 CONTENT_SETTING_ALLOW
104 }, {
105 prefs::kManagedPopupsBlockedForUrls,
106 CONTENT_SETTINGS_TYPE_POPUPS,
107 CONTENT_SETTING_BLOCK
108 }, {
109 prefs::kManagedNotificationsAllowedForUrls,
110 CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
111 CONTENT_SETTING_ALLOW
112 }, {
113 prefs::kManagedNotificationsBlockedForUrls,
114 CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
115 CONTENT_SETTING_BLOCK
116 }
117 };
118
119 } // namespace
120
121 namespace content_settings {
122
123 // static
124 void PolicyProvider::RegisterProfilePrefs(
125 user_prefs::PrefRegistrySyncable* registry) {
126 registry->RegisterListPref(prefs::kManagedAutoSelectCertificateForUrls,
127 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
128 registry->RegisterListPref(prefs::kManagedCookiesAllowedForUrls,
129 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
130 registry->RegisterListPref(prefs::kManagedCookiesBlockedForUrls,
131 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
132 registry->RegisterListPref(prefs::kManagedCookiesSessionOnlyForUrls,
133 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
134 registry->RegisterListPref(prefs::kManagedImagesAllowedForUrls,
135 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
136 registry->RegisterListPref(prefs::kManagedImagesBlockedForUrls,
137 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
138 registry->RegisterListPref(prefs::kManagedJavaScriptAllowedForUrls,
139 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
140 registry->RegisterListPref(prefs::kManagedJavaScriptBlockedForUrls,
141 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
142 registry->RegisterListPref(prefs::kManagedPluginsAllowedForUrls,
143 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
144 registry->RegisterListPref(prefs::kManagedPluginsBlockedForUrls,
145 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
146 registry->RegisterListPref(prefs::kManagedPopupsAllowedForUrls,
147 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
148 registry->RegisterListPref(prefs::kManagedPopupsBlockedForUrls,
149 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
150 registry->RegisterListPref(prefs::kManagedNotificationsAllowedForUrls,
151 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
152 registry->RegisterListPref(prefs::kManagedNotificationsBlockedForUrls,
153 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
154 // Preferences for default content setting policies. If a policy is not set of
155 // the corresponding preferences below is set to CONTENT_SETTING_DEFAULT.
156 registry->RegisterIntegerPref(
157 prefs::kManagedDefaultCookiesSetting,
158 CONTENT_SETTING_DEFAULT,
159 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
160 registry->RegisterIntegerPref(
161 prefs::kManagedDefaultImagesSetting,
162 CONTENT_SETTING_DEFAULT,
163 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
164 registry->RegisterIntegerPref(
165 prefs::kManagedDefaultJavaScriptSetting,
166 CONTENT_SETTING_DEFAULT,
167 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
168 registry->RegisterIntegerPref(
169 prefs::kManagedDefaultPluginsSetting,
170 CONTENT_SETTING_DEFAULT,
171 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
172 registry->RegisterIntegerPref(
173 prefs::kManagedDefaultPopupsSetting,
174 CONTENT_SETTING_DEFAULT,
175 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
176 registry->RegisterIntegerPref(
177 prefs::kManagedDefaultGeolocationSetting,
178 CONTENT_SETTING_DEFAULT,
179 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
180 registry->RegisterIntegerPref(
181 prefs::kManagedDefaultNotificationsSetting,
182 CONTENT_SETTING_DEFAULT,
183 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
184 registry->RegisterIntegerPref(
185 prefs::kManagedDefaultMediaStreamSetting,
186 CONTENT_SETTING_DEFAULT,
187 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
188 }
189
190 PolicyProvider::PolicyProvider(PrefService* prefs) : prefs_(prefs) {
191 ReadManagedDefaultSettings();
192 ReadManagedContentSettings(false);
193
194 pref_change_registrar_.Init(prefs_);
195 PrefChangeRegistrar::NamedChangeCallback callback =
196 base::Bind(&PolicyProvider::OnPreferenceChanged, base::Unretained(this));
197 pref_change_registrar_.Add(
198 prefs::kManagedAutoSelectCertificateForUrls, callback);
199 pref_change_registrar_.Add(prefs::kManagedCookiesBlockedForUrls, callback);
200 pref_change_registrar_.Add(prefs::kManagedCookiesAllowedForUrls, callback);
201 pref_change_registrar_.Add(
202 prefs::kManagedCookiesSessionOnlyForUrls, callback);
203 pref_change_registrar_.Add(prefs::kManagedImagesBlockedForUrls, callback);
204 pref_change_registrar_.Add(prefs::kManagedImagesAllowedForUrls, callback);
205 pref_change_registrar_.Add(prefs::kManagedJavaScriptBlockedForUrls, callback);
206 pref_change_registrar_.Add(prefs::kManagedJavaScriptAllowedForUrls, callback);
207 pref_change_registrar_.Add(prefs::kManagedPluginsBlockedForUrls, callback);
208 pref_change_registrar_.Add(prefs::kManagedPluginsAllowedForUrls, callback);
209 pref_change_registrar_.Add(prefs::kManagedPopupsBlockedForUrls, callback);
210 pref_change_registrar_.Add(prefs::kManagedPopupsAllowedForUrls, callback);
211 pref_change_registrar_.Add(
212 prefs::kManagedNotificationsAllowedForUrls, callback);
213 pref_change_registrar_.Add(
214 prefs::kManagedNotificationsBlockedForUrls, callback);
215 // The following preferences are only used to indicate if a default content
216 // setting is managed and to hold the managed default setting value. If the
217 // value for any of the following preferences is set then the corresponding
218 // default content setting is managed. These preferences exist in parallel to
219 // the preference default content settings. If a default content settings type
220 // is managed any user defined exceptions (patterns) for this type are
221 // ignored.
222 pref_change_registrar_.Add(prefs::kManagedDefaultCookiesSetting, callback);
223 pref_change_registrar_.Add(prefs::kManagedDefaultImagesSetting, callback);
224 pref_change_registrar_.Add(prefs::kManagedDefaultJavaScriptSetting, callback);
225 pref_change_registrar_.Add(prefs::kManagedDefaultPluginsSetting, callback);
226 pref_change_registrar_.Add(prefs::kManagedDefaultPopupsSetting, callback);
227 pref_change_registrar_.Add(
228 prefs::kManagedDefaultGeolocationSetting, callback);
229 pref_change_registrar_.Add(
230 prefs::kManagedDefaultNotificationsSetting, callback);
231 pref_change_registrar_.Add(
232 prefs::kManagedDefaultMediaStreamSetting, callback);
233 }
234
235 PolicyProvider::~PolicyProvider() {
236 DCHECK(!prefs_);
237 }
238
239 RuleIterator* PolicyProvider::GetRuleIterator(
240 ContentSettingsType content_type,
241 const ResourceIdentifier& resource_identifier,
242 bool incognito) const {
243 return value_map_.GetRuleIterator(content_type, resource_identifier, &lock_);
244 }
245
246 void PolicyProvider::GetContentSettingsFromPreferences(
247 OriginIdentifierValueMap* value_map) {
248 for (size_t i = 0; i < arraysize(kPrefsForManagedContentSettingsMap); ++i) {
249 const char* pref_name = kPrefsForManagedContentSettingsMap[i].pref_name;
250 // Skip unset policies.
251 if (!prefs_->HasPrefPath(pref_name)) {
252 VLOG(2) << "Skipping unset preference: " << pref_name;
253 continue;
254 }
255
256 const PrefService::Preference* pref = prefs_->FindPreference(pref_name);
257 DCHECK(pref);
258 DCHECK(pref->IsManaged());
259
260 const base::ListValue* pattern_str_list = NULL;
261 if (!pref->GetValue()->GetAsList(&pattern_str_list)) {
262 NOTREACHED();
263 return;
264 }
265
266 for (size_t j = 0; j < pattern_str_list->GetSize(); ++j) {
267 std::string original_pattern_str;
268 if (!pattern_str_list->GetString(j, &original_pattern_str)) {
269 NOTREACHED();
270 continue;
271 }
272
273 PatternPair pattern_pair = ParsePatternString(original_pattern_str);
274 // Ignore invalid patterns.
275 if (!pattern_pair.first.IsValid()) {
276 VLOG(1) << "Ignoring invalid content settings pattern: " <<
277 original_pattern_str;
278 continue;
279 }
280
281 ContentSettingsType content_type =
282 kPrefsForManagedContentSettingsMap[i].content_type;
283 DCHECK_NE(content_type, CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE);
284 // If only one pattern was defined auto expand it to a pattern pair.
285 ContentSettingsPattern secondary_pattern =
286 !pattern_pair.second.IsValid() ? ContentSettingsPattern::Wildcard()
287 : pattern_pair.second;
288 value_map->SetValue(pattern_pair.first,
289 secondary_pattern,
290 content_type,
291 NO_RESOURCE_IDENTIFIER,
292 new base::FundamentalValue(
293 kPrefsForManagedContentSettingsMap[i].setting));
294 }
295 }
296 }
297
298 void PolicyProvider::GetAutoSelectCertificateSettingsFromPreferences(
299 OriginIdentifierValueMap* value_map) {
300 const char* pref_name = prefs::kManagedAutoSelectCertificateForUrls;
301
302 if (!prefs_->HasPrefPath(pref_name)) {
303 VLOG(2) << "Skipping unset preference: " << pref_name;
304 return;
305 }
306
307 const PrefService::Preference* pref = prefs_->FindPreference(pref_name);
308 DCHECK(pref);
309 DCHECK(pref->IsManaged());
310
311 const base::ListValue* pattern_filter_str_list = NULL;
312 if (!pref->GetValue()->GetAsList(&pattern_filter_str_list)) {
313 NOTREACHED();
314 return;
315 }
316
317 // Parse the list of pattern filter strings. A pattern filter string has
318 // the following JSON format:
319 //
320 // {
321 // "pattern": <content settings pattern string>,
322 // "filter" : <certificate filter in JSON format>
323 // }
324 //
325 // e.g.
326 // {
327 // "pattern": "[*.]example.com",
328 // "filter": {
329 // "ISSUER": {
330 // "CN": "some name"
331 // }
332 // }
333 // }
334 for (size_t j = 0; j < pattern_filter_str_list->GetSize(); ++j) {
335 std::string pattern_filter_json;
336 if (!pattern_filter_str_list->GetString(j, &pattern_filter_json)) {
337 NOTREACHED();
338 continue;
339 }
340
341 scoped_ptr<base::Value> value(base::JSONReader::Read(pattern_filter_json,
342 base::JSON_ALLOW_TRAILING_COMMAS));
343 if (!value || !value->IsType(base::Value::TYPE_DICTIONARY)) {
344 VLOG(1) << "Ignoring invalid certificate auto select setting. Reason:"
345 " Invalid JSON object: " << pattern_filter_json;
346 continue;
347 }
348
349 scoped_ptr<base::DictionaryValue> pattern_filter_pair(
350 static_cast<base::DictionaryValue*>(value.release()));
351 std::string pattern_str;
352 bool pattern_read = pattern_filter_pair->GetStringWithoutPathExpansion(
353 "pattern", &pattern_str);
354 base::DictionaryValue* cert_filter = NULL;
355 pattern_filter_pair->GetDictionaryWithoutPathExpansion("filter",
356 &cert_filter);
357 if (!pattern_read || !cert_filter) {
358 VLOG(1) << "Ignoring invalid certificate auto select setting. Reason:"
359 " Missing pattern or filter.";
360 continue;
361 }
362
363 ContentSettingsPattern pattern =
364 ContentSettingsPattern::FromString(pattern_str);
365 // Ignore invalid patterns.
366 if (!pattern.IsValid()) {
367 VLOG(1) << "Ignoring invalid certificate auto select setting:"
368 " Invalid content settings pattern: " << pattern.ToString();
369 continue;
370 }
371
372 // Don't pass removed values from |value|, because base::Values read with
373 // JSONReader use a shared string buffer. Instead, DeepCopy here.
374 value_map->SetValue(pattern,
375 ContentSettingsPattern::Wildcard(),
376 CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
377 std::string(),
378 cert_filter->DeepCopy());
379 }
380 }
381
382 void PolicyProvider::ReadManagedDefaultSettings() {
383 for (size_t type = 0; type < arraysize(kPrefToManageType); ++type) {
384 if (kPrefToManageType[type] == NULL) {
385 continue;
386 }
387 UpdateManagedDefaultSetting(ContentSettingsType(type));
388 }
389 }
390
391 void PolicyProvider::UpdateManagedDefaultSetting(
392 ContentSettingsType content_type) {
393 // If a pref to manage a default-content-setting was not set (NOTICE:
394 // "HasPrefPath" returns false if no value was set for a registered pref) then
395 // the default value of the preference is used. The default value of a
396 // preference to manage a default-content-settings is CONTENT_SETTING_DEFAULT.
397 // This indicates that no managed value is set. If a pref was set, than it
398 // MUST be managed.
399 DCHECK(!prefs_->HasPrefPath(kPrefToManageType[content_type]) ||
400 prefs_->IsManagedPreference(kPrefToManageType[content_type]));
401 base::AutoLock auto_lock(lock_);
402
403 int setting = prefs_->GetInteger(kPrefToManageType[content_type]);
404 if (setting == CONTENT_SETTING_DEFAULT) {
405 value_map_.DeleteValue(
406 ContentSettingsPattern::Wildcard(),
407 ContentSettingsPattern::Wildcard(),
408 content_type,
409 std::string());
410 } else {
411 value_map_.SetValue(ContentSettingsPattern::Wildcard(),
412 ContentSettingsPattern::Wildcard(),
413 content_type,
414 std::string(),
415 new base::FundamentalValue(setting));
416 }
417 }
418
419
420 void PolicyProvider::ReadManagedContentSettings(bool overwrite) {
421 base::AutoLock auto_lock(lock_);
422 if (overwrite)
423 value_map_.clear();
424 GetContentSettingsFromPreferences(&value_map_);
425 GetAutoSelectCertificateSettingsFromPreferences(&value_map_);
426 }
427
428 // Since the PolicyProvider is a read only content settings provider, all
429 // methodes of the ProviderInterface that set or delete any settings do nothing.
430 bool PolicyProvider::SetWebsiteSetting(
431 const ContentSettingsPattern& primary_pattern,
432 const ContentSettingsPattern& secondary_pattern,
433 ContentSettingsType content_type,
434 const ResourceIdentifier& resource_identifier,
435 base::Value* value) {
436 return false;
437 }
438
439 void PolicyProvider::ClearAllContentSettingsRules(
440 ContentSettingsType content_type) {
441 }
442
443 void PolicyProvider::ShutdownOnUIThread() {
444 DCHECK(CalledOnValidThread());
445 RemoveAllObservers();
446 if (!prefs_)
447 return;
448 pref_change_registrar_.RemoveAll();
449 prefs_ = NULL;
450 }
451
452 void PolicyProvider::OnPreferenceChanged(const std::string& name) {
453 DCHECK(CalledOnValidThread());
454
455 if (name == prefs::kManagedDefaultCookiesSetting) {
456 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_COOKIES);
457 } else if (name == prefs::kManagedDefaultImagesSetting) {
458 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_IMAGES);
459 } else if (name == prefs::kManagedDefaultJavaScriptSetting) {
460 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_JAVASCRIPT);
461 } else if (name == prefs::kManagedDefaultPluginsSetting) {
462 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_PLUGINS);
463 } else if (name == prefs::kManagedDefaultPopupsSetting) {
464 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_POPUPS);
465 } else if (name == prefs::kManagedDefaultGeolocationSetting) {
466 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_GEOLOCATION);
467 } else if (name == prefs::kManagedDefaultNotificationsSetting) {
468 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
469 } else if (name == prefs::kManagedDefaultMediaStreamSetting) {
470 UpdateManagedDefaultSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM);
471 } else if (name == prefs::kManagedAutoSelectCertificateForUrls ||
472 name == prefs::kManagedCookiesAllowedForUrls ||
473 name == prefs::kManagedCookiesBlockedForUrls ||
474 name == prefs::kManagedCookiesSessionOnlyForUrls ||
475 name == prefs::kManagedImagesAllowedForUrls ||
476 name == prefs::kManagedImagesBlockedForUrls ||
477 name == prefs::kManagedJavaScriptAllowedForUrls ||
478 name == prefs::kManagedJavaScriptBlockedForUrls ||
479 name == prefs::kManagedPluginsAllowedForUrls ||
480 name == prefs::kManagedPluginsBlockedForUrls ||
481 name == prefs::kManagedPopupsAllowedForUrls ||
482 name == prefs::kManagedPopupsBlockedForUrls ||
483 name == prefs::kManagedNotificationsAllowedForUrls ||
484 name == prefs::kManagedNotificationsBlockedForUrls) {
485 ReadManagedContentSettings(true);
486 ReadManagedDefaultSettings();
487 }
488
489 NotifyObservers(ContentSettingsPattern(),
490 ContentSettingsPattern(),
491 CONTENT_SETTINGS_TYPE_DEFAULT,
492 std::string());
493 }
494
495 } // namespace content_settings
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698