1 // Copyright 2014 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/supervised_user/supervised_user_site_list.h" 6 7 #include "base/json/json_file_value_serializer.h" 8 #include "base/logging.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "base/values.h" 11 #include "extensions/common/extension.h" 12 13 using base::DictionaryValue; 14 using base::ListValue; 15 using base::Value; 16 17 const int kSitelistFormatVersion = 1; 18 19 const char kCategoriesKey[] = "categories"; 20 const char kHostnameHashesKey[] = "hostname_hashes"; 21 const char kNameKey[] = "name"; 22 const char kSitesKey[] = "sites"; 23 const char kSitelistFormatVersionKey[] = "version"; 24 const char kThumbnailKey[] = "thumbnail"; 25 const char kThumbnailUrlKey[] = "thumbnail_url"; 26 const char kUrlKey[] = "url"; 27 const char kWhitelistKey[] = "whitelist"; 28 29 namespace { 30 31 struct CategoryInfo { 32 const char* identifier; 33 const char* name; 34 }; 35 36 // These are placeholders for now. 37 CategoryInfo g_categories[] = { 38 {"com.google.chrome.animals", "Animals and Plants"}, 39 {"com.google.chrome.arts", "Arts"}, 40 {"com.google.chrome.business", "Business"}, 41 {"com.google.chrome.computers", "Computers"}, 42 {"com.google.chrome.education", "Education"}, 43 {"com.google.chrome.entertainment", "Entertainment"}, 44 {"com.google.chrome.games", "Games"}, 45 {"com.google.chrome.health", "Health"}, 46 {"com.google.chrome.home", "Home"}, 47 {"com.google.chrome.international", "International"}, 48 {"com.google.chrome.news", "News"}, 49 {"com.google.chrome.people", "People and Society"}, 50 {"com.google.chrome.places", "Places"}, 51 {"com.google.chrome.pre-school", "Pre-School"}, 52 {"com.google.chrome.reference", "Reference"}, 53 {"com.google.chrome.science", "Science"}, 54 {"com.google.chrome.shopping", "Shopping"}, 55 {"com.google.chrome.sports", "Sports and Hobbies"}, 56 {"com.google.chrome.teens", "Teens"} 57 }; 58 59 // Category 0 is "not listed"; actual category IDs start at 1. 60 int GetCategoryId(const std::string& category) { 61 for (size_t i = 0; i < arraysize(g_categories); ++i) { 62 if (g_categories[i].identifier == category) 63 return i + 1; 64 } 65 return 0; 66 } 67 68 // Takes a DictionaryValue entry from the JSON file and fills the whitelist 69 // (via URL patterns or hostname hashes) and the URL in the corresponding Site 70 // struct. 71 void AddWhitelistEntries(const base::DictionaryValue* site_dict, 72 SupervisedUserSiteList::Site* site) { 73 std::vector<std::string>* patterns = &site->patterns; 74 75 bool found = false; 76 const base::ListValue* whitelist = NULL; 77 if (site_dict->GetList(kWhitelistKey, &whitelist)) { 78 found = true; 79 for (base::ListValue::const_iterator whitelist_it = whitelist->begin(); 80 whitelist_it != whitelist->end(); ++whitelist_it) { 81 std::string pattern; 82 if (!(*whitelist_it)->GetAsString(&pattern)) { 83 LOG(ERROR) << "Invalid whitelist entry"; 84 continue; 85 } 86 87 patterns->push_back(pattern); 88 } 89 } 90 91 std::vector<std::string>* hashes = &site->hostname_hashes; 92 const base::ListValue* hash_list = NULL; 93 if (site_dict->GetList(kHostnameHashesKey, &hash_list)) { 94 found = true; 95 for (base::ListValue::const_iterator hash_list_it = hash_list->begin(); 96 hash_list_it != hash_list->end(); ++hash_list_it) { 97 std::string hash; 98 if (!(*hash_list_it)->GetAsString(&hash)) { 99 LOG(ERROR) << "Invalid whitelist entry"; 100 continue; 101 } 102 103 hashes->push_back(hash); 104 } 105 } 106 107 if (found) 108 return; 109 110 // Fall back to using a whitelist based on the URL. 111 std::string url_str; 112 if (!site_dict->GetString(kUrlKey, &url_str)) { 113 LOG(ERROR) << "Whitelist is invalid"; 114 return; 115 } 116 117 GURL url(url_str); 118 if (!url.is_valid()) { 119 LOG(ERROR) << "URL " << url_str << " is invalid"; 120 return; 121 } 122 123 patterns->push_back(url.host()); 124 } 125 126 } // namespace 127 128 SupervisedUserSiteList::Site::Site(const base::string16& name, 129 int category_id) 130 : name(name), 131 category_id(category_id) {} 132 133 SupervisedUserSiteList::Site::~Site() {} 134 135 SupervisedUserSiteList::SupervisedUserSiteList( 136 const std::string& extension_id, 137 const base::FilePath& path) 138 : extension_id_(extension_id), 139 path_(path) { 140 } 141 142 SupervisedUserSiteList::~SupervisedUserSiteList() { 143 } 144 145 SupervisedUserSiteList* SupervisedUserSiteList::Clone() { 146 return new SupervisedUserSiteList(extension_id_, path_); 147 } 148 149 // static 150 void SupervisedUserSiteList::GetCategoryNames( 151 std::vector<base::string16>* categories) { 152 // TODO(bauerb): Collect custom categories from extensions. 153 for (size_t i = 0; i < arraysize(g_categories); ++i) { 154 categories->push_back(base::ASCIIToUTF16(g_categories[i].name)); 155 } 156 } 157 158 void SupervisedUserSiteList::GetSites(std::vector<Site>* sites) { 159 if (!LazyLoad()) 160 return; 161 162 for (base::ListValue::iterator entry_it = sites_->begin(); 163 entry_it != sites_->end(); ++entry_it) { 164 base::DictionaryValue* entry = NULL; 165 if (!(*entry_it)->GetAsDictionary(&entry)) { 166 LOG(ERROR) << "Entry is invalid"; 167 continue; 168 } 169 170 base::string16 name; 171 entry->GetString(kNameKey, &name); 172 173 // TODO(bauerb): We need to distinguish between "no category assigned" and 174 // "not on any site list". 175 int category_id = 0; 176 const base::ListValue* categories = NULL; 177 if (entry->GetList(kCategoriesKey, &categories)) { 178 for (base::ListValue::const_iterator it = categories->begin(); 179 it != categories->end(); ++it) { 180 std::string category; 181 if (!(*it)->GetAsString(&category)) { 182 LOG(ERROR) << "Invalid category"; 183 continue; 184 } 185 category_id = GetCategoryId(category); 186 break; 187 } 188 } 189 sites->push_back(Site(name, category_id)); 190 AddWhitelistEntries(entry, &sites->back()); 191 } 192 } 193 194 bool SupervisedUserSiteList::LazyLoad() { 195 if (sites_.get()) 196 return true; 197 198 JSONFileValueSerializer serializer(path_); 199 std::string error; 200 scoped_ptr<base::Value> value(serializer.Deserialize(NULL, &error)); 201 if (!value.get()) { 202 LOG(ERROR) << "Couldn't load site list " << path_.value() << ": " 203 << error; 204 return false; 205 } 206 207 base::DictionaryValue* dict = NULL; 208 if (!value->GetAsDictionary(&dict)) { 209 LOG(ERROR) << "Site list " << path_.value() << " is invalid"; 210 return false; 211 } 212 213 int version = 0; 214 if (!dict->GetInteger(kSitelistFormatVersionKey, &version)) { 215 LOG(ERROR) << "Site list " << path_.value() << " has invalid version"; 216 return false; 217 } 218 219 if (version > kSitelistFormatVersion) { 220 LOG(ERROR) << "Site list " << path_.value() << " has a too new format"; 221 return false; 222 } 223 224 base::ListValue* sites = NULL; 225 if (dict->GetList(kSitesKey, &sites)) 226 sites_.reset(sites->DeepCopy()); 227 228 base::DictionaryValue* categories = NULL; 229 if (dict->GetDictionary(kCategoriesKey, &categories)) 230 categories_.reset(categories->DeepCopy()); 231 232 return true; 233 } 234 235 void SupervisedUserSiteList::CopyThumbnailUrl( 236 const base::DictionaryValue* source, 237 base::DictionaryValue* dest) { 238 if (!source->HasKey(kThumbnailKey)) 239 return; 240 241 std::string thumbnail; 242 if (!source->GetString(kThumbnailKey, &thumbnail)) { 243 LOG(ERROR) << "Invalid thumbnail"; 244 return; 245 } 246 247 GURL base_url = 248 extensions::Extension::GetBaseURLFromExtensionId(extension_id_); 249 GURL thumbnail_url = base_url.Resolve(thumbnail); 250 if (!thumbnail_url.is_valid()) { 251 LOG(ERROR) << "Invalid thumbnail"; 252 return; 253 } 254 255 dest->SetString(kThumbnailUrlKey, thumbnail_url.spec()); 256 } 257