| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "chromeos/network/onc/onc_validator.h" | 5 #include "chromeos/network/onc/onc_validator.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <string> | 8 #include <string> |
| 9 | 9 |
| 10 #include "base/json/json_writer.h" | 10 #include "base/json/json_writer.h" |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 85 } | 85 } |
| 86 | 86 |
| 87 return make_scoped_ptr(result_dict); | 87 return make_scoped_ptr(result_dict); |
| 88 } | 88 } |
| 89 | 89 |
| 90 scoped_ptr<base::Value> Validator::MapValue( | 90 scoped_ptr<base::Value> Validator::MapValue( |
| 91 const OncValueSignature& signature, | 91 const OncValueSignature& signature, |
| 92 const base::Value& onc_value, | 92 const base::Value& onc_value, |
| 93 bool* error) { | 93 bool* error) { |
| 94 if (onc_value.GetType() != signature.onc_type) { | 94 if (onc_value.GetType() != signature.onc_type) { |
| 95 LOG(ERROR) << ErrorHeader() << "Found value '" << onc_value | 95 LOG(ERROR) << MessageHeader() << "Found value '" << onc_value |
| 96 << "' of type '" << ValueTypeToString(onc_value.GetType()) | 96 << "' of type '" << ValueTypeToString(onc_value.GetType()) |
| 97 << "', but type '" << ValueTypeToString(signature.onc_type) | 97 << "', but type '" << ValueTypeToString(signature.onc_type) |
| 98 << "' is required."; | 98 << "' is required."; |
| 99 error_or_warning_found_ = *error = true; | 99 error_or_warning_found_ = *error = true; |
| 100 return scoped_ptr<base::Value>(); | 100 return scoped_ptr<base::Value>(); |
| 101 } | 101 } |
| 102 | 102 |
| 103 scoped_ptr<base::Value> repaired = | 103 scoped_ptr<base::Value> repaired = |
| 104 Mapper::MapValue(signature, onc_value, error); | 104 Mapper::MapValue(signature, onc_value, error); |
| 105 if (repaired.get() != NULL) | 105 if (repaired.get() != NULL) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 161 path_.push_back(field_name); | 161 path_.push_back(field_name); |
| 162 bool current_field_unknown = false; | 162 bool current_field_unknown = false; |
| 163 scoped_ptr<base::Value> result = Mapper::MapField( | 163 scoped_ptr<base::Value> result = Mapper::MapField( |
| 164 field_name, object_signature, onc_value, ¤t_field_unknown, error); | 164 field_name, object_signature, onc_value, ¤t_field_unknown, error); |
| 165 | 165 |
| 166 DCHECK_EQ(field_name, path_.back()); | 166 DCHECK_EQ(field_name, path_.back()); |
| 167 path_.pop_back(); | 167 path_.pop_back(); |
| 168 | 168 |
| 169 if (current_field_unknown) { | 169 if (current_field_unknown) { |
| 170 error_or_warning_found_ = *found_unknown_field = true; | 170 error_or_warning_found_ = *found_unknown_field = true; |
| 171 std::string message = MessageHeader(error_on_unknown_field_) | 171 std::string message = MessageHeader() + "Field name '" + field_name + |
| 172 + "Field name '" + field_name + "' is unknown."; | 172 "' is unknown."; |
| 173 if (error_on_unknown_field_) | 173 if (error_on_unknown_field_) |
| 174 LOG(ERROR) << message; | 174 LOG(ERROR) << message; |
| 175 else | 175 else |
| 176 LOG(WARNING) << message; | 176 LOG(WARNING) << message; |
| 177 } | 177 } |
| 178 | 178 |
| 179 return result.Pass(); | 179 return result.Pass(); |
| 180 } | 180 } |
| 181 | 181 |
| 182 scoped_ptr<base::ListValue> Validator::MapArray( | 182 scoped_ptr<base::ListValue> Validator::MapArray( |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 243 return true; | 243 return true; |
| 244 } | 244 } |
| 245 base::ListValue* recommended_list = NULL; | 245 base::ListValue* recommended_list = NULL; |
| 246 recommended_value->GetAsList(&recommended_list); | 246 recommended_value->GetAsList(&recommended_list); |
| 247 CHECK(recommended_list != NULL); | 247 CHECK(recommended_list != NULL); |
| 248 | 248 |
| 249 recommended.reset(recommended_list); | 249 recommended.reset(recommended_list); |
| 250 | 250 |
| 251 if (!managed_onc_) { | 251 if (!managed_onc_) { |
| 252 error_or_warning_found_ = true; | 252 error_or_warning_found_ = true; |
| 253 LOG(WARNING) << WarningHeader() << "Found the field '" << onc::kRecommended | 253 LOG(WARNING) << MessageHeader() << "Found the field '" << onc::kRecommended |
| 254 << "' in an unmanaged ONC. Removing it."; | 254 << "' in an unmanaged ONC. Removing it."; |
| 255 return true; | 255 return true; |
| 256 } | 256 } |
| 257 | 257 |
| 258 scoped_ptr<base::ListValue> repaired_recommended(new base::ListValue); | 258 scoped_ptr<base::ListValue> repaired_recommended(new base::ListValue); |
| 259 for (base::ListValue::iterator it = recommended->begin(); | 259 for (base::ListValue::iterator it = recommended->begin(); |
| 260 it != recommended->end(); ++it) { | 260 it != recommended->end(); ++it) { |
| 261 std::string field_name; | 261 std::string field_name; |
| 262 if (!(*it)->GetAsString(&field_name)) { | 262 if (!(*it)->GetAsString(&field_name)) { |
| 263 NOTREACHED(); | 263 NOTREACHED(); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 274 error_cause = "unknown"; | 274 error_cause = "unknown"; |
| 275 } else if (field_signature->value_signature->onc_type == | 275 } else if (field_signature->value_signature->onc_type == |
| 276 base::Value::TYPE_DICTIONARY) { | 276 base::Value::TYPE_DICTIONARY) { |
| 277 found_error = true; | 277 found_error = true; |
| 278 error_cause = "dictionary-typed"; | 278 error_cause = "dictionary-typed"; |
| 279 } | 279 } |
| 280 | 280 |
| 281 if (found_error) { | 281 if (found_error) { |
| 282 error_or_warning_found_ = true; | 282 error_or_warning_found_ = true; |
| 283 path_.push_back(onc::kRecommended); | 283 path_.push_back(onc::kRecommended); |
| 284 std::string message = MessageHeader(error_on_wrong_recommended_) + | 284 std::string message = MessageHeader() + "The " + error_cause + |
| 285 "The " + error_cause + " field '" + field_name + | 285 " field '" + field_name + "' cannot be recommended."; |
| 286 "' cannot be recommended."; | |
| 287 path_.pop_back(); | 286 path_.pop_back(); |
| 288 if (error_on_wrong_recommended_) { | 287 if (error_on_wrong_recommended_) { |
| 289 LOG(ERROR) << message; | 288 LOG(ERROR) << message; |
| 290 return false; | 289 return false; |
| 291 } else { | 290 } else { |
| 292 LOG(WARNING) << message; | 291 LOG(WARNING) << message; |
| 293 continue; | 292 continue; |
| 294 } | 293 } |
| 295 } | 294 } |
| 296 | 295 |
| (...skipping 26 matching lines...) Expand all Loading... |
| 323 | 322 |
| 324 const char** it = valid_values; | 323 const char** it = valid_values; |
| 325 for (; *it != NULL; ++it) { | 324 for (; *it != NULL; ++it) { |
| 326 if (actual_value == *it) | 325 if (actual_value == *it) |
| 327 return false; | 326 return false; |
| 328 } | 327 } |
| 329 error_or_warning_found_ = true; | 328 error_or_warning_found_ = true; |
| 330 std::string valid_values_str = | 329 std::string valid_values_str = |
| 331 "[" + JoinStringRange(valid_values, it, ", ") + "]"; | 330 "[" + JoinStringRange(valid_values, it, ", ") + "]"; |
| 332 path_.push_back(field_name); | 331 path_.push_back(field_name); |
| 333 LOG(ERROR) << ErrorHeader() << "Found value '" << actual_value << | 332 LOG(ERROR) << MessageHeader() << "Found value '" << actual_value << |
| 334 "', but expected one of the values " << valid_values_str; | 333 "', but expected one of the values " << valid_values_str; |
| 335 path_.pop_back(); | 334 path_.pop_back(); |
| 336 return true; | 335 return true; |
| 337 } | 336 } |
| 338 | 337 |
| 339 bool Validator::FieldExistsAndIsNotInRange(const base::DictionaryValue& object, | 338 bool Validator::FieldExistsAndIsNotInRange(const base::DictionaryValue& object, |
| 340 const std::string &field_name, | 339 const std::string &field_name, |
| 341 int lower_bound, | 340 int lower_bound, |
| 342 int upper_bound) { | 341 int upper_bound) { |
| 343 int actual_value; | 342 int actual_value; |
| 344 if (!object.GetIntegerWithoutPathExpansion(field_name, &actual_value) || | 343 if (!object.GetIntegerWithoutPathExpansion(field_name, &actual_value) || |
| 345 (lower_bound <= actual_value && actual_value <= upper_bound)) { | 344 (lower_bound <= actual_value && actual_value <= upper_bound)) { |
| 346 return false; | 345 return false; |
| 347 } | 346 } |
| 348 error_or_warning_found_ = true; | 347 error_or_warning_found_ = true; |
| 349 path_.push_back(field_name); | 348 path_.push_back(field_name); |
| 350 LOG(ERROR) << ErrorHeader() << "Found value '" << actual_value | 349 LOG(ERROR) << MessageHeader() << "Found value '" << actual_value |
| 351 << "', but expected a value in the range [" << lower_bound | 350 << "', but expected a value in the range [" << lower_bound |
| 352 << ", " << upper_bound << "] (boundaries inclusive)"; | 351 << ", " << upper_bound << "] (boundaries inclusive)"; |
| 353 path_.pop_back(); | 352 path_.pop_back(); |
| 354 return true; | 353 return true; |
| 355 } | 354 } |
| 356 | 355 |
| 357 bool Validator::FieldExistsAndIsEmpty(const base::DictionaryValue& object, | 356 bool Validator::FieldExistsAndIsEmpty(const base::DictionaryValue& object, |
| 358 const std::string& field_name) { | 357 const std::string& field_name) { |
| 359 std::string value; | 358 std::string value; |
| 360 if (!object.GetStringWithoutPathExpansion(field_name, &value) || | 359 if (!object.GetStringWithoutPathExpansion(field_name, &value) || |
| 361 !value.empty()) { | 360 !value.empty()) { |
| 362 return false; | 361 return false; |
| 363 } | 362 } |
| 364 | 363 |
| 365 error_or_warning_found_ = true; | 364 error_or_warning_found_ = true; |
| 366 path_.push_back(field_name); | 365 path_.push_back(field_name); |
| 367 LOG(ERROR) << ErrorHeader() << "Found an empty string, but expected a " | 366 LOG(ERROR) << MessageHeader() << "Found an empty string, but expected a " |
| 368 << "non-empty string."; | 367 << "non-empty string."; |
| 369 path_.pop_back(); | 368 path_.pop_back(); |
| 370 return true; | 369 return true; |
| 371 } | 370 } |
| 372 | 371 |
| 373 bool Validator::RequireField(const base::DictionaryValue& dict, | 372 bool Validator::RequireField(const base::DictionaryValue& dict, |
| 374 const std::string& field_name) { | 373 const std::string& field_name) { |
| 375 if (dict.HasKey(field_name)) | 374 if (dict.HasKey(field_name)) |
| 376 return true; | 375 return true; |
| 377 error_or_warning_found_ = true; | 376 error_or_warning_found_ = true; |
| 378 LOG(ERROR) << ErrorHeader() << "The required field '" << field_name | 377 std::string message = MessageHeader() + "The required field '" + field_name + |
| 379 << "' is missing."; | 378 "' is missing."; |
| 379 if (error_on_missing_field_) |
| 380 LOG(ERROR) << message; |
| 381 else |
| 382 LOG(WARNING) << message; |
| 380 return false; | 383 return false; |
| 381 } | 384 } |
| 382 | 385 |
| 383 // Prohibit certificate patterns for device policy ONC so that an unmanaged user | 386 // Prohibit certificate patterns for device policy ONC so that an unmanaged user |
| 384 // won't have a certificate presented for them involuntarily. | 387 // won't have a certificate presented for them involuntarily. |
| 385 bool Validator::CertPatternInDevicePolicy(const std::string& cert_type) { | 388 bool Validator::CertPatternInDevicePolicy(const std::string& cert_type) { |
| 386 if (cert_type == certificate::kPattern && | 389 if (cert_type == certificate::kPattern && |
| 387 onc_source_ == ONC_SOURCE_DEVICE_POLICY) { | 390 onc_source_ == ONC_SOURCE_DEVICE_POLICY) { |
| 388 error_or_warning_found_ = true; | 391 error_or_warning_found_ = true; |
| 389 LOG(ERROR) << ErrorHeader() << "Client certificate patterns are " | 392 LOG(ERROR) << MessageHeader() << "Client certificate patterns are " |
| 390 << "prohibited in ONC device policies."; | 393 << "prohibited in ONC device policies."; |
| 391 return true; | 394 return true; |
| 392 } | 395 } |
| 393 return false; | 396 return false; |
| 394 } | 397 } |
| 395 | 398 |
| 396 bool Validator::ValidateToplevelConfiguration( | 399 bool Validator::ValidateToplevelConfiguration( |
| 397 const base::DictionaryValue& onc_object, | 400 const base::DictionaryValue& onc_object, |
| 398 base::DictionaryValue* result) { | 401 base::DictionaryValue* result) { |
| 399 using namespace onc::toplevel_config; | 402 using namespace onc::toplevel_config; |
| 400 | 403 |
| 401 static const char* kValidTypes[] = { kUnencryptedConfiguration, | 404 static const char* kValidTypes[] = { kUnencryptedConfiguration, |
| 402 kEncryptedConfiguration, | 405 kEncryptedConfiguration, |
| 403 NULL }; | 406 NULL }; |
| 404 if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes)) | 407 if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes)) |
| 405 return false; | 408 return false; |
| 406 | 409 |
| 407 bool allRequiredExist = true; | 410 bool allRequiredExist = true; |
| 408 | 411 |
| 409 // Not part of the ONC spec yet: | 412 // Not part of the ONC spec yet: |
| 410 // We don't require the type field and default to UnencryptedConfiguration. | 413 // We don't require the type field and default to UnencryptedConfiguration. |
| 411 std::string type = kUnencryptedConfiguration; | 414 std::string type = kUnencryptedConfiguration; |
| 412 result->GetStringWithoutPathExpansion(kType, &type); | 415 result->GetStringWithoutPathExpansion(kType, &type); |
| 413 if (type == kUnencryptedConfiguration && | 416 if (type == kUnencryptedConfiguration && |
| 414 !result->HasKey(kNetworkConfigurations) && | 417 !result->HasKey(kNetworkConfigurations) && |
| 415 !result->HasKey(kCertificates)) { | 418 !result->HasKey(kCertificates)) { |
| 416 error_or_warning_found_ = true; | 419 error_or_warning_found_ = true; |
| 417 std::string message = MessageHeader(error_on_missing_field_) + | 420 std::string message = MessageHeader() + "Neither the field '" + |
| 418 "Neither the field '" + kNetworkConfigurations + "' nor '" + | 421 kNetworkConfigurations + "' nor '" + kCertificates + |
| 419 kCertificates + "is present, but at least one is required."; | 422 "is present, but at least one is required."; |
| 420 if (error_on_missing_field_) | 423 if (error_on_missing_field_) |
| 421 LOG(ERROR) << message; | 424 LOG(ERROR) << message; |
| 422 else | 425 else |
| 423 LOG(WARNING) << message; | 426 LOG(WARNING) << message; |
| 424 allRequiredExist = false; | 427 allRequiredExist = false; |
| 425 } | 428 } |
| 426 | 429 |
| 427 return !error_on_missing_field_ || allRequiredExist; | 430 return !error_on_missing_field_ || allRequiredExist; |
| 428 } | 431 } |
| 429 | 432 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 452 | 455 |
| 453 std::string type; | 456 std::string type; |
| 454 result->GetStringWithoutPathExpansion(kType, &type); | 457 result->GetStringWithoutPathExpansion(kType, &type); |
| 455 | 458 |
| 456 // Prohibit anything but WiFi and Ethernet for device-level policy (which | 459 // Prohibit anything but WiFi and Ethernet for device-level policy (which |
| 457 // corresponds to shared networks). See also http://crosbug.com/28741. | 460 // corresponds to shared networks). See also http://crosbug.com/28741. |
| 458 if (onc_source_ == ONC_SOURCE_DEVICE_POLICY && | 461 if (onc_source_ == ONC_SOURCE_DEVICE_POLICY && |
| 459 type != network_type::kWiFi && | 462 type != network_type::kWiFi && |
| 460 type != network_type::kEthernet) { | 463 type != network_type::kEthernet) { |
| 461 error_or_warning_found_ = true; | 464 error_or_warning_found_ = true; |
| 462 LOG(ERROR) << ErrorHeader() << "Networks of type '" | 465 LOG(ERROR) << MessageHeader() << "Networks of type '" |
| 463 << type << "' are prohibited in ONC device policies."; | 466 << type << "' are prohibited in ONC device policies."; |
| 464 return false; | 467 return false; |
| 465 } | 468 } |
| 466 | 469 |
| 467 if (type == network_type::kWiFi) | 470 if (type == network_type::kWiFi) |
| 468 allRequiredExist &= RequireField(*result, network_config::kWiFi); | 471 allRequiredExist &= RequireField(*result, network_config::kWiFi); |
| 469 else if (type == network_type::kEthernet) | 472 else if (type == network_type::kEthernet) |
| 470 allRequiredExist &= RequireField(*result, network_config::kEthernet); | 473 allRequiredExist &= RequireField(*result, network_config::kEthernet); |
| 471 else if (type == network_type::kCellular) | 474 else if (type == network_type::kCellular) |
| 472 allRequiredExist &= RequireField(*result, network_config::kCellular); | 475 allRequiredExist &= RequireField(*result, network_config::kCellular); |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 652 bool Validator::ValidateCertificatePattern( | 655 bool Validator::ValidateCertificatePattern( |
| 653 const base::DictionaryValue& onc_object, | 656 const base::DictionaryValue& onc_object, |
| 654 base::DictionaryValue* result) { | 657 base::DictionaryValue* result) { |
| 655 using namespace onc::certificate; | 658 using namespace onc::certificate; |
| 656 | 659 |
| 657 bool allRequiredExist = true; | 660 bool allRequiredExist = true; |
| 658 if (!result->HasKey(kSubject) && !result->HasKey(kIssuer) && | 661 if (!result->HasKey(kSubject) && !result->HasKey(kIssuer) && |
| 659 !result->HasKey(kIssuerCARef)) { | 662 !result->HasKey(kIssuerCARef)) { |
| 660 error_or_warning_found_ = true; | 663 error_or_warning_found_ = true; |
| 661 allRequiredExist = false; | 664 allRequiredExist = false; |
| 662 std::string message = MessageHeader(error_on_missing_field_) + | 665 std::string message = MessageHeader() + "None of the fields '" + kSubject + |
| 663 "None of the fields '" + kSubject + "', '" + kIssuer + "', and '" + | 666 "', '" + kIssuer + "', and '" + kIssuerCARef + |
| 664 kIssuerCARef + "' is present, but at least one is required."; | 667 "' is present, but at least one is required."; |
| 665 if (error_on_missing_field_) | 668 if (error_on_missing_field_) |
| 666 LOG(ERROR) << message; | 669 LOG(ERROR) << message; |
| 667 else | 670 else |
| 668 LOG(WARNING) << message; | 671 LOG(WARNING) << message; |
| 669 } | 672 } |
| 670 | 673 |
| 671 return !error_on_missing_field_ || allRequiredExist; | 674 return !error_on_missing_field_ || allRequiredExist; |
| 672 } | 675 } |
| 673 | 676 |
| 674 bool Validator::ValidateProxySettings(const base::DictionaryValue& onc_object, | 677 bool Validator::ValidateProxySettings(const base::DictionaryValue& onc_object, |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 744 if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes) || | 747 if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes) || |
| 745 FieldExistsAndIsEmpty(*result, kGUID)) { | 748 FieldExistsAndIsEmpty(*result, kGUID)) { |
| 746 return false; | 749 return false; |
| 747 } | 750 } |
| 748 | 751 |
| 749 std::string type; | 752 std::string type; |
| 750 result->GetStringWithoutPathExpansion(kType, &type); | 753 result->GetStringWithoutPathExpansion(kType, &type); |
| 751 if (onc_source_ == ONC_SOURCE_DEVICE_POLICY && | 754 if (onc_source_ == ONC_SOURCE_DEVICE_POLICY && |
| 752 (type == kServer || type == kAuthority)) { | 755 (type == kServer || type == kAuthority)) { |
| 753 error_or_warning_found_ = true; | 756 error_or_warning_found_ = true; |
| 754 LOG(ERROR) << ErrorHeader() << "Server and authority certificates are " | 757 LOG(ERROR) << MessageHeader() << "Server and authority certificates are " |
| 755 << "prohibited in ONC device policies."; | 758 << "prohibited in ONC device policies."; |
| 756 return false; | 759 return false; |
| 757 } | 760 } |
| 758 | 761 |
| 759 bool allRequiredExist = RequireField(*result, kGUID); | 762 bool allRequiredExist = RequireField(*result, kGUID); |
| 760 | 763 |
| 761 bool remove = false; | 764 bool remove = false; |
| 762 result->GetBooleanWithoutPathExpansion(kRemove, &remove); | 765 result->GetBooleanWithoutPathExpansion(kRemove, &remove); |
| 763 if (!remove) { | 766 if (!remove) { |
| 764 allRequiredExist &= RequireField(*result, kType); | 767 allRequiredExist &= RequireField(*result, kType); |
| 765 | 768 |
| 766 if (type == kClient) | 769 if (type == kClient) |
| 767 allRequiredExist &= RequireField(*result, kPKCS12); | 770 allRequiredExist &= RequireField(*result, kPKCS12); |
| 768 else if (type == kServer || type == kAuthority) | 771 else if (type == kServer || type == kAuthority) |
| 769 allRequiredExist &= RequireField(*result, kX509); | 772 allRequiredExist &= RequireField(*result, kX509); |
| 770 } | 773 } |
| 771 | 774 |
| 772 return !error_on_missing_field_ || allRequiredExist; | 775 return !error_on_missing_field_ || allRequiredExist; |
| 773 } | 776 } |
| 774 | 777 |
| 775 std::string Validator::WarningHeader() { | 778 std::string Validator::MessageHeader() { |
| 776 return MessageHeader(false); | |
| 777 } | |
| 778 | |
| 779 std::string Validator::ErrorHeader() { | |
| 780 return MessageHeader(true); | |
| 781 } | |
| 782 | |
| 783 std::string Validator::MessageHeader(bool is_error) { | |
| 784 std::string path = path_.empty() ? "toplevel" : JoinString(path_, "."); | 779 std::string path = path_.empty() ? "toplevel" : JoinString(path_, "."); |
| 785 std::string message = "At " + path + ": "; | 780 std::string message = "At " + path + ": "; |
| 786 return message; | 781 return message; |
| 787 } | 782 } |
| 788 | 783 |
| 789 } // namespace onc | 784 } // namespace onc |
| 790 } // namespace chromeos | 785 } // namespace chromeos |
| OLD | NEW |