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

Side by Side Diff: chrome/browser/extensions/extension_service_unittest.cc

Issue 595363002: Add policy controlled permission block list for extensions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@ext-fix
Patch Set: fix memory leaks 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
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <algorithm> 5 #include <algorithm>
6 #include <set> 6 #include <set>
7 #include <vector> 7 #include <vector>
8 8
9 #include "base/at_exit.h" 9 #include "base/at_exit.h"
10 #include "base/basictypes.h" 10 #include "base/basictypes.h"
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
48 #include "chrome/browser/extensions/external_install_error.h" 48 #include "chrome/browser/extensions/external_install_error.h"
49 #include "chrome/browser/extensions/external_install_manager.h" 49 #include "chrome/browser/extensions/external_install_manager.h"
50 #include "chrome/browser/extensions/external_policy_loader.h" 50 #include "chrome/browser/extensions/external_policy_loader.h"
51 #include "chrome/browser/extensions/external_pref_loader.h" 51 #include "chrome/browser/extensions/external_pref_loader.h"
52 #include "chrome/browser/extensions/external_provider_impl.h" 52 #include "chrome/browser/extensions/external_provider_impl.h"
53 #include "chrome/browser/extensions/fake_safe_browsing_database_manager.h" 53 #include "chrome/browser/extensions/fake_safe_browsing_database_manager.h"
54 #include "chrome/browser/extensions/installed_loader.h" 54 #include "chrome/browser/extensions/installed_loader.h"
55 #include "chrome/browser/extensions/pack_extension_job.h" 55 #include "chrome/browser/extensions/pack_extension_job.h"
56 #include "chrome/browser/extensions/pending_extension_info.h" 56 #include "chrome/browser/extensions/pending_extension_info.h"
57 #include "chrome/browser/extensions/pending_extension_manager.h" 57 #include "chrome/browser/extensions/pending_extension_manager.h"
58 #include "chrome/browser/extensions/permissions_updater.h"
58 #include "chrome/browser/extensions/test_blacklist.h" 59 #include "chrome/browser/extensions/test_blacklist.h"
59 #include "chrome/browser/extensions/test_extension_system.h" 60 #include "chrome/browser/extensions/test_extension_system.h"
60 #include "chrome/browser/extensions/unpacked_installer.h" 61 #include "chrome/browser/extensions/unpacked_installer.h"
61 #include "chrome/browser/extensions/updater/extension_updater.h" 62 #include "chrome/browser/extensions/updater/extension_updater.h"
62 #include "chrome/browser/prefs/pref_service_syncable.h" 63 #include "chrome/browser/prefs/pref_service_syncable.h"
63 #include "chrome/browser/sync/profile_sync_service.h" 64 #include "chrome/browser/sync/profile_sync_service.h"
64 #include "chrome/browser/sync/profile_sync_service_factory.h" 65 #include "chrome/browser/sync/profile_sync_service_factory.h"
65 #include "chrome/common/chrome_constants.h" 66 #include "chrome/common/chrome_constants.h"
66 #include "chrome/common/chrome_switches.h" 67 #include "chrome/common/chrome_switches.h"
67 #include "chrome/common/extensions/api/plugins/plugins_handler.h" 68 #include "chrome/common/extensions/api/plugins/plugins_handler.h"
(...skipping 24 matching lines...) Expand all
92 #include "extensions/browser/test_management_policy.h" 93 #include "extensions/browser/test_management_policy.h"
93 #include "extensions/browser/uninstall_reason.h" 94 #include "extensions/browser/uninstall_reason.h"
94 #include "extensions/common/constants.h" 95 #include "extensions/common/constants.h"
95 #include "extensions/common/extension.h" 96 #include "extensions/common/extension.h"
96 #include "extensions/common/extension_builder.h" 97 #include "extensions/common/extension_builder.h"
97 #include "extensions/common/extension_l10n_util.h" 98 #include "extensions/common/extension_l10n_util.h"
98 #include "extensions/common/extension_resource.h" 99 #include "extensions/common/extension_resource.h"
99 #include "extensions/common/feature_switch.h" 100 #include "extensions/common/feature_switch.h"
100 #include "extensions/common/manifest_constants.h" 101 #include "extensions/common/manifest_constants.h"
101 #include "extensions/common/manifest_handlers/background_info.h" 102 #include "extensions/common/manifest_handlers/background_info.h"
103 #include "extensions/common/manifest_handlers/permissions_parser.h"
102 #include "extensions/common/manifest_url_handlers.h" 104 #include "extensions/common/manifest_url_handlers.h"
103 #include "extensions/common/permissions/permission_set.h" 105 #include "extensions/common/permissions/permission_set.h"
104 #include "extensions/common/permissions/permissions_data.h" 106 #include "extensions/common/permissions/permissions_data.h"
105 #include "extensions/common/switches.h" 107 #include "extensions/common/switches.h"
106 #include "extensions/common/url_pattern.h" 108 #include "extensions/common/url_pattern.h"
107 #include "extensions/common/value_builder.h" 109 #include "extensions/common/value_builder.h"
108 #include "gpu/config/gpu_info.h" 110 #include "gpu/config/gpu_info.h"
109 #include "grit/browser_resources.h" 111 #include "grit/browser_resources.h"
110 #include "net/cookies/canonical_cookie.h" 112 #include "net/cookies/canonical_cookie.h"
111 #include "net/cookies/cookie_monster.h" 113 #include "net/cookies/cookie_monster.h"
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 const char all_zero[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 184 const char all_zero[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
183 const char good2048[] = "nmgjhmhbleinmjpbdhgajfjkbijcmgbh"; 185 const char good2048[] = "nmgjhmhbleinmjpbdhgajfjkbijcmgbh";
184 const char good_crx[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; 186 const char good_crx[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
185 const char hosted_app[] = "kbmnembihfiondgfjekmnmcbddelicoi"; 187 const char hosted_app[] = "kbmnembihfiondgfjekmnmcbddelicoi";
186 const char page_action[] = "obcimlgaoabeegjmmpldobjndiealpln"; 188 const char page_action[] = "obcimlgaoabeegjmmpldobjndiealpln";
187 const char theme_crx[] = "iamefpfkojoapidjnbafmgkgncegbkad"; 189 const char theme_crx[] = "iamefpfkojoapidjnbafmgkgncegbkad";
188 const char theme2_crx[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf"; 190 const char theme2_crx[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf";
189 const char permissions_crx[] = "eagpmdpfmaekmmcejjbmjoecnejeiiin"; 191 const char permissions_crx[] = "eagpmdpfmaekmmcejjbmjoecnejeiiin";
190 const char unpacked[] = "cbcdidchbppangcjoddlpdjlenngjldk"; 192 const char unpacked[] = "cbcdidchbppangcjoddlpdjlenngjldk";
191 const char updates_from_webstore[] = "akjooamlhcgeopfifcmlggaebeocgokj"; 193 const char updates_from_webstore[] = "akjooamlhcgeopfifcmlggaebeocgokj";
194 const char permissions_blocklist[] = "noffkehfcaggllbcojjbopcmlhcnhcdn";
192 195
193 struct ExtensionsOrder { 196 struct ExtensionsOrder {
194 bool operator()(const scoped_refptr<const Extension>& a, 197 bool operator()(const scoped_refptr<const Extension>& a,
195 const scoped_refptr<const Extension>& b) { 198 const scoped_refptr<const Extension>& b) {
196 return a->name() < b->name(); 199 return a->name() < b->name();
197 } 200 }
198 }; 201 };
199 202
200 static std::vector<base::string16> GetErrors() { 203 static std::vector<base::string16> GetErrors() {
201 const std::vector<base::string16>* errors = 204 const std::vector<base::string16>* errors =
(...skipping 558 matching lines...) Expand 10 before | Expand all | Expand 10 after
760 " \"id\": 1,\n" 763 " \"id\": 1,\n"
761 " \"features\": [\"webgl\"]\n" 764 " \"features\": [\"webgl\"]\n"
762 " }\n" 765 " }\n"
763 " ]\n" 766 " ]\n"
764 "}"; 767 "}";
765 gpu::GPUInfo gpu_info; 768 gpu::GPUInfo gpu_info;
766 content::GpuDataManager::GetInstance()->InitializeForTesting( 769 content::GpuDataManager::GetInstance()->InitializeForTesting(
767 json_blacklist, gpu_info); 770 json_blacklist, gpu_info);
768 } 771 }
769 772
773 // Grants all optional permissions stated in manifest to active permission
774 // set for extension |id|.
775 void GrantAllOptionalPermissions(const std::string& id) {
776 const Extension* extension = service()->GetInstalledExtension(id);
777 scoped_refptr<const PermissionSet> all_optional_permissions =
778 extensions::PermissionsParser::GetOptionalPermissions(extension);
779 extensions::PermissionsUpdater perms_updater(profile());
780 perms_updater.AddPermissions(extension, all_optional_permissions.get());
781 }
782
770 // Helper method to set up a WindowedNotificationObserver to wait for a 783 // Helper method to set up a WindowedNotificationObserver to wait for a
771 // specific CrxInstaller to finish if we don't know the value of the 784 // specific CrxInstaller to finish if we don't know the value of the
772 // |installer| yet. 785 // |installer| yet.
773 static bool IsCrxInstallerDone(extensions::CrxInstaller** installer, 786 static bool IsCrxInstallerDone(extensions::CrxInstaller** installer,
774 const content::NotificationSource& source, 787 const content::NotificationSource& source,
775 const content::NotificationDetails& details) { 788 const content::NotificationDetails& details) {
776 return content::Source<extensions::CrxInstaller>(source).ptr() == 789 return content::Source<extensions::CrxInstaller>(source).ptr() ==
777 *installer; 790 *installer;
778 } 791 }
779 792
(...skipping 3056 matching lines...) Expand 10 before | Expand all | Expand 10 after
3836 extensions::TestManagementPolicyProvider provider( 3849 extensions::TestManagementPolicyProvider provider(
3837 extensions::TestManagementPolicyProvider::MUST_REMAIN_ENABLED); 3850 extensions::TestManagementPolicyProvider::MUST_REMAIN_ENABLED);
3838 GetManagementPolicy()->RegisterProvider(&provider); 3851 GetManagementPolicy()->RegisterProvider(&provider);
3839 3852
3840 // Reinstall the extension. 3853 // Reinstall the extension.
3841 InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_UPDATED); 3854 InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_UPDATED);
3842 EXPECT_EQ(1u, registry()->enabled_extensions().size()); 3855 EXPECT_EQ(1u, registry()->enabled_extensions().size());
3843 EXPECT_EQ(0u, registry()->disabled_extensions().size()); 3856 EXPECT_EQ(0u, registry()->disabled_extensions().size());
3844 } 3857 }
3845 3858
3859 // Tests that extensions with conflicting required permissions by enterprise
3860 // policy cannot be installed.
3861 TEST_F(ExtensionServiceTest, PolicyBlockedPermissionNewExtensionInstall) {
3862 InitializeEmptyExtensionServiceWithTestingPrefs();
3863 base::FilePath path = data_dir().AppendASCII("permissions_blocklist");
3864
3865 {
3866 // Update policy to block one of the required permissions of target.
3867 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3868 pref.AddBlockedPermission("*", "tabs");
3869 }
3870
3871 // The extension should be failed to install.
3872 PackAndInstallCRX(path, INSTALL_FAILED);
3873
3874 {
3875 // Update policy to block one of the optional permissions instead.
3876 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3877 pref.ClearBlockedPermissions("*");
3878 pref.AddBlockedPermission("*", "history");
3879 }
3880
3881 // The extension should succeed to install this time.
3882 std::string id = PackAndInstallCRX(path, INSTALL_NEW)->id();
3883
3884 // Uninstall the extension and update policy to block some arbitrary
3885 // unknown permission.
3886 UninstallExtension(id, false);
3887 {
3888 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3889 pref.ClearBlockedPermissions("*");
3890 pref.AddBlockedPermission("*", "unknown.permission.for.testing");
3891 }
3892
3893 // The extension should succeed to install as well.
3894 PackAndInstallCRX(path, INSTALL_NEW);
3895 }
3896
3897 // Tests that extension supposed to be force installed but with conflicting
3898 // required permissions cannot be installed.
3899 TEST_F(ExtensionServiceTest, PolicyBlockedPermissionConflictsWithForceInstall) {
3900 InitializeEmptyExtensionServiceWithTestingPrefs();
3901
3902 // Pack the crx file.
3903 base::FilePath path = data_dir().AppendASCII("permissions_blocklist");
3904 base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem");
3905 base::ScopedTempDir temp_dir;
3906 EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
3907 base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx");
3908
3909 PackCRX(path, pem_path, crx_path);
3910
3911 {
3912 // Block one of the required permissions.
3913 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3914 pref.AddBlockedPermission("*", "tabs");
3915 }
3916
3917 // Use MockExtensionProvider to simulate force installing extension.
3918 MockExtensionProvider* provider =
3919 new MockExtensionProvider(service(), Manifest::EXTERNAL_POLICY_DOWNLOAD);
3920 AddMockExternalProvider(provider);
3921 provider->UpdateOrAddExtension(permissions_blocklist, "1.0", crx_path);
3922
3923 {
3924 // Attempts to force install this extension.
3925 content::WindowedNotificationObserver observer(
3926 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
3927 content::NotificationService::AllSources());
3928 service()->CheckForExternalUpdates();
3929 observer.Wait();
3930 }
3931
3932 // The extension should not be installed.
3933 ASSERT_FALSE(service()->GetInstalledExtension(permissions_blocklist));
3934
3935 // Remove this extension from pending extension manager as we would like to
3936 // give another attempt later.
3937 service()->pending_extension_manager()->Remove(permissions_blocklist);
3938
3939 {
3940 // Clears the permission block list.
3941 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3942 pref.ClearBlockedPermissions("*");
3943 }
3944
3945 {
3946 // Attempts to force install this extension again.
3947 content::WindowedNotificationObserver observer(
3948 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
3949 content::NotificationService::AllSources());
3950 service()->CheckForExternalUpdates();
3951 observer.Wait();
3952 }
3953
3954 const Extension* installed =
3955 service()->GetInstalledExtension(permissions_blocklist);
3956 ASSERT_TRUE(installed);
3957 EXPECT_EQ(installed->location(), Manifest::EXTERNAL_POLICY_DOWNLOAD);
3958 }
3959
3960 // Tests that newer versions of an extension with conflicting required
3961 // permissions by enterprise policy cannot be updated to.
3962 TEST_F(ExtensionServiceTest, PolicyBlockedPermissionExtensionUpdate) {
3963 InitializeEmptyExtensionServiceWithTestingPrefs();
3964
3965 base::FilePath path = data_dir().AppendASCII("permissions_blocklist");
3966 base::FilePath path2 = data_dir().AppendASCII("permissions_blocklist2");
3967 base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem");
3968
3969 // Install 'permissions_blocklist'.
3970 const Extension* installed = PackAndInstallCRX(path, pem_path, INSTALL_NEW);
3971 EXPECT_EQ(installed->id(), permissions_blocklist);
3972
3973 {
3974 // Block one of the required permissions of 'permissions_blocklist2'.
3975 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
3976 pref.AddBlockedPermission("*", "downloads");
3977 }
3978
3979 // Install 'permissions_blocklist' again, should be updated.
3980 const Extension* updated = PackAndInstallCRX(path, pem_path, INSTALL_UPDATED);
3981 EXPECT_EQ(updated->id(), permissions_blocklist);
3982
3983 std::string old_version = updated->VersionString();
3984
3985 // Attempts to update to 'permissions_blocklist2' should fail.
3986 PackAndInstallCRX(path2, pem_path, INSTALL_FAILED);
3987
3988 // Verify that the old version is still enabled.
3989 updated = service()->GetExtensionById(permissions_blocklist, false);
3990 ASSERT_TRUE(updated);
3991 EXPECT_EQ(old_version, updated->VersionString());
3992 }
3993
3994 // Tests that policy update with additional permissions blocked revoke
3995 // conflicting granted optional permissions and unload extensions with
3996 // conflicting required permissions, including the force installed ones.
3997 TEST_F(ExtensionServiceTest, PolicyBlockedPermissionPolicyUpdate) {
3998 InitializeEmptyExtensionServiceWithTestingPrefs();
3999
4000 base::FilePath path = data_dir().AppendASCII("permissions_blocklist");
4001 base::FilePath path2 = data_dir().AppendASCII("permissions_blocklist2");
4002 base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem");
4003
4004 // Pack the crx file.
4005 base::ScopedTempDir temp_dir;
4006 EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
4007 base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx");
4008
4009 PackCRX(path2, pem_path, crx_path);
4010
4011 // Install two arbitary extensions with specified manifest.
4012 std::string ext1 = PackAndInstallCRX(path, INSTALL_NEW)->id();
4013 std::string ext2 = PackAndInstallCRX(path2, INSTALL_NEW)->id();
4014 ASSERT_NE(ext1, permissions_blocklist);
4015 ASSERT_NE(ext2, permissions_blocklist);
4016 ASSERT_NE(ext1, ext2);
4017
4018 // Force install another extension with known id and same manifest as 'ext2'.
4019 std::string ext2_forced = permissions_blocklist;
4020 MockExtensionProvider* provider =
4021 new MockExtensionProvider(service(), Manifest::EXTERNAL_POLICY_DOWNLOAD);
4022 AddMockExternalProvider(provider);
4023 provider->UpdateOrAddExtension(ext2_forced, "2.0", crx_path);
4024
4025 content::WindowedNotificationObserver observer(
4026 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
4027 content::NotificationService::AllSources());
4028 service()->CheckForExternalUpdates();
4029 observer.Wait();
4030
4031 extensions::ExtensionRegistry* registry =
4032 extensions::ExtensionRegistry::Get(profile());
4033
4034 // Verify all three extensions are installed and enabled.
4035 ASSERT_TRUE(registry->enabled_extensions().GetByID(ext1));
4036 ASSERT_TRUE(registry->enabled_extensions().GetByID(ext2));
4037 ASSERT_TRUE(registry->enabled_extensions().GetByID(ext2_forced));
4038
4039 // Grant all optional permissions to each extension.
4040 GrantAllOptionalPermissions(ext1);
4041 GrantAllOptionalPermissions(ext2);
4042 GrantAllOptionalPermissions(ext2_forced);
4043
4044 scoped_refptr<const PermissionSet> active_permissions(
4045 ExtensionPrefs::Get(profile())->GetActivePermissions(ext1));
4046 EXPECT_TRUE(active_permissions->HasAPIPermission(
4047 extensions::APIPermission::kDownloads));
4048
4049 // Set policy to block 'downloads' permission.
4050 {
4051 ManagementPrefUpdater pref(profile_->GetTestingPrefService());
4052 pref.AddBlockedPermission("*", "downloads");
4053 }
4054
4055 base::RunLoop().RunUntilIdle();
4056
4057 // 'ext1' should still be enabled, but with 'downloads' permission revoked.
4058 EXPECT_TRUE(registry->enabled_extensions().GetByID(ext1));
4059 active_permissions =
4060 ExtensionPrefs::Get(profile())->GetActivePermissions(ext1);
4061 EXPECT_FALSE(active_permissions->HasAPIPermission(
4062 extensions::APIPermission::kDownloads));
4063
4064 // 'ext2' should be disabled because one of its required permissions is
4065 // blocked.
4066 EXPECT_FALSE(registry->enabled_extensions().GetByID(ext2));
4067
4068 // 'ext2_forced' should be handled the same as 'ext2'
4069 EXPECT_FALSE(registry->enabled_extensions().GetByID(ext2_forced));
4070 }
4071
3846 // Flaky on windows; http://crbug.com/309833 4072 // Flaky on windows; http://crbug.com/309833
3847 #if defined(OS_WIN) 4073 #if defined(OS_WIN)
3848 #define MAYBE_ExternalExtensionAutoAcknowledgement DISABLED_ExternalExtensionAut oAcknowledgement 4074 #define MAYBE_ExternalExtensionAutoAcknowledgement DISABLED_ExternalExtensionAut oAcknowledgement
3849 #else 4075 #else
3850 #define MAYBE_ExternalExtensionAutoAcknowledgement ExternalExtensionAutoAcknowle dgement 4076 #define MAYBE_ExternalExtensionAutoAcknowledgement ExternalExtensionAutoAcknowle dgement
3851 #endif 4077 #endif
3852 TEST_F(ExtensionServiceTest, MAYBE_ExternalExtensionAutoAcknowledgement) { 4078 TEST_F(ExtensionServiceTest, MAYBE_ExternalExtensionAutoAcknowledgement) {
3853 InitializeEmptyExtensionService(); 4079 InitializeEmptyExtensionService();
3854 service()->set_extensions_enabled(true); 4080 service()->set_extensions_enabled(true);
3855 4081
(...skipping 3271 matching lines...) Expand 10 before | Expand all | Expand 10 after
7127 7353
7128 service()->Observe(chrome::NOTIFICATION_PROFILE_DESTRUCTION_STARTED, 7354 service()->Observe(chrome::NOTIFICATION_PROFILE_DESTRUCTION_STARTED,
7129 content::Source<Profile>(profile()), 7355 content::Source<Profile>(profile()),
7130 content::NotificationService::NoDetails()); 7356 content::NotificationService::NoDetails());
7131 EXPECT_EQ(UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN, unloaded_reason_); 7357 EXPECT_EQ(UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN, unloaded_reason_);
7132 EXPECT_EQ(0u, registry()->enabled_extensions().size()); 7358 EXPECT_EQ(0u, registry()->enabled_extensions().size());
7133 EXPECT_EQ(0u, registry()->disabled_extensions().size()); 7359 EXPECT_EQ(0u, registry()->disabled_extensions().size());
7134 EXPECT_EQ(0u, registry()->terminated_extensions().size()); 7360 EXPECT_EQ(0u, registry()->terminated_extensions().size());
7135 EXPECT_EQ(0u, registry()->blacklisted_extensions().size()); 7361 EXPECT_EQ(0u, registry()->blacklisted_extensions().size());
7136 } 7362 }
OLDNEW
« no previous file with comments | « chrome/browser/extensions/extension_service.cc ('k') | chrome/browser/extensions/extension_system_impl.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698