| OLD | NEW |
| (Empty) |
| 1 // Copyright 2011 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 | |
| 16 // Utility class to manage a set of experiment labels. Experiment labels | |
| 17 // are a set of key/value pairs used to track an install's "membership" in | |
| 18 // A/B experiment groups issued by the Omaha server. Keys are strings with | |
| 19 // a limited character set (Perl \w, plus a few characters), while values | |
| 20 // consist of a string and an expiration date. Label sets are stored per-app | |
| 21 // and transmitted to the server as part of requests (pings / update checks), | |
| 22 // and a delta is potentially returned from the server with each response. | |
| 23 // | |
| 24 // Experiment labels are serialized with key/value separated by an equals, and | |
| 25 // value/expiration by a pipe symbol; the expiration is represented in RFC822 | |
| 26 // format. For example: "test_key=test_value|Fri, 14 Aug 2015 16:13:03 GMT". | |
| 27 // If an app participates in multiple experiments simultaneously, labels are | |
| 28 // concatenated with semicolon delimiters. | |
| 29 | |
| 30 #include "omaha/common/experiment_labels.h" | |
| 31 | |
| 32 #include "omaha/base/debug.h" | |
| 33 #include "omaha/base/safe_format.h" | |
| 34 #include "omaha/common/app_registry_utils.h" | |
| 35 | |
| 36 namespace omaha { | |
| 37 | |
| 38 ExperimentLabels::ExperimentLabels() : labels_(), preserve_expired_(false) {} | |
| 39 | |
| 40 ExperimentLabels::~ExperimentLabels() {} | |
| 41 | |
| 42 int ExperimentLabels::NumLabels() const { | |
| 43 return labels_.size(); | |
| 44 } | |
| 45 | |
| 46 bool ExperimentLabels::ContainsKey(const CString& key) const { | |
| 47 ASSERT1(!key.IsEmpty()); | |
| 48 return labels_.find(key) != labels_.end(); | |
| 49 } | |
| 50 | |
| 51 void ExperimentLabels::GetLabelByIndex(int index, CString* key, CString* value, | |
| 52 time64* expiration) const { | |
| 53 ASSERT1(index >= 0); | |
| 54 ASSERT1(static_cast<LabelMap::size_type>(index) < labels_.size()); | |
| 55 | |
| 56 LabelMap::const_iterator cit = labels_.begin(); | |
| 57 std::advance(cit, static_cast<LabelMap::size_type>(index)); | |
| 58 if (key) { | |
| 59 *key = cit->first; | |
| 60 } | |
| 61 if (value) { | |
| 62 *value = cit->second.first; | |
| 63 } | |
| 64 if (expiration) { | |
| 65 *expiration = cit->second.second; | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 bool ExperimentLabels::FindLabelByKey(const CString& key, CString* value, | |
| 70 time64* expiration) const { | |
| 71 ASSERT1(!key.IsEmpty()); | |
| 72 LabelMap::const_iterator cit = labels_.find(key); | |
| 73 if (labels_.end() == cit) { | |
| 74 return false; | |
| 75 } | |
| 76 | |
| 77 if (value) { | |
| 78 *value = cit->second.first; | |
| 79 } | |
| 80 if (expiration) { | |
| 81 *expiration = cit->second.second; | |
| 82 } | |
| 83 return true; | |
| 84 } | |
| 85 | |
| 86 bool ExperimentLabels::SetLabel(const CString& key, const CString& value, | |
| 87 time64 expiration) { | |
| 88 if (!IsLabelContentValid(key) || !IsLabelContentValid(value)) { | |
| 89 return false; | |
| 90 } | |
| 91 if (expiration < GetCurrent100NSTime() && !preserve_expired_) { | |
| 92 return false; | |
| 93 } | |
| 94 labels_[key] = std::make_pair(value, expiration); | |
| 95 return true; | |
| 96 } | |
| 97 | |
| 98 bool ExperimentLabels::ClearLabel(const CString& key) { | |
| 99 LabelMap::iterator it = labels_.find(key); | |
| 100 if (labels_.end() == it) { | |
| 101 return false; | |
| 102 } | |
| 103 labels_.erase(it); | |
| 104 return true; | |
| 105 } | |
| 106 | |
| 107 void ExperimentLabels::ExpireLabels() { | |
| 108 time64 current_time = GetCurrent100NSTime(); | |
| 109 for (LabelMap::iterator it = labels_.begin(); it != labels_.end(); ++it) { | |
| 110 if (it->second.second < current_time) { | |
| 111 it = labels_.erase(it); | |
| 112 if (it == labels_.end()) { | |
| 113 break; | |
| 114 } | |
| 115 } | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 void ExperimentLabels::ClearAllLabels() { | |
| 120 labels_.clear(); | |
| 121 } | |
| 122 | |
| 123 CString ExperimentLabels::Serialize() const { | |
| 124 CString serialized; | |
| 125 time64 current_time = GetCurrent100NSTime(); | |
| 126 for (LabelMap::const_iterator cit = labels_.begin(); | |
| 127 cit != labels_.end(); | |
| 128 ++cit) { | |
| 129 if (preserve_expired_ || cit->second.second >= current_time) { | |
| 130 if (!serialized.IsEmpty()) { | |
| 131 serialized.Append(L";"); | |
| 132 } | |
| 133 FILETIME ft = {}; | |
| 134 Time64ToFileTime(cit->second.second, &ft); | |
| 135 SafeCStringAppendFormat(&serialized, L"%s=%s|%s", | |
| 136 cit->first, | |
| 137 cit->second.first, | |
| 138 ConvertTimeToGMTString(&ft)); | |
| 139 } | |
| 140 } | |
| 141 return serialized; | |
| 142 } | |
| 143 | |
| 144 bool ExperimentLabels::Deserialize(const CString& label_list) { | |
| 145 LabelMap new_labels; | |
| 146 if (DoDeserialize(&new_labels, label_list, preserve_expired_)) { | |
| 147 std::swap(labels_, new_labels); | |
| 148 return true; | |
| 149 } | |
| 150 return false; | |
| 151 } | |
| 152 | |
| 153 bool ExperimentLabels::DeserializeAndApplyDelta(const CString& label_list) { | |
| 154 LabelMap merged_labels = labels_; | |
| 155 if (DoDeserialize(&merged_labels, label_list, false)) { | |
| 156 std::swap(labels_, merged_labels); | |
| 157 return true; | |
| 158 } | |
| 159 return false; | |
| 160 } | |
| 161 | |
| 162 void ExperimentLabels::SetPreserveExpiredLabels(bool preserve) { | |
| 163 preserve_expired_ = preserve; | |
| 164 } | |
| 165 | |
| 166 HRESULT ExperimentLabels::WriteToRegistry(bool is_machine, | |
| 167 const CString& app_id) { | |
| 168 return app_registry_utils::SetExperimentLabels(is_machine, app_id, | |
| 169 Serialize()); | |
| 170 } | |
| 171 | |
| 172 HRESULT ExperimentLabels::ReadFromRegistry(bool is_machine, | |
| 173 const CString& app_id) { | |
| 174 CString label_list; | |
| 175 HRESULT hr = app_registry_utils::GetExperimentLabels(is_machine, app_id, | |
| 176 &label_list); | |
| 177 if (FAILED(hr)) { | |
| 178 return hr; | |
| 179 } | |
| 180 return Deserialize(label_list) ? S_OK : E_FAIL; | |
| 181 } | |
| 182 | |
| 183 bool ExperimentLabels::IsStringValidLabelSet(const CString& label_list) { | |
| 184 ExperimentLabels labels; | |
| 185 return labels.Deserialize(label_list); | |
| 186 } | |
| 187 | |
| 188 bool ExperimentLabels::IsLabelContentValid(const CString& str) { | |
| 189 if (str.IsEmpty()) { | |
| 190 return false; | |
| 191 } | |
| 192 for (int i = 0; i < str.GetLength(); ++i) { | |
| 193 wchar_t ch = str[i]; | |
| 194 if (!((ch >= L'+' && ch <= L'-') || | |
| 195 (ch >= L'0' && ch <= L':') || | |
| 196 (ch >= L'A' && ch <= L'Z') || | |
| 197 (ch >= L'a' && ch <= L'z') || | |
| 198 (ch == L'_') || (ch == L' '))) { | |
| 199 return false; | |
| 200 } | |
| 201 } | |
| 202 return true; | |
| 203 } | |
| 204 | |
| 205 bool ExperimentLabels::SplitCombinedLabel(const CString& combined, CString* key, | |
| 206 CString* value, time64* expiration) { | |
| 207 ASSERT1(!combined.IsEmpty()); | |
| 208 ASSERT1(key); | |
| 209 ASSERT1(value); | |
| 210 ASSERT1(expiration); | |
| 211 | |
| 212 int value_offset = combined.Find(L'='); | |
| 213 if (value_offset <= 0 || value_offset == combined.GetLength()) { | |
| 214 return false; | |
| 215 } | |
| 216 *key = combined.Left(value_offset); | |
| 217 if (!IsLabelContentValid(*key)) { | |
| 218 return false; | |
| 219 } | |
| 220 ++value_offset; | |
| 221 | |
| 222 int expiration_offset = combined.Find(L'|', value_offset); | |
| 223 if (expiration_offset <= value_offset || | |
| 224 expiration_offset == combined.GetLength()) { | |
| 225 return false; | |
| 226 } | |
| 227 *value = combined.Mid(value_offset, expiration_offset - value_offset); | |
| 228 if (!IsLabelContentValid(*value)) { | |
| 229 return false; | |
| 230 } | |
| 231 ++expiration_offset; | |
| 232 | |
| 233 CString expiration_string = combined.Mid(expiration_offset); | |
| 234 if (!IsLabelContentValid(expiration_string)) { | |
| 235 return false; | |
| 236 } | |
| 237 SYSTEMTIME system_time = {}; | |
| 238 if (!RFC822DateToSystemTime(expiration_string, &system_time, false)) { | |
| 239 return false; | |
| 240 } | |
| 241 *expiration = SystemTimeToTime64(&system_time); | |
| 242 | |
| 243 return true; | |
| 244 } | |
| 245 | |
| 246 bool ExperimentLabels::DoDeserialize(LabelMap* map, const CString& label_list, | |
| 247 bool accept_expired) { | |
| 248 ASSERT1(map); | |
| 249 | |
| 250 if (label_list.IsEmpty()) { | |
| 251 return true; | |
| 252 } | |
| 253 | |
| 254 time64 current_time = GetCurrent100NSTime(); | |
| 255 | |
| 256 for (int offset = 0;;) { | |
| 257 CString combined_label = label_list.Tokenize(L";", offset); | |
| 258 if (combined_label.IsEmpty()) { | |
| 259 if (offset < 0) { | |
| 260 break; // Natural end-of-string reached. | |
| 261 } | |
| 262 return false; | |
| 263 } | |
| 264 | |
| 265 CString key; | |
| 266 CString value; | |
| 267 time64 expiration = 0; | |
| 268 if (!SplitCombinedLabel(combined_label, &key, &value, &expiration)) { | |
| 269 return false; | |
| 270 } | |
| 271 | |
| 272 // If the label is well-formatted but expired, we accept the input, but | |
| 273 // do not add it to the map and do not emit an error. If there is already | |
| 274 // a label in the map with that key, delete it. | |
| 275 if (accept_expired || expiration > current_time) { | |
| 276 (*map)[key] = std::make_pair(value, expiration); | |
| 277 } else { | |
| 278 map->erase(key); | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 return true; | |
| 283 } | |
| 284 | |
| 285 } // namespace omaha | |
| 286 | |
| OLD | NEW |