| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 "chrome/browser/autofill/credit_card.h" | 5 #include "chrome/browser/autofill/credit_card.h" |
| 6 | 6 |
| 7 #include "app/l10n_util.h" | 7 #include "app/l10n_util.h" |
| 8 #include "base/basictypes.h" | 8 #include "base/basictypes.h" |
| 9 #include "base/string_util.h" | 9 #include "base/string_util.h" |
| 10 #include "base/utf_string_conversions.h" | 10 #include "base/utf_string_conversions.h" |
| 11 #include "chrome/browser/autofill/autofill_type.h" | 11 #include "chrome/browser/autofill/autofill_type.h" |
| 12 #include "chrome/browser/autofill/field_types.h" | 12 #include "chrome/browser/autofill/field_types.h" |
| 13 #include "grit/generated_resources.h" | 13 #include "grit/generated_resources.h" |
| 14 | 14 |
| 15 static const string16 kCreditCardSeparators = ASCIIToUTF16(" -"); | 15 static const string16 kCreditCardSeparators = ASCIIToUTF16(" -"); |
| 16 static const char* kCreditCardObfuscationString = "************"; | 16 static const char* kCreditCardObfuscationString = "************"; |
| 17 | 17 |
| 18 static const AutoFillFieldType kAutoFillCreditCardTypes[] = { | 18 static const AutoFillFieldType kAutoFillCreditCardTypes[] = { |
| 19 CREDIT_CARD_NAME, | 19 CREDIT_CARD_NAME, |
| 20 CREDIT_CARD_NUMBER, | 20 CREDIT_CARD_NUMBER, |
| 21 CREDIT_CARD_TYPE, | 21 CREDIT_CARD_TYPE, |
| 22 CREDIT_CARD_EXP_MONTH, | 22 CREDIT_CARD_EXP_MONTH, |
| 23 CREDIT_CARD_EXP_4_DIGIT_YEAR, | 23 CREDIT_CARD_EXP_4_DIGIT_YEAR, |
| 24 CREDIT_CARD_VERIFICATION_CODE, | |
| 25 }; | 24 }; |
| 26 | 25 |
| 27 static const int kAutoFillCreditCardLength = | 26 static const int kAutoFillCreditCardLength = |
| 28 arraysize(kAutoFillCreditCardTypes); | 27 arraysize(kAutoFillCreditCardTypes); |
| 29 | 28 |
| 30 CreditCard::CreditCard(const string16& label, int unique_id) | 29 CreditCard::CreditCard(const string16& label, int unique_id) |
| 31 : expiration_month_(0), | 30 : expiration_month_(0), |
| 32 expiration_year_(0), | 31 expiration_year_(0), |
| 33 label_(label), | 32 label_(label), |
| 34 unique_id_(unique_id) { | 33 unique_id_(unique_id) { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 59 possible_types->insert(CREDIT_CARD_EXP_MONTH); | 58 possible_types->insert(CREDIT_CARD_EXP_MONTH); |
| 60 | 59 |
| 61 if (Is2DigitExpirationYear(text)) | 60 if (Is2DigitExpirationYear(text)) |
| 62 possible_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); | 61 possible_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); |
| 63 | 62 |
| 64 if (Is4DigitExpirationYear(text)) | 63 if (Is4DigitExpirationYear(text)) |
| 65 possible_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); | 64 possible_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); |
| 66 | 65 |
| 67 if (IsCardType(text)) | 66 if (IsCardType(text)) |
| 68 possible_types->insert(CREDIT_CARD_TYPE); | 67 possible_types->insert(CREDIT_CARD_TYPE); |
| 68 } |
| 69 | 69 |
| 70 if (IsVerificationCode(text)) | 70 void CreditCard::GetAvailableFieldTypes(FieldTypeSet* available_types) const { |
| 71 possible_types->insert(CREDIT_CARD_VERIFICATION_CODE); | 71 DCHECK(available_types); |
| 72 |
| 73 if (!name_on_card().empty()) |
| 74 available_types->insert(CREDIT_CARD_NAME); |
| 75 |
| 76 if (!ExpirationMonthAsString().empty()) |
| 77 available_types->insert(CREDIT_CARD_EXP_MONTH); |
| 78 |
| 79 if (!Expiration2DigitYearAsString().empty()) |
| 80 available_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); |
| 81 |
| 82 if (!Expiration4DigitYearAsString().empty()) |
| 83 available_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); |
| 84 |
| 85 if (!type().empty()) |
| 86 available_types->insert(CREDIT_CARD_TYPE); |
| 87 |
| 88 if (!number().empty()) |
| 89 available_types->insert(CREDIT_CARD_NUMBER); |
| 72 } | 90 } |
| 73 | 91 |
| 74 void CreditCard::FindInfoMatches(const AutoFillType& type, | 92 void CreditCard::FindInfoMatches(const AutoFillType& type, |
| 75 const string16& info, | 93 const string16& info, |
| 76 std::vector<string16>* matched_text) const { | 94 std::vector<string16>* matched_text) const { |
| 77 if (matched_text == NULL) { | 95 DCHECK(matched_text); |
| 78 DLOG(ERROR) << "NULL matched vector passed in"; | |
| 79 return; | |
| 80 } | |
| 81 | 96 |
| 82 string16 match; | 97 string16 match; |
| 83 switch (type.field_type()) { | 98 switch (type.field_type()) { |
| 84 case CREDIT_CARD_NUMBER: { | 99 case CREDIT_CARD_NUMBER: { |
| 85 // Because the credit card number is encrypted and we are not able to do | 100 // Because the credit card number is encrypted and we are not able to do |
| 86 // comparisons with it we will say that any field that is known to be a | 101 // comparisons with it we will say that any field that is known to be a |
| 87 // credit card number field will match all credit card numbers. | 102 // credit card number field will match all credit card numbers. |
| 88 string16 text = GetPreviewText(AutoFillType(CREDIT_CARD_NUMBER)); | 103 string16 text = GetPreviewText(AutoFillType(CREDIT_CARD_NUMBER)); |
| 89 if (!text.empty()) | 104 if (!text.empty()) |
| 90 matched_text->push_back(text); | 105 matched_text->push_back(text); |
| 91 break; | 106 break; |
| 92 } | 107 } |
| 93 | 108 |
| 94 case CREDIT_CARD_VERIFICATION_CODE: | 109 case CREDIT_CARD_VERIFICATION_CODE: |
| 110 NOTREACHED(); |
| 95 break; | 111 break; |
| 96 | 112 |
| 97 case UNKNOWN_TYPE: | 113 case UNKNOWN_TYPE: |
| 98 for (int i = 0; i < kAutoFillCreditCardLength; ++i) { | 114 for (int i = 0; i < kAutoFillCreditCardLength; ++i) { |
| 99 if (FindInfoMatchesHelper(kAutoFillCreditCardTypes[i], info, &match)) | 115 if (FindInfoMatchesHelper(kAutoFillCreditCardTypes[i], info, &match)) |
| 100 matched_text->push_back(match); | 116 matched_text->push_back(match); |
| 101 } | 117 } |
| 102 break; | 118 break; |
| 103 | 119 |
| 104 default: | 120 default: |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 138 return string16(); | 154 return string16(); |
| 139 } | 155 } |
| 140 | 156 |
| 141 case CREDIT_CARD_TYPE: | 157 case CREDIT_CARD_TYPE: |
| 142 return this->type(); | 158 return this->type(); |
| 143 | 159 |
| 144 case CREDIT_CARD_NUMBER: | 160 case CREDIT_CARD_NUMBER: |
| 145 return number(); | 161 return number(); |
| 146 | 162 |
| 147 case CREDIT_CARD_VERIFICATION_CODE: | 163 case CREDIT_CARD_VERIFICATION_CODE: |
| 148 return verification_code(); | 164 NOTREACHED(); |
| 165 return string16(); |
| 149 | 166 |
| 150 default: | 167 default: |
| 151 // ComputeDataPresentForArray will hit this repeatedly. | 168 // ComputeDataPresentForArray will hit this repeatedly. |
| 152 return string16(); | 169 return string16(); |
| 153 } | 170 } |
| 154 } | 171 } |
| 155 | 172 |
| 156 string16 CreditCard::GetPreviewText(const AutoFillType& type) const { | 173 string16 CreditCard::GetPreviewText(const AutoFillType& type) const { |
| 157 switch (type.field_type()) { | 174 switch (type.field_type()) { |
| 158 case CREDIT_CARD_NUMBER: | 175 case CREDIT_CARD_NUMBER: |
| 159 return last_four_digits(); | 176 return last_four_digits(); |
| 160 | 177 |
| 161 case CREDIT_CARD_VERIFICATION_CODE: | 178 case CREDIT_CARD_VERIFICATION_CODE: |
| 162 return ASCIIToUTF16("xxx"); | 179 NOTREACHED(); |
| 180 return string16(); |
| 163 | 181 |
| 164 default: | 182 default: |
| 165 return GetFieldText(type); | 183 return GetFieldText(type); |
| 166 } | 184 } |
| 167 } | 185 } |
| 168 | 186 |
| 169 void CreditCard::SetInfo(const AutoFillType& type, const string16& value) { | 187 void CreditCard::SetInfo(const AutoFillType& type, const string16& value) { |
| 170 switch (type.field_type()) { | 188 switch (type.field_type()) { |
| 171 case CREDIT_CARD_NAME: | 189 case CREDIT_CARD_NAME: |
| 172 set_name_on_card(value); | 190 set_name_on_card(value); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 191 } | 209 } |
| 192 set_number(value); | 210 set_number(value); |
| 193 // Update last four digits as well. | 211 // Update last four digits as well. |
| 194 if (value.length() > 4) | 212 if (value.length() > 4) |
| 195 set_last_four_digits(value.substr(value.length() - 4)); | 213 set_last_four_digits(value.substr(value.length() - 4)); |
| 196 else | 214 else |
| 197 set_last_four_digits(string16()); | 215 set_last_four_digits(string16()); |
| 198 } break; | 216 } break; |
| 199 | 217 |
| 200 case CREDIT_CARD_VERIFICATION_CODE: | 218 case CREDIT_CARD_VERIFICATION_CODE: |
| 201 set_verification_code(value); | 219 NOTREACHED(); |
| 202 break; | 220 break; |
| 203 | 221 |
| 204 default: | 222 default: |
| 205 DLOG(ERROR) << "Attempting to set unknown info-type " | 223 DLOG(ERROR) << "Attempting to set unknown info-type " |
| 206 << type.field_type(); | 224 << type.field_type(); |
| 207 break; | 225 break; |
| 208 } | 226 } |
| 209 } | 227 } |
| 210 | 228 |
| 211 string16 CreditCard::ObfuscatedNumber() const { | 229 string16 CreditCard::ObfuscatedNumber() const { |
| 212 if (number().empty()) | 230 if (number().empty()) |
| 213 return string16(); // No CC number, means empty preview. | 231 return string16(); // No CC number, means empty preview. |
| 214 string16 result(ASCIIToUTF16(kCreditCardObfuscationString)); | 232 string16 result(ASCIIToUTF16(kCreditCardObfuscationString)); |
| 215 result.append(last_four_digits()); | 233 result.append(last_four_digits()); |
| 216 | 234 |
| 217 return result; | 235 return result; |
| 218 } | 236 } |
| 219 | 237 |
| 220 string16 CreditCard::PreviewSummary() const { | 238 string16 CreditCard::PreviewSummary() const { |
| 221 string16 preview; | 239 string16 preview; |
| 222 if (number().empty()) | 240 if (number().empty()) |
| 223 return preview; // No CC number, means empty preview. | 241 return preview; // No CC number, means empty preview. |
| 224 string16 obfuscated_cc_number = ObfuscatedNumber(); | 242 string16 obfuscated_cc_number = ObfuscatedNumber(); |
| 225 if (!expiration_month() || !expiration_year()) | 243 if (!expiration_month() || !expiration_year()) |
| 226 return obfuscated_cc_number; // no expiration date set | 244 return obfuscated_cc_number; // No expiration date set. |
| 227 // TODO(georgey): internationalize date | 245 // TODO(georgey): Internationalize date. |
| 228 string16 formatted_date(ExpirationMonthAsString()); | 246 string16 formatted_date(ExpirationMonthAsString()); |
| 229 formatted_date.append(ASCIIToUTF16("/")); | 247 formatted_date.append(ASCIIToUTF16("/")); |
| 230 formatted_date.append(Expiration4DigitYearAsString()); | 248 formatted_date.append(Expiration4DigitYearAsString()); |
| 231 | 249 |
| 232 preview = l10n_util::GetStringFUTF16( | 250 preview = l10n_util::GetStringFUTF16( |
| 233 IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT, | 251 IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT, |
| 234 obfuscated_cc_number, | 252 obfuscated_cc_number, |
| 235 formatted_date); | 253 formatted_date); |
| 236 return preview; | 254 return preview; |
| 237 } | 255 } |
| 238 | 256 |
| 239 string16 CreditCard::LastFourDigits() const { | 257 string16 CreditCard::LastFourDigits() const { |
| 240 static const size_t kNumLastDigits = 4; | 258 static const size_t kNumLastDigits = 4; |
| 241 | 259 |
| 242 if (number().size() < kNumLastDigits) | 260 if (number().size() < kNumLastDigits) |
| 243 return string16(); | 261 return string16(); |
| 244 | 262 |
| 245 return number().substr(number().size() - kNumLastDigits, kNumLastDigits); | 263 return number().substr(number().size() - kNumLastDigits, kNumLastDigits); |
| 246 } | 264 } |
| 247 | 265 |
| 248 void CreditCard::operator=(const CreditCard& source) { | 266 void CreditCard::operator=(const CreditCard& source) { |
| 249 number_ = source.number_; | 267 number_ = source.number_; |
| 250 name_on_card_ = source.name_on_card_; | 268 name_on_card_ = source.name_on_card_; |
| 251 type_ = source.type_; | 269 type_ = source.type_; |
| 252 verification_code_ = source.verification_code_; | |
| 253 last_four_digits_ = source.last_four_digits_; | 270 last_four_digits_ = source.last_four_digits_; |
| 254 expiration_month_ = source.expiration_month_; | 271 expiration_month_ = source.expiration_month_; |
| 255 expiration_year_ = source.expiration_year_; | 272 expiration_year_ = source.expiration_year_; |
| 256 label_ = source.label_; | 273 label_ = source.label_; |
| 257 billing_address_ = source.billing_address_; | 274 billing_address_ = source.billing_address_; |
| 258 shipping_address_ = source.shipping_address_; | |
| 259 unique_id_ = source.unique_id_; | 275 unique_id_ = source.unique_id_; |
| 260 } | 276 } |
| 261 | 277 |
| 262 bool CreditCard::operator==(const CreditCard& creditcard) const { | 278 bool CreditCard::operator==(const CreditCard& creditcard) const { |
| 263 // The following CreditCard field types are the only types we store in the | 279 // The following CreditCard field types are the only types we store in the |
| 264 // WebDB so far, so we're only concerned with matching these types in the | 280 // WebDB so far, so we're only concerned with matching these types in the |
| 265 // profile. | 281 // profile. |
| 266 const AutoFillFieldType types[] = { CREDIT_CARD_NAME, | 282 const AutoFillFieldType types[] = { CREDIT_CARD_NAME, |
| 267 CREDIT_CARD_TYPE, | 283 CREDIT_CARD_TYPE, |
| 268 CREDIT_CARD_NUMBER, | 284 CREDIT_CARD_NUMBER, |
| 269 CREDIT_CARD_VERIFICATION_CODE, | |
| 270 CREDIT_CARD_EXP_MONTH, | 285 CREDIT_CARD_EXP_MONTH, |
| 271 CREDIT_CARD_EXP_4_DIGIT_YEAR }; | 286 CREDIT_CARD_EXP_4_DIGIT_YEAR }; |
| 272 | 287 |
| 273 if (label_ != creditcard.label_ || | 288 if (label_ != creditcard.label_ || |
| 274 unique_id_ != creditcard.unique_id_ || | 289 unique_id_ != creditcard.unique_id_ || |
| 275 billing_address_ != creditcard.billing_address_ || | 290 billing_address_ != creditcard.billing_address_) { |
| 276 shipping_address_ != creditcard.shipping_address_) { | |
| 277 return false; | 291 return false; |
| 278 } | 292 } |
| 279 | 293 |
| 280 for (size_t index = 0; index < arraysize(types); ++index) { | 294 for (size_t index = 0; index < arraysize(types); ++index) { |
| 281 if (GetFieldText(AutoFillType(types[index])) != | 295 if (GetFieldText(AutoFillType(types[index])) != |
| 282 creditcard.GetFieldText(AutoFillType(types[index]))) | 296 creditcard.GetFieldText(AutoFillType(types[index]))) |
| 283 return false; | 297 return false; |
| 284 } | 298 } |
| 285 | 299 |
| 286 return true; | 300 return true; |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 (expiration_year < 2006 || expiration_year > 10000)) { | 384 (expiration_year < 2006 || expiration_year > 10000)) { |
| 371 return; | 385 return; |
| 372 } | 386 } |
| 373 | 387 |
| 374 expiration_year_ = expiration_year; | 388 expiration_year_ = expiration_year; |
| 375 } | 389 } |
| 376 | 390 |
| 377 bool CreditCard::FindInfoMatchesHelper(const AutoFillFieldType& field_type, | 391 bool CreditCard::FindInfoMatchesHelper(const AutoFillFieldType& field_type, |
| 378 const string16& info, | 392 const string16& info, |
| 379 string16* match) const { | 393 string16* match) const { |
| 380 if (match == NULL) { | 394 DCHECK(match); |
| 381 DLOG(ERROR) << "NULL match string passed in"; | |
| 382 return false; | |
| 383 } | |
| 384 | 395 |
| 385 match->clear(); | 396 match->clear(); |
| 386 switch (field_type) { | 397 switch (field_type) { |
| 387 case CREDIT_CARD_NAME: { | 398 case CREDIT_CARD_NAME: { |
| 388 if (StartsWith(name_on_card(), info, false)) | 399 if (StartsWith(name_on_card(), info, false)) |
| 389 *match = name_on_card(); | 400 *match = name_on_card(); |
| 390 break; | 401 break; |
| 391 } | 402 } |
| 392 | 403 |
| 393 case CREDIT_CARD_EXP_MONTH: { | 404 case CREDIT_CARD_EXP_MONTH: { |
| (...skipping 27 matching lines...) Expand all Loading... |
| 421 if (StartsWith(exp_date, info, true)) | 432 if (StartsWith(exp_date, info, true)) |
| 422 *match = exp_date; | 433 *match = exp_date; |
| 423 } | 434 } |
| 424 | 435 |
| 425 case CREDIT_CARD_TYPE: { | 436 case CREDIT_CARD_TYPE: { |
| 426 string16 card_type(this->type()); | 437 string16 card_type(this->type()); |
| 427 if (StartsWith(card_type, info, true)) | 438 if (StartsWith(card_type, info, true)) |
| 428 *match = card_type; | 439 *match = card_type; |
| 429 } | 440 } |
| 430 | 441 |
| 431 case CREDIT_CARD_VERIFICATION_CODE: { | 442 case CREDIT_CARD_VERIFICATION_CODE: |
| 432 if (StartsWith(verification_code(), info, true)) | 443 NOTREACHED(); |
| 433 *match = verification_code(); | 444 break; |
| 434 } | |
| 435 | 445 |
| 436 default: | 446 default: |
| 437 break; | 447 break; |
| 438 } | 448 } |
| 439 return !match->empty(); | 449 return !match->empty(); |
| 440 } | 450 } |
| 441 | 451 |
| 442 bool CreditCard::IsNameOnCard(const string16& text) const { | 452 bool CreditCard::IsNameOnCard(const string16& text) const { |
| 443 return StringToLowerASCII(text) == StringToLowerASCII(name_on_card_); | 453 return StringToLowerASCII(text) == StringToLowerASCII(name_on_card_); |
| 444 } | 454 } |
| 445 | 455 |
| 446 bool CreditCard::IsVerificationCode(const string16& text) const { | |
| 447 return StringToLowerASCII(text) == StringToLowerASCII(verification_code_); | |
| 448 } | |
| 449 | |
| 450 bool CreditCard::IsExpirationMonth(const string16& text) const { | 456 bool CreditCard::IsExpirationMonth(const string16& text) const { |
| 451 int month; | 457 int month; |
| 452 if (!StringToInt(text, &month)) | 458 if (!StringToInt(text, &month)) |
| 453 return false; | 459 return false; |
| 454 | 460 |
| 455 return expiration_month_ == month; | 461 return expiration_month_ == month; |
| 456 } | 462 } |
| 457 | 463 |
| 458 bool CreditCard::Is2DigitExpirationYear(const string16& text) const { | 464 bool CreditCard::Is2DigitExpirationYear(const string16& text) const { |
| 459 int year; | 465 int year; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 491 | 497 |
| 492 // So we can compare CreditCards with EXPECT_EQ(). | 498 // So we can compare CreditCards with EXPECT_EQ(). |
| 493 std::ostream& operator<<(std::ostream& os, const CreditCard& creditcard) { | 499 std::ostream& operator<<(std::ostream& os, const CreditCard& creditcard) { |
| 494 return os | 500 return os |
| 495 << UTF16ToUTF8(creditcard.Label()) | 501 << UTF16ToUTF8(creditcard.Label()) |
| 496 << " " | 502 << " " |
| 497 << creditcard.unique_id() | 503 << creditcard.unique_id() |
| 498 << " " | 504 << " " |
| 499 << UTF16ToUTF8(creditcard.billing_address()) | 505 << UTF16ToUTF8(creditcard.billing_address()) |
| 500 << " " | 506 << " " |
| 501 << UTF16ToUTF8(creditcard.shipping_address()) | |
| 502 << " " | |
| 503 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_NAME))) | 507 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_NAME))) |
| 504 << " " | 508 << " " |
| 505 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_TYPE))) | 509 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_TYPE))) |
| 506 << " " | 510 << " " |
| 507 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_NUMBER))) | 511 << UTF16ToUTF8(creditcard.GetFieldText(AutoFillType(CREDIT_CARD_NUMBER))) |
| 508 << " " | 512 << " " |
| 509 << UTF16ToUTF8(creditcard.GetFieldText( | 513 << UTF16ToUTF8(creditcard.GetFieldText( |
| 510 AutoFillType(CREDIT_CARD_VERIFICATION_CODE))) | |
| 511 << " " | |
| 512 << UTF16ToUTF8(creditcard.GetFieldText( | |
| 513 AutoFillType(CREDIT_CARD_EXP_MONTH))) | 514 AutoFillType(CREDIT_CARD_EXP_MONTH))) |
| 514 << " " | 515 << " " |
| 515 << UTF16ToUTF8(creditcard.GetFieldText( | 516 << UTF16ToUTF8(creditcard.GetFieldText( |
| 516 AutoFillType(CREDIT_CARD_EXP_4_DIGIT_YEAR))); | 517 AutoFillType(CREDIT_CARD_EXP_4_DIGIT_YEAR))); |
| 517 } | 518 } |
| OLD | NEW |