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 "components/variations/study_filtering.h" 6 7 #include <set> 8 9 namespace chrome_variations { 10 11 namespace { 12 13 Study_Platform GetCurrentPlatform() { 14 #if defined(OS_WIN) 15 return Study_Platform_PLATFORM_WINDOWS; 16 #elif defined(OS_IOS) 17 return Study_Platform_PLATFORM_IOS; 18 #elif defined(OS_MACOSX) 19 return Study_Platform_PLATFORM_MAC; 20 #elif defined(OS_CHROMEOS) 21 return Study_Platform_PLATFORM_CHROMEOS; 22 #elif defined(OS_ANDROID) 23 return Study_Platform_PLATFORM_ANDROID; 24 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) 25 // Default BSD and SOLARIS to Linux to not break those builds, although these 26 // platforms are not officially supported by Chrome. 27 return Study_Platform_PLATFORM_LINUX; 28 #else 29 #error Unknown platform 30 #endif 31 } 32 33 // Converts |date_time| in Study date format to base::Time. 34 base::Time ConvertStudyDateToBaseTime(int64 date_time) { 35 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); 36 } 37 38 } // namespace 39 40 namespace internal { 41 42 bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) { 43 // An empty channel list matches all channels. 44 if (filter.channel_size() == 0) 45 return true; 46 47 for (int i = 0; i < filter.channel_size(); ++i) { 48 if (filter.channel(i) == channel) 49 return true; 50 } 51 return false; 52 } 53 54 bool CheckStudyFormFactor(const Study_Filter& filter, 55 Study_FormFactor form_factor) { 56 // An empty form factor list matches all form factors. 57 if (filter.form_factor_size() == 0) 58 return true; 59 60 for (int i = 0; i < filter.form_factor_size(); ++i) { 61 if (filter.form_factor(i) == form_factor) 62 return true; 63 } 64 return false; 65 } 66 67 bool CheckStudyHardwareClass(const Study_Filter& filter, 68 const std::string& hardware_class) { 69 // Empty hardware_class and exclude_hardware_class matches all. 70 if (filter.hardware_class_size() == 0 && 71 filter.exclude_hardware_class_size() == 0) { 72 return true; 73 } 74 75 // Checks if we are supposed to filter for a specified set of 76 // hardware_classes. Note that this means this overrides the 77 // exclude_hardware_class in case that ever occurs (which it shouldn't). 78 if (filter.hardware_class_size() > 0) { 79 for (int i = 0; i < filter.hardware_class_size(); ++i) { 80 // Check if the entry is a substring of |hardware_class|. 81 size_t position = hardware_class.find(filter.hardware_class(i)); 82 if (position != std::string::npos) 83 return true; 84 } 85 // None of the requested hardware_classes match. 86 return false; 87 } 88 89 // Omit if matches any of the exclude entries. 90 for (int i = 0; i < filter.exclude_hardware_class_size(); ++i) { 91 // Check if the entry is a substring of |hardware_class|. 92 size_t position = hardware_class.find( 93 filter.exclude_hardware_class(i)); 94 if (position != std::string::npos) 95 return false; 96 } 97 98 // None of the exclusions match, so this accepts. 99 return true; 100 } 101 102 bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) { 103 // An empty locale list matches all locales. 104 if (filter.locale_size() == 0) 105 return true; 106 107 for (int i = 0; i < filter.locale_size(); ++i) { 108 if (filter.locale(i) == locale) 109 return true; 110 } 111 return false; 112 } 113 114 bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) { 115 // An empty platform list matches all platforms. 116 if (filter.platform_size() == 0) 117 return true; 118 119 for (int i = 0; i < filter.platform_size(); ++i) { 120 if (filter.platform(i) == platform) 121 return true; 122 } 123 return false; 124 } 125 126 bool CheckStudyStartDate(const Study_Filter& filter, 127 const base::Time& date_time) { 128 if (filter.has_start_date()) { 129 const base::Time start_date = 130 ConvertStudyDateToBaseTime(filter.start_date()); 131 return date_time >= start_date; 132 } 133 134 return true; 135 } 136 137 bool CheckStudyVersion(const Study_Filter& filter, 138 const base::Version& version) { 139 if (filter.has_min_version()) { 140 if (version.CompareToWildcardString(filter.min_version()) < 0) 141 return false; 142 } 143 144 if (filter.has_max_version()) { 145 if (version.CompareToWildcardString(filter.max_version()) > 0) 146 return false; 147 } 148 149 return true; 150 } 151 152 bool IsStudyExpired(const Study& study, const base::Time& date_time) { 153 if (study.has_expiry_date()) { 154 const base::Time expiry_date = 155 ConvertStudyDateToBaseTime(study.expiry_date()); 156 return date_time >= expiry_date; 157 } 158 159 return false; 160 } 161 162 bool ShouldAddStudy( 163 const Study& study, 164 const std::string& locale, 165 const base::Time& reference_date, 166 const base::Version& version, 167 Study_Channel channel, 168 Study_FormFactor form_factor, 169 const std::string& hardware_class) { 170 if (study.has_filter()) { 171 if (!CheckStudyChannel(study.filter(), channel)) { 172 DVLOG(1) << "Filtered out study " << study.name() << " due to channel."; 173 return false; 174 } 175 176 if (!CheckStudyFormFactor(study.filter(), form_factor)) { 177 DVLOG(1) << "Filtered out study " << study.name() << 178 " due to form factor."; 179 return false; 180 } 181 182 if (!CheckStudyLocale(study.filter(), locale)) { 183 DVLOG(1) << "Filtered out study " << study.name() << " due to locale."; 184 return false; 185 } 186 187 if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) { 188 DVLOG(1) << "Filtered out study " << study.name() << " due to platform."; 189 return false; 190 } 191 192 if (!CheckStudyVersion(study.filter(), version)) { 193 DVLOG(1) << "Filtered out study " << study.name() << " due to version."; 194 return false; 195 } 196 197 if (!CheckStudyStartDate(study.filter(), reference_date)) { 198 DVLOG(1) << "Filtered out study " << study.name() << 199 " due to start date."; 200 return false; 201 } 202 203 if (!CheckStudyHardwareClass(study.filter(), hardware_class)) { 204 DVLOG(1) << "Filtered out study " << study.name() << 205 " due to hardware_class."; 206 return false; 207 } 208 209 } 210 211 DVLOG(1) << "Kept study " << study.name() << "."; 212 return true; 213 } 214 215 } // namespace internal 216 217 void FilterAndValidateStudies( 218 const VariationsSeed& seed, 219 const std::string& locale, 220 const base::Time& reference_date, 221 const base::Version& version, 222 Study_Channel channel, 223 Study_FormFactor form_factor, 224 const std::string& hardware_class, 225 std::vector<ProcessedStudy>* filtered_studies) { 226 DCHECK(version.IsValid()); 227 228 // Add expired studies (in a disabled state) only after all the non-expired 229 // studies have been added (and do not add an expired study if a corresponding 230 // non-expired study got added). This way, if there's both an expired and a 231 // non-expired study that applies, the non-expired study takes priority. 232 std::set<std::string> created_studies; 233 std::vector<const Study*> expired_studies; 234 235 for (int i = 0; i < seed.study_size(); ++i) { 236 const Study& study = seed.study(i); 237 if (!internal::ShouldAddStudy(study, locale, reference_date, version, 238 channel, form_factor, hardware_class)) { 239 continue; 240 } 241 242 if (internal::IsStudyExpired(study, reference_date)) { 243 expired_studies.push_back(&study); 244 } else if (!ContainsKey(created_studies, study.name())) { 245 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies); 246 created_studies.insert(study.name()); 247 } 248 } 249 250 for (size_t i = 0; i < expired_studies.size(); ++i) { 251 if (!ContainsKey(created_studies, expired_studies[i]->name())) { 252 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true, 253 filtered_studies); 254 } 255 } 256 } 257 258 } // namespace chrome_variations 259