Bug 1461477 - Create a CreditCard.jsm to consolidate various credit card handling and validation. r=MattN

MozReview-Commit-ID: 3tJdzU3hBvY

--HG--
extra : rebase_source : bf79b4767ed40e792d1523bd262d527061a21a4c
This commit is contained in:
Jared Wein 2018-05-15 12:41:35 -04:00
parent 16d53b6538
commit 23f6d33e60
27 changed files with 837 additions and 383 deletions

View File

@ -195,9 +195,7 @@ function checkPaymentAddressMatchesStorageAddress(paymentAddress, storageAddress
function checkPaymentMethodDetailsMatchesCard(methodDetails, card, msg) {
info(msg);
// The card expiry month should be a zero-padded two-digit string.
let cardExpiryMonth = card["cc-exp-month"] < 10 ?
"0" + card["cc-exp-month"] :
card["cc-exp-month"].toString();
let cardExpiryMonth = card["cc-exp-month"].toString().padStart(2, "0");
is(methodDetails.cardholderName, card["cc-name"], "Check cardholderName");
is(methodDetails.cardNumber, card["cc-number"], "Check cardNumber");
is(methodDetails.expiryMonth, cardExpiryMonth, "Check expiryMonth");

View File

@ -38,6 +38,7 @@ ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
CreditCard: "resource://gre/modules/CreditCard.jsm",
FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
MasterPassword: "resource://formautofill/MasterPassword.jsm",
@ -519,7 +520,12 @@ FormAutofillParent.prototype = {
return;
}
const description = FormAutofillUtils.getCreditCardLabel(creditCard.record, false);
const card = new CreditCard({
number: creditCard.record["cc-number"] || creditCard.record["cc-number-decrypted"],
encryptedNumber: creditCard.record["cc-number-encrypted"],
name: creditCard.record["cc-name"],
});
const description = await card.getLabel();
const state = await FormAutofillDoorhanger.show(target,
creditCard.guid ? "updateCreditCard" : "addCreditCard",
description);

View File

@ -132,6 +132,8 @@ ChromeUtils.import("resource://gre/modules/osfile.jsm");
ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
ChromeUtils.defineModuleGetter(this, "JSONFile",
"resource://gre/modules/JSONFile.jsm");
ChromeUtils.defineModuleGetter(this, "FormAutofillNameUtils",
@ -1161,7 +1163,7 @@ class AutofillRecords {
}
if (typeof record[key] !== "string" &&
typeof record[key] !== "number") {
throw new Error(`"${key}" contains invalid data type.`);
throw new Error(`"${key}" contains invalid data type: ${typeof record[key]}`);
}
if (!preserveEmptyFields && record[key] === "") {
delete record[key];
@ -1494,13 +1496,6 @@ class CreditCards extends AutofillRecords {
super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
}
_getMaskedCCNumber(ccNumber) {
if (ccNumber.length <= 4) {
throw new Error(`Invalid credit card number`);
}
return "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
}
_computeFields(creditCard) {
// NOTE: Remember to bump the schema version number if any of the existing
// computing algorithm changes. (No need to bump when just adding new
@ -1538,7 +1533,7 @@ class CreditCards extends AutofillRecords {
if (!("cc-number-encrypted" in creditCard)) {
if ("cc-number" in creditCard) {
let ccNumber = creditCard["cc-number"];
creditCard["cc-number"] = this._getMaskedCCNumber(ccNumber);
creditCard["cc-number"] = CreditCard.getLongMaskedNumber(ccNumber);
creditCard["cc-number-encrypted"] = MasterPassword.encryptSync(ccNumber);
} else {
creditCard["cc-number-encrypted"] = "";
@ -1578,7 +1573,8 @@ class CreditCards extends AutofillRecords {
_normalizeCCNumber(creditCard) {
if (creditCard["cc-number"]) {
creditCard["cc-number"] = FormAutofillUtils.normalizeCCNumber(creditCard["cc-number"]);
let card = new CreditCard({number: creditCard["cc-number"]});
creditCard["cc-number"] = card.number;
if (!creditCard["cc-number"]) {
delete creditCard["cc-number"];
}
@ -1586,84 +1582,21 @@ class CreditCards extends AutofillRecords {
}
_normalizeCCExpirationDate(creditCard) {
if (creditCard["cc-exp-month"]) {
let expMonth = parseInt(creditCard["cc-exp-month"], 10);
if (isNaN(expMonth) || expMonth < 1 || expMonth > 12) {
delete creditCard["cc-exp-month"];
} else {
creditCard["cc-exp-month"] = expMonth;
}
let card = new CreditCard({
expirationMonth: creditCard["cc-exp-month"],
expirationYear: creditCard["cc-exp-year"],
expirationString: creditCard["cc-exp"],
});
if (card.expirationMonth) {
creditCard["cc-exp-month"] = card.expirationMonth;
} else {
delete creditCard["cc-exp-month"];
}
if (creditCard["cc-exp-year"]) {
let expYear = parseInt(creditCard["cc-exp-year"], 10);
if (isNaN(expYear) || expYear < 0) {
delete creditCard["cc-exp-year"];
} else if (expYear < 100) {
// Enforce 4 digits years.
creditCard["cc-exp-year"] = expYear + 2000;
} else {
creditCard["cc-exp-year"] = expYear;
}
if (card.expirationYear) {
creditCard["cc-exp-year"] = card.expirationYear;
} else {
delete creditCard["cc-exp-year"];
}
if (creditCard["cc-exp"] && (!creditCard["cc-exp-month"] || !creditCard["cc-exp-year"])) {
let rules = [
{
regex: "(\\d{4})[-/](\\d{1,2})",
yearIndex: 1,
monthIndex: 2,
},
{
regex: "(\\d{1,2})[-/](\\d{4})",
yearIndex: 2,
monthIndex: 1,
},
{
regex: "(\\d{1,2})[-/](\\d{1,2})",
},
{
regex: "(\\d{2})(\\d{2})",
},
];
for (let rule of rules) {
let result = new RegExp(`(?:^|\\D)${rule.regex}(?!\\d)`).exec(creditCard["cc-exp"]);
if (!result) {
continue;
}
let expYear, expMonth;
if (!rule.yearIndex || !rule.monthIndex) {
expMonth = parseInt(result[1], 10);
if (expMonth > 12) {
expYear = parseInt(result[1], 10);
expMonth = parseInt(result[2], 10);
} else {
expYear = parseInt(result[2], 10);
}
} else {
expYear = parseInt(result[rule.yearIndex], 10);
expMonth = parseInt(result[rule.monthIndex], 10);
}
if (expMonth < 1 || expMonth > 12) {
continue;
}
if (expYear < 100) {
expYear += 2000;
} else if (expYear < 2000) {
continue;
}
creditCard["cc-exp-month"] = expMonth;
creditCard["cc-exp-year"] = expYear;
break;
}
}
delete creditCard["cc-exp"];
}
@ -1686,7 +1619,7 @@ class CreditCards extends AutofillRecords {
if (MasterPassword.isEnabled) {
// Compare the masked numbers instead when the master password is
// enabled because we don't want to leak the credit card number.
return this._getMaskedCCNumber(clonedTargetCreditCard[field]) == creditCard[field];
return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
}
return clonedTargetCreditCard[field] == MasterPassword.decryptSync(creditCard["cc-number-encrypted"]);
}

View File

@ -42,6 +42,8 @@ const MAX_FIELD_VALUE_LENGTH = 200;
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
let AddressDataLoader = {
// Status of address data loading. We'll load all the countries with basic level 1
@ -229,17 +231,9 @@ this.FormAutofillUtils = {
return this._fieldNameInfo[fieldName] == "creditCard";
},
normalizeCCNumber(ccNumber) {
ccNumber = ccNumber.replace(/[-\s]/g, "");
// Based on the information on wiki[1], the shortest valid length should be
// 12 digits(Maestro).
// [1] https://en.wikipedia.org/wiki/Payment_card_number
return ccNumber.match(/^\d{12,}$/) ? ccNumber : null;
},
isCCNumber(ccNumber) {
return !!this.normalizeCCNumber(ccNumber);
let card = new CreditCard({number: ccNumber});
return !!card.number;
},
getCategoryFromFieldName(fieldName) {
@ -263,42 +257,6 @@ this.FormAutofillUtils = {
return " ";
},
/**
* Get credit card display label. It should display masked numbers and the
* cardholder's name, separated by a comma. If `showCreditCards` is set to
* true, decrypted credit card numbers are shown instead.
*
* @param {object} creditCard
* @param {boolean} showCreditCards [optional]
* @returns {string}
*/
getCreditCardLabel(creditCard, showCreditCards = false) {
let parts = [];
let ccLabel;
let ccNumber = creditCard["cc-number"];
let decryptedCCNumber = creditCard["cc-number-decrypted"];
if (showCreditCards && decryptedCCNumber) {
ccLabel = decryptedCCNumber;
}
if (ccNumber && !ccLabel) {
if (this.isCCNumber(ccNumber)) {
ccLabel = "*".repeat(4) + " " + ccNumber.substr(-4);
} else {
let {affix, label} = this.fmtMaskedCreditCardLabel(ccNumber);
ccLabel = `${affix} ${label}`;
}
}
if (ccLabel) {
parts.push(ccLabel);
}
if (creditCard["cc-name"]) {
parts.push(creditCard["cc-name"]);
}
return parts.join(", ");
},
/**
* Get address display label. It should display up to two pieces of
* information, separated by a comma.
@ -379,13 +337,6 @@ this.FormAutofillUtils = {
}
},
fmtMaskedCreditCardLabel(maskedCCNum = "") {
return {
affix: "****",
label: maskedCCNum.replace(/^\**/, ""),
};
},
defineLazyLogGetter(scope, logPrefix) {
XPCOMUtils.defineLazyGetter(scope, "log", () => {
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;

View File

@ -11,6 +11,8 @@ var EXPORTED_SYMBOLS = ["AddressResult", "CreditCardResult"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "insecureWarningEnabled", "security.insecure_field_warning.contextual.enabled");
@ -339,7 +341,7 @@ class CreditCardResult extends ProfileAutoCompleteResult {
if (matching) {
if (currentFieldName == "cc-number") {
let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(profile[currentFieldName]);
let {affix, label} = CreditCard.formatMaskedNumber(profile[currentFieldName]);
return affix + label;
}
return profile[currentFieldName];
@ -374,7 +376,7 @@ class CreditCardResult extends ProfileAutoCompleteResult {
let primary = profile[focusedFieldName];
if (focusedFieldName == "cc-number") {
let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(primary);
let {affix, label} = CreditCard.formatMaskedNumber(primary);
primaryAffix = affix;
primary = label;
}

View File

@ -13,6 +13,8 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
"resource://formautofill/FormAutofillStorage.jsm");
ChromeUtils.defineModuleGetter(this, "MasterPassword",
@ -334,17 +336,17 @@ class ManageCreditCards extends ManageRecords {
* cardholder's name, separated by a comma. If `showCreditCards` is set to
* true, decrypted credit card numbers are shown instead.
*
* @param {object} creditCard
* @param {boolean} showCreditCards [optional]
* @param {object} creditCard
* @param {boolean} showCreditCards [optional]
* @returns {string}
*/
async getLabel(creditCard, showCreditCards = false) {
let patchObj = {};
if (creditCard["cc-number"] && showCreditCards) {
patchObj["cc-number-decrypted"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
}
return FormAutofillUtils.getCreditCardLabel({...creditCard, ...patchObj}, showCreditCards);
let cardObj = new CreditCard({
encryptedNumber: creditCard["cc-number-encrypted"],
number: creditCard["cc-number"],
name: creditCard["cc-name"],
});
return cardObj.getLabel({showNumbers: showCreditCards});
}
async toggleShowHideCards(options) {

View File

@ -18,7 +18,7 @@ add_task(async function test_submit_creditCard_cancel_saving() {
name.setUserInput("User 1");
let number = form.querySelector("#cc-number");
number.setUserInput("1111222233334444");
number.setUserInput("5038146897157463");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
@ -57,7 +57,7 @@ add_task(async function test_submit_creditCard_saved() {
name.focus();
name.setUserInput("User 1");
form.querySelector("#cc-number").setUserInput("1111222233334444");
form.querySelector("#cc-number").setUserInput("5038146897157463");
form.querySelector("#cc-exp-month").setUserInput("12");
form.querySelector("#cc-exp-year").setUserInput("2017");
@ -135,7 +135,7 @@ add_task(async function test_submit_changed_subset_creditCard_form() {
await new Promise(resolve => setTimeout(resolve, 1000));
name.setUserInput("");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-number").setUserInput("4111111111111111");
form.querySelector("#cc-exp-month").setUserInput("4");
form.querySelector("#cc-exp-year").setUserInput(new Date().getFullYear());
// Wait 1000ms before submission to make sure the input value applied
@ -173,7 +173,7 @@ add_task(async function test_submit_duplicate_creditCard_form() {
name.focus();
name.setUserInput("John Doe");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-number").setUserInput("4111111111111111");
form.querySelector("#cc-exp-month").setUserInput("4");
form.querySelector("#cc-exp-year").setUserInput(new Date().getFullYear());
@ -209,7 +209,7 @@ add_task(async function test_submit_unnormailzed_creditCard_form() {
name.focus();
name.setUserInput("John Doe");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-number").setUserInput("4111111111111111");
form.querySelector("#cc-exp-month").setUserInput("4");
// Set unnormalized year
form.querySelector("#cc-exp-year").setUserInput(new Date().getFullYear().toString().substr(2, 2));
@ -248,7 +248,7 @@ add_task(async function test_submit_creditCard_never_save() {
name.setUserInput("User 0");
let number = form.querySelector("#cc-number");
number.setUserInput("1234123412341234");
number.setUserInput("6387060366272981");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
@ -286,7 +286,7 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
name.setUserInput("User 0");
let number = form.querySelector("#cc-number");
number.setUserInput("1234123412341234");
number.setUserInput("6387060366272981");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
@ -303,7 +303,7 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
let creditCards = await getCreditCards();
is(creditCards.length, 1, "1 credit card in storage");
is(creditCards[0]["cc-name"], "User 0", "Verify the name field");
is(creditCards[0]["cc-number"], "************1234", "Verify the card number field");
is(creditCards[0]["cc-number"], "************2981", "Verify the card number field");
LoginTestUtils.masterPassword.disable();
await removeAllRecords();
});
@ -323,7 +323,7 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_cancele
name.setUserInput("User 2");
let number = form.querySelector("#cc-number");
number.setUserInput("5678567856785678");
number.setUserInput("5471839082338112");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
@ -361,7 +361,7 @@ add_task(async function test_submit_creditCard_with_sync_account() {
name.setUserInput("User 2");
let number = form.querySelector("#cc-number");
number.setUserInput("1234123412341234");
number.setUserInput("6387060366272981");
// Wait 500ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 500));
@ -417,7 +417,7 @@ add_task(async function test_submit_creditCard_with_synced_already() {
name.setUserInput("User 2");
let number = form.querySelector("#cc-number");
number.setUserInput("1234123412341234");
number.setUserInput("6387060366272981");
// Wait 500ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 500));
@ -451,7 +451,7 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
name.focus();
name.setUserInput("User 3");
form.querySelector("#cc-number").setUserInput("9999888877776666");
form.querySelector("#cc-number").setUserInput("5103059495477870");
form.querySelector("#cc-exp-month").setUserInput("1");
form.querySelector("#cc-exp-year").setUserInput("2000");
@ -506,7 +506,7 @@ add_task(async function test_update_autofill_form_name() {
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card");
is(creditCards[0]["cc-name"], "User 1", "cc-name field is updated");
is(creditCards[0]["cc-number"], "************5678", "Verify the card number field");
is(creditCards[0]["cc-number"], "************1111", "Verify the card number field");
is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
await removeAllRecords();
@ -546,7 +546,7 @@ add_task(async function test_update_autofill_form_exp_date() {
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card");
is(creditCards[0]["cc-exp-year"], "2020", "cc-exp-year field is updated");
is(creditCards[0]["cc-number"], "************5678", "Verify the card number field");
is(creditCards[0]["cc-number"], "************1111", "Verify the card number field");
is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
await removeAllRecords();
@ -600,10 +600,10 @@ add_task(async function test_update_duplicate_autofill_form() {
],
});
await saveCreditCard({
"cc-number": "1234123412341234",
"cc-number": "6387060366272981",
});
await saveCreditCard({
"cc-number": "1111222233334444",
"cc-number": "5038146897157463",
});
let creditCards = await getCreditCards();
is(creditCards.length, 2, "2 credit card in storage");
@ -615,9 +615,9 @@ add_task(async function test_update_duplicate_autofill_form() {
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let number = form.querySelector("#cc-number");
is(number.value, "1234123412341234", "Should be the first credit card number");
is(number.value, "6387060366272981", "Should be the first credit card number");
// Change number to the second credit card number
number.setUserInput("1111222233334444");
number.setUserInput("5038146897157463");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));

View File

@ -51,9 +51,9 @@ add_task(async function test_removingSingleAndMultipleCreditCards() {
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
is(selRecords.length, 3, "Three credit cards");
is(selRecords[0].text, "**** 6666", "Masked credit card 3");
is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
is(selRecords[2].text, "**** 5678, John Doe", "Masked credit card 1");
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
is(selRecords[2].text, "**** 1111, John Doe", "Masked credit card 1");
EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
is(btnRemove.disabled, false, "Remove button enabled");
@ -125,29 +125,29 @@ add_task(async function test_showCreditCards() {
// Show credit card numbers
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
is(selRecords[1].text, "1111222233334444, Timothy Berners-Lee", "Decrypted credit card 2");
is(selRecords[2].text, "1234567812345678, John Doe", "Decrypted credit card 1");
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
is(selRecords[1].text, "4929001587121045, Timothy Berners-Lee", "Decrypted credit card 2");
is(selRecords[2].text, "4111111111111111, John Doe", "Decrypted credit card 1");
is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
// Hide credit card numbers
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
is(selRecords[0].text, "**** 6666", "Masked credit card 3");
is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
is(selRecords[2].text, "**** 5678, John Doe", "Masked credit card 1");
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
is(selRecords[2].text, "**** 1111, John Doe", "Masked credit card 1");
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
// Show credit card numbers again to test if they revert back to masked form when reloaded
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
// Ensure credit card numbers are shown again
is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
// Remove a card to trigger reloading
await removeCreditCards([selRecords.options[2].value]);
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
is(selRecords[0].text, "**** 6666", "Masked credit card 3");
is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
// Remove the rest of the cards
await removeCreditCards([selRecords.options[1].value]);

View File

@ -100,20 +100,20 @@ const TEST_ADDRESS_DE_1 = {
const TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": new Date().getFullYear(),
};
const TEST_CREDIT_CARD_2 = {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "4929001587121045",
"cc-exp-month": 12,
"cc-exp-year": new Date().getFullYear() + 10,
};
const TEST_CREDIT_CARD_3 = {
"cc-number": "9999888877776666",
"cc-number": "5103059495477870",
"cc-exp-month": 1,
"cc-exp-year": 2000,
};

View File

@ -202,9 +202,11 @@ async function cleanUpStorage() {
}
function patchRecordCCNumber(record) {
const ccNumber = record["cc-number"];
const normalizedCCNumber = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
const ccNumberFmt = FormAutofillUtils.fmtMaskedCreditCardLabel(normalizedCCNumber);
const number = record["cc-number"];
const ccNumberFmt = {
affix: "****",
label: number.substr(-4),
};
return Object.assign({}, record, {ccNumberFmt});
}

View File

@ -22,19 +22,19 @@ Form autofill test: simple form credit card autofill
const MOCK_STORAGE = [{
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}, {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
}];
const reducedMockRecord = {
"cc-name": "John Doe",
"cc-number": "1234123456785678",
"cc-number": "4929001587121045",
};
async function setupCreditCardStorage() {

View File

@ -33,12 +33,12 @@ const MOCK_ADDR_STORAGE = [{
}];
const MOCK_CC_STORAGE = [{
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}, {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
}];

View File

@ -22,12 +22,12 @@ Form autofill test: simple form credit card autofill
const MOCK_STORAGE = [{
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}, {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
}];
@ -40,7 +40,7 @@ async function setupCreditCardStorage() {
async function setupFormHistory() {
await updateFormHistory([
{op: "add", fieldname: "cc-name", value: "John Smith"},
{op: "add", fieldname: "cc-number", value: "1234000056780000"},
{op: "add", fieldname: "cc-number", value: "6011029476355493"},
]);
}
@ -53,7 +53,7 @@ add_task(async function history_only_menu_checking() {
await setInput("#cc-number", "");
synthesizeKey("KEY_ArrowDown");
await expectPopup();
checkMenuEntries(["1234000056780000"], false);
checkMenuEntries(["6011029476355493"], false);
await setInput("#cc-name", "");
synthesizeKey("KEY_ArrowDown");

View File

@ -7,33 +7,34 @@
const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
ChromeUtils.defineModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
const TEST_STORE_FILE_NAME = "test-credit-card.json";
const COLLECTION_NAME = "creditCards";
const TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
const TEST_CREDIT_CARD_2 = {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
};
const TEST_CREDIT_CARD_3 = {
"cc-number": "9999888877776666",
"cc-number": "3589993783099582",
"cc-exp-month": 1,
"cc-exp-year": 2000,
};
const TEST_CREDIT_CARD_4 = {
"cc-name": "Foo Bar",
"cc-number": "9999888877776666",
"cc-number": "3589993783099582",
};
const TEST_CREDIT_CARD_WITH_BILLING_ADDRESS = {
@ -45,7 +46,7 @@ const TEST_CREDIT_CARD_WITH_BILLING_ADDRESS = {
const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
billingAddressGUID: "",
"cc-name": "",
"cc-number": "1234123412341234",
"cc-number": "344060747836806",
"cc-exp-month": 1,
};
@ -54,31 +55,31 @@ const TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD = {
"cc-additional-name": "",
"cc-family-name": "",
"cc-exp": "",
"cc-number": "1928374619283746",
"cc-number": "5415425865751454",
};
const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
"cc-number": "1234123412341234",
"cc-number": "344060747836806",
"cc-exp-month": 1,
"cc-exp-year": 12,
};
const TEST_CREDIT_CARD_WITH_INVALID_FIELD = {
"cc-name": "John Doe",
"cc-number": "1234123412341234",
"cc-number": "344060747836806",
invalidField: "INVALID",
};
const TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE = {
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "5103059495477870",
"cc-exp-month": 13,
"cc-exp-year": -3,
};
const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
"cc-name": "John Doe",
"cc-number": "1111 2222 3333 4444",
"cc-number": "5103 0594 9547 7870",
};
const TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE = {
@ -96,19 +97,19 @@ const MERGE_TESTCASES = [
{
description: "Merge a superset",
creditCardInStorage: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
creditCardToMerge: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
@ -116,14 +117,14 @@ const MERGE_TESTCASES = [
{
description: "Merge a superset with billingAddressGUID",
creditCardInStorage: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
billingAddressGUID: "ijsnbhfr",
},
expectedCreditCard: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
billingAddressGUID: "ijsnbhfr",
},
},
@ -131,18 +132,18 @@ const MERGE_TESTCASES = [
description: "Merge a subset",
creditCardInStorage: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
@ -151,15 +152,15 @@ const MERGE_TESTCASES = [
{
description: "Merge a subset with billingAddressGUID",
creditCardInStorage: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
billingAddressGUID: "8fhdb3ug6",
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
},
expectedCreditCard: {
billingAddressGUID: "8fhdb3ug6",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
},
noNeedToUpdate: true,
},
@ -167,16 +168,16 @@ const MERGE_TESTCASES = [
description: "Merge an creditCard with partial overlaps",
creditCardInStorage: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
@ -317,7 +318,7 @@ add_task(async function test_add() {
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD);
creditCard = profileStorage.creditCards._data[3];
Assert.equal(creditCard["cc-number"],
profileStorage.creditCards._getMaskedCCNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
/"invalidField" is not a valid field\./);
@ -399,11 +400,11 @@ add_task(async function test_update() {
profileStorage.creditCards.update(profileStorage.creditCards._data[0].guid, TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD, false);
creditCard = profileStorage.creditCards._data[0];
Assert.equal(creditCard["cc-number"],
profileStorage.creditCards._getMaskedCCNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
profileStorage.creditCards.update(profileStorage.creditCards._data[1].guid, TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD, true);
creditCard = profileStorage.creditCards._data[1];
Assert.equal(creditCard["cc-number"],
profileStorage.creditCards._getMaskedCCNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
Assert.throws(
() => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
@ -635,7 +636,9 @@ add_task(async function test_getDuplicateGuid() {
// Numbers with the same last 4 digits shouldn't be treated as a duplicate.
record = Object.assign({}, TEST_CREDIT_CARD_3);
let last4Digits = record["cc-number"].substr(-4);
record["cc-number"] = "000000000000" + last4Digits;
// This number differs from TEST_CREDIT_CARD_3 by swapping the order of the
// 09 and 90 adjacent digits, which is still a valid credit card number.
record["cc-number"] = "358999378390" + last4Digits;
Assert.equal(profileStorage.creditCards.getDuplicateGuid(record), null);
// ... However, we treat numbers with the same last 4 digits as a duplicate if

View File

@ -7,6 +7,7 @@
let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
const TEST_ADDRESS_1 = {
"given-name": "Timothy",
@ -29,14 +30,14 @@ const TEST_ADDRESS_2 = {
let TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
let TEST_CREDIT_CARD_2 = {
"cc-name": "John Dai",
"cc-number": "1111222233334444",
"cc-number": "4929001587121045",
"cc-exp-month": 2,
"cc-exp-year": 2017,
};
@ -59,7 +60,7 @@ add_task(async function test_getRecords() {
}],
creditCards: [{
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}],
@ -169,7 +170,7 @@ add_task(async function test_getRecords_creditCards() {
let collection = formAutofillParent.formAutofillStorage.creditCards;
let encryptedCCRecords = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(record => {
let clonedRecord = Object.assign({}, record);
clonedRecord["cc-number"] = collection._getMaskedCCNumber(record["cc-number"]);
clonedRecord["cc-number"] = CreditCard.getLongMaskedNumber(record["cc-number"]);
clonedRecord["cc-number-encrypted"] = MasterPassword.encryptSync(record["cc-number"]);
return clonedRecord;
});
@ -212,7 +213,7 @@ add_task(async function test_getRecords_creditCards() {
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
searchString: "123",
searchString: "411",
},
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
},
@ -221,7 +222,7 @@ add_task(async function test_getRecords_creditCards() {
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
searchString: "1",
searchString: "4",
},
expectedResult: CreditCardsWithDecryptedNumber,
},
@ -240,7 +241,7 @@ add_task(async function test_getRecords_creditCards() {
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
searchString: "123",
searchString: "411",
},
mpEnabled: true,
expectedResult: encryptedCCRecords,

View File

@ -472,13 +472,13 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "2bbd2d8fbc6b",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
// The current local record - by comparing against parent we can see that
// only the cc-number has changed locally.
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
}],
remote: {
// This is the incoming record. It has the same values as "parent", so
@ -487,12 +487,12 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "2bbd2d8fbc6b",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
reconciled: {
"guid": "2bbd2d8fbc6b",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
},
{
@ -501,22 +501,22 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "e3680e9f890d",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
}],
remote: {
"guid": "e3680e9f890d",
"version": 1,
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
reconciled: {
"guid": "e3680e9f890d",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
},
@ -526,23 +526,23 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "0cba738b1be0",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
}],
remote: {
"guid": "0cba738b1be0",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
reconciled: {
"guid": "0cba738b1be0",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
},
@ -552,23 +552,23 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "be3ef97f8285",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
}],
remote: {
"guid": "be3ef97f8285",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
reconciled: {
"guid": "be3ef97f8285",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
},
@ -578,24 +578,24 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "9627322248ec",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
}],
remote: {
"guid": "9627322248ec",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
reconciled: {
"guid": "9627322248ec",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
},
{
@ -604,24 +604,24 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "7d7509f3eeb2",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
}],
remote: {
"guid": "7d7509f3eeb2",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
reconciled: {
"guid": "7d7509f3eeb2",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
},
{
@ -631,13 +631,13 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "e087a06dfc57",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
// The current local record - so locally we've changed "cc-number".
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
"cc-exp-month": 12,
}],
remote: {
@ -645,13 +645,13 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "e087a06dfc57",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 1,
},
reconciled: {
"guid": "e087a06dfc57",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
"cc-exp-month": 1,
},
},
@ -661,27 +661,27 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "340a078c596f",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
"cc-name": "Skip",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
}, {
"cc-name": "Skip",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
}],
remote: {
"guid": "340a078c596f",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-year": 2000,
},
reconciled: {
"guid": "340a078c596f",
"cc-name": "Skip",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
"cc-exp-year": 2000,
},
@ -694,22 +694,22 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "0b3a72a1bea2",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
}],
remote: {
"guid": "0b3a72a1bea2",
"version": 1,
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
reconciled: {
"guid": "0b3a72a1bea2",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
},
{
@ -719,31 +719,31 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "62068784d089",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
local: [{
// The current version of the local record - the cc-number has changed locally.
"cc-name": "John Doe",
"cc-number": "1111111111111111",
"cc-number": "5103059495477870",
}],
remote: {
// An incoming record has a different cc-number than any of the above!
"guid": "62068784d089",
"version": 1,
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
forked: {
// So we've forked the local record to a new GUID (and the next sync is
// going to write this as a new record)
"cc-name": "John Doe",
"cc-number": "1111111111111111",
"cc-number": "5103059495477870",
},
reconciled: {
// And we've updated the local version of the record to be the remote version.
guid: "62068784d089",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
},
},
{
@ -752,30 +752,30 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "244dbb692e94",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111111111111111",
"cc-number": "5103059495477870",
"cc-exp-month": 1,
}],
remote: {
"guid": "244dbb692e94",
"version": 1,
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
"cc-exp-month": 3,
},
forked: {
"cc-name": "John Doe",
"cc-number": "1111111111111111",
"cc-number": "5103059495477870",
"cc-exp-month": 1,
},
reconciled: {
"guid": "244dbb692e94",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
"cc-exp-month": 3,
},
},
@ -785,28 +785,28 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "6fc45e03d19a",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
}],
remote: {
"guid": "6fc45e03d19a",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 3,
},
forked: {
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
reconciled: {
"guid": "6fc45e03d19a",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 3,
},
},
@ -816,29 +816,29 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "fff9fa27fa18",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 12,
},
local: [{
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 3,
}],
remote: {
"guid": "fff9fa27fa18",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
forked: {
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"cc-exp-month": 3,
},
reconciled: {
"guid": "fff9fa27fa18",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
},
},
{
@ -850,7 +850,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "5113f329c42f",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"timeCreated": 1234,
"timeLastModified": 5678,
"timeLastUsed": 5678,
@ -861,7 +861,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "5113f329c42f",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"timeCreated": 1200,
"timeLastModified": 5700,
"timeLastUsed": 5700,
@ -870,7 +870,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
reconciled: {
"guid": "5113f329c42f",
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"timeCreated": 1200,
"timeLastModified": 5700,
"timeLastUsed": 5678,
@ -885,7 +885,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
"guid": "791e5608b80a",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"timeCreated": 1234,
"timeLastModified": 5678,
"timeLastUsed": 5678,
@ -893,13 +893,13 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
},
local: [{
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
}],
remote: {
"guid": "791e5608b80a",
"version": 1,
"cc-name": "John Doe",
"cc-number": "1111222233334444",
"cc-number": "4111111111111111",
"timeCreated": 1300,
"timeLastModified": 5000,
"timeLastUsed": 5000,
@ -908,7 +908,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
reconciled: {
"guid": "791e5608b80a",
"cc-name": "John Doe",
"cc-number": "4444333322221111",
"cc-number": "4929001587121045",
"timeCreated": 1234,
"timeLastUsed": 5678,
"timesUsed": 6,

View File

@ -29,14 +29,14 @@ const TEST_ADDRESS_2 = {
const TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
const TEST_CREDIT_CARD_2 = {
"cc-name": "Timothy Berners-Lee",
"cc-number": "1111222233334444",
"cc-number": "4929001587121045",
"cc-exp-month": 12,
"cc-exp-year": 2022,
};

View File

@ -24,7 +24,7 @@ const TEST_ADDRESS_1 = {
const TEST_CC_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};

View File

@ -539,10 +539,10 @@ const CREDIT_CARD_COMPUTE_TESTCASES = [
{
description: "Number should be encrypted and masked",
creditCard: {
"cc-number": "1234123412341234",
"cc-number": "4929001587121045",
},
expectedResult: {
"cc-number": "************1234",
"cc-number": "************1045",
},
},
@ -609,28 +609,28 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = [
{
description: "Regular number",
creditCard: {
"cc-number": "1234123412341234",
"cc-number": "4929001587121045",
},
expectedResult: {
"cc-number": "1234123412341234",
"cc-number": "4929001587121045",
},
},
{
description: "Number with spaces",
creditCard: {
"cc-number": "1234 1234 1234 1234",
"cc-number": "4111 1111 1111 1111",
},
expectedResult: {
"cc-number": "1234123412341234",
"cc-number": "4111111111111111",
},
},
{
description: "Number with hyphens",
creditCard: {
"cc-number": "1234-1234-1234-1234",
"cc-number": "4111-1111-1111-1111",
},
expectedResult: {
"cc-number": "1234123412341234",
"cc-number": "4111111111111111",
},
},
@ -788,7 +788,7 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = [
{
description: "Has invalid \"cc-exp\"",
creditCard: {
"cc-number": "1111222233334444", // Make sure it won't be an empty record.
"cc-number": "4111111111111111", // Make sure it won't be an empty record.
"cc-exp": "99-9999",
},
expectedResult: {

View File

@ -4,6 +4,9 @@
/* eslint-env mozilla/frame-script */
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
(function() {
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -31,36 +34,6 @@ let satchelFormListener = {
this.enabled = Services.prefs.getBoolPref("browser.formfill.enable");
},
// Implements the Luhn checksum algorithm as described at
// http://wikipedia.org/wiki/Luhn_algorithm
isValidCCNumber(ccNumber) {
// Remove dashes and whitespace
ccNumber = ccNumber.replace(/[\-\s]/g, "");
let len = ccNumber.length;
if (len != 9 && len != 15 && len != 16) {
return false;
}
if (!/^\d+$/.test(ccNumber)) {
return false;
}
let total = 0;
for (let i = 0; i < len; i++) {
let ch = parseInt(ccNumber[len - i - 1], 10);
if (i % 2 == 1) {
// Double it, add digits together if > 10
ch *= 2;
if (ch > 9) {
ch -= 9;
}
}
total += ch;
}
return total % 10 == 0;
},
log(message) {
if (!this.debug) {
return;
@ -127,7 +100,7 @@ let satchelFormListener = {
}
// Don't save credit card numbers.
if (this.isValidCCNumber(value)) {
if (CreditCard.isValidNumber(value)) {
this.log("skipping saving a credit card number");
continue;
}

View File

@ -0,0 +1,295 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["CreditCard"];
ChromeUtils.defineModuleGetter(this, "MasterPassword",
"resource://formautofill/MasterPassword.jsm");
class CreditCard {
/**
* @param {string} name
* @param {string} number
* @param {string} expirationString
* @param {string|number} expirationMonth
* @param {string|number} expirationYear
* @param {string|number} ccv
* @param {string} encryptedNumber
*/
constructor({
name,
number,
expirationString,
expirationMonth,
expirationYear,
ccv,
encryptedNumber
}) {
this._name = name;
this._unmodifiedNumber = number;
this._encryptedNumber = encryptedNumber;
this._ccv = ccv;
this.number = number;
// Only prefer the string version if missing one or both parsed formats.
if (expirationString && (!expirationMonth || !expirationYear)) {
this.expirationString = expirationString;
} else {
this.expirationMonth = expirationMonth;
this.expirationYear = expirationYear;
}
}
set name(value) {
this._name = value;
}
set expirationMonth(value) {
if (typeof value == "undefined") {
this._expirationMonth = undefined;
return;
}
this._expirationMonth = this._normalizeExpirationMonth(value);
}
get expirationMonth() {
return this._expirationMonth;
}
set expirationYear(value) {
if (typeof value == "undefined") {
this._expirationYear = undefined;
return;
}
this._expirationYear = this._normalizeExpirationYear(value);
}
get expirationYear() {
return this._expirationYear;
}
set expirationString(value) {
let {month, year} = this._parseExpirationString(value);
this.expirationMonth = month;
this.expirationYear = year;
}
set ccv(value) {
this._ccv = value;
}
get number() {
return this._number;
}
set number(value) {
if (value) {
let normalizedNumber = value.replace(/[-\s]/g, "");
// Based on the information on wiki[1], the shortest valid length should be
// 9 digits (Canadian SIN).
// [1] https://en.wikipedia.org/wiki/Social_Insurance_Number
normalizedNumber = normalizedNumber.match(/^\d{9,}$/) ?
normalizedNumber : null;
this._number = normalizedNumber;
}
}
// Implements the Luhn checksum algorithm as described at
// http://wikipedia.org/wiki/Luhn_algorithm
isValidNumber() {
if (!this._number) {
return false;
}
// Remove dashes and whitespace
let number = this._number.replace(/[\-\s]/g, "");
let len = number.length;
if (len != 9 && len != 15 && len != 16) {
return false;
}
if (!/^\d+$/.test(number)) {
return false;
}
let total = 0;
for (let i = 0; i < len; i++) {
let ch = parseInt(number[len - i - 1], 10);
if (i % 2 == 1) {
// Double it, add digits together if > 10
ch *= 2;
if (ch > 9) {
ch -= 9;
}
}
total += ch;
}
return total % 10 == 0;
}
/**
* Returns true if the card number is valid and the
* expiration date has not passed. Otherwise false.
*
* @returns {boolean}
*/
isValid() {
if (!this.isValidNumber()) {
return false;
}
let currentDate = new Date();
let currentYear = currentDate.getFullYear();
if (this._expirationYear > currentYear) {
return true;
}
// getMonth is 0-based, so add 1 because credit cards are 1-based
let currentMonth = currentDate.getMonth() + 1;
return this._expirationYear == currentYear &&
this._expirationMonth >= currentMonth;
}
get maskedNumber() {
if (!this.isValidNumber()) {
throw new Error("Invalid credit card number");
}
return "*".repeat(4) + " " + this._number.substr(-4);
}
get longMaskedNumber() {
if (!this.isValidNumber()) {
throw new Error("Invalid credit card number");
}
return "*".repeat(this.number.length - 4) + this.number.substr(-4);
}
/**
* Get credit card display label. It should display masked numbers and the
* cardholder's name, separated by a comma. If `showNumbers` is set to
* true, decrypted credit card numbers are shown instead.
*/
async getLabel({showNumbers} = {}) {
let parts = [];
let label;
if (showNumbers) {
if (this._encryptedNumber) {
label = await MasterPassword.decrypt(this._encryptedNumber);
} else {
label = this._number;
}
}
if (this._unmodifiedNumber && !label) {
if (this.isValidNumber()) {
label = this.maskedNumber;
} else {
let maskedNumber = CreditCard.formatMaskedNumber(this._unmodifiedNumber);
label = `${maskedNumber.affix} ${maskedNumber.label}`;
}
}
if (label) {
parts.push(label);
}
if (this._name) {
parts.push(this._name);
}
return parts.join(", ");
}
_normalizeExpirationMonth(month) {
month = parseInt(month, 10);
if (isNaN(month) || month < 1 || month > 12) {
return undefined;
}
return month;
}
_normalizeExpirationYear(year) {
year = parseInt(year, 10);
if (isNaN(year) || year < 0) {
return undefined;
}
if (year < 100) {
year += 2000;
}
return year;
}
_parseExpirationString(expirationString) {
let rules = [
{
regex: "(\\d{4})[-/](\\d{1,2})",
yearIndex: 1,
monthIndex: 2,
},
{
regex: "(\\d{1,2})[-/](\\d{4})",
yearIndex: 2,
monthIndex: 1,
},
{
regex: "(\\d{1,2})[-/](\\d{1,2})",
},
{
regex: "(\\d{2})(\\d{2})",
},
];
for (let rule of rules) {
let result = new RegExp(`(?:^|\\D)${rule.regex}(?!\\d)`).exec(expirationString);
if (!result) {
continue;
}
let year, month;
if (!rule.yearIndex || !rule.monthIndex) {
month = parseInt(result[1], 10);
if (month > 12) {
year = parseInt(result[1], 10);
month = parseInt(result[2], 10);
} else {
year = parseInt(result[2], 10);
}
} else {
year = parseInt(result[rule.yearIndex], 10);
month = parseInt(result[rule.monthIndex], 10);
}
if ((month < 1 || month > 12) ||
(year >= 100 && year < 2000)) {
continue;
}
return {month, year};
}
return {month: undefined, year: undefined};
}
static formatMaskedNumber(maskedNumber) {
return {
affix: "****",
label: maskedNumber.replace(/^\**/, ""),
};
}
static getMaskedNumber(number) {
let creditCard = new CreditCard({number});
return creditCard.maskedNumber;
}
static getLongMaskedNumber(number) {
let creditCard = new CreditCard({number});
return creditCard.longMaskedNumber;
}
static isValidNumber(number) {
let creditCard = new CreditCard({number});
return creditCard.isValidNumber();
}
}

View File

@ -189,6 +189,7 @@ EXTRA_JS_MODULES += [
'ClientID.jsm',
'Color.jsm',
'Console.jsm',
'CreditCard.jsm',
'css-selector.js',
'DateTimePickerHelper.jsm',
'DeferredTask.jsm',

View File

@ -7,6 +7,9 @@
var EXPORTED_SYMBOLS = ["FormData"];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "CreditCard",
"resource://gre/modules/CreditCard.jsm");
/**
* Returns whether the given URL very likely has input
* fields that contain serialized session store data.
@ -35,44 +38,6 @@ function getDocumentURI(doc) {
return doc.documentURI.replace(/#.*$/, "");
}
/**
* Returns whether the given value is a valid credit card number based on
* the Luhn algorithm. See https://en.wikipedia.org/wiki/Luhn_algorithm.
*/
function isValidCCNumber(value) {
// Remove dashes and whitespace.
let ccNumber = value.replace(/[-\s]+/g, "");
// Check for non-alphanumeric characters.
if (/[^0-9]/.test(ccNumber)) {
return false;
}
// Check for invalid length.
let length = ccNumber.length;
if (length != 9 && length != 15 && length != 16) {
return false;
}
let total = 0;
for (let i = 0; i < length; i++) {
let currentChar = ccNumber.charAt(length - i - 1);
let currentDigit = parseInt(currentChar, 10);
if (i % 2) {
// Double every other value.
total += currentDigit * 2;
// If the doubled value has two digits, add the digits together.
if (currentDigit > 4) {
total -= 9;
}
} else {
total += currentDigit;
}
}
return total % 10 == 0;
}
// For a comprehensive list of all available <INPUT> types see
// https://dxr.mozilla.org/mozilla-central/search?q=kInputTypeTable&redirect=false
const IGNORE_PROPERTIES = [
@ -198,9 +163,10 @@ var FormDataInternal = {
}
// We do not want to collect credit card numbers.
if (ChromeUtils.getClassName(node) === "HTMLInputElement" &&
isValidCCNumber(node.value)) {
continue;
if (ChromeUtils.getClassName(node) === "HTMLInputElement") {
if (CreditCard.isValidNumber(node.value)) {
continue;
}
}
if (ChromeUtils.getClassName(node) === "HTMLInputElement" ||

View File

@ -29,6 +29,7 @@ support-files =
[browser_AsyncPrefs.js]
[browser_Battery.js]
[browser_BrowserUtils.js]
[browser_CreditCard.js]
[browser_Deprecated.js]
[browser_Finder.js]
[browser_Finder_hidden_textarea.js]

View File

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
let oldGetters = {};
let gFakeLoggedIn = true;
add_task(function setup() {
oldGetters._token = Object.getOwnPropertyDescriptor(MasterPassword, "_token").get;
oldGetters.isEnabled = Object.getOwnPropertyDescriptor(MasterPassword, "isEnabled").get;
oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(MasterPassword, "isLoggedIn").get;
MasterPassword.__defineGetter__("_token", () => { return {hasPassword: true}; });
MasterPassword.__defineGetter__("isEnabled", () => true);
MasterPassword.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
registerCleanupFunction(() => {
MasterPassword.__defineGetter__("_token", oldGetters._token);
MasterPassword.__defineGetter__("isEnabled", oldGetters.isEnabled);
MasterPassword.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
// CreditCard.jsm and MasterPassword.jsm are imported into the global scope
// -- the window -- above. If they're not deleted, they outlive the test and
// are reported as a leak.
delete window.MasterPassword;
delete window.CreditCard;
});
});
add_task(async function test_getLabel_withMasterPassword() {
ok(MasterPassword.isEnabled, "Confirm that MasterPassword is faked and thinks it is enabled");
ok(MasterPassword.isLoggedIn, "Confirm that MasterPassword is faked and thinks it is logged in");
const ccNumber = "4111111111111111";
const encryptedNumber = await MasterPassword.encrypt(ccNumber);
const decryptedNumber = await MasterPassword.decrypt(encryptedNumber);
is(decryptedNumber, ccNumber, "Decrypted CC number should match original");
const name = "Foxkeh";
const creditCard = new CreditCard({encryptedNumber, name: "Foxkeh"});
const label = await creditCard.getLabel({showNumbers: true});
is(label, `${ccNumber}, ${name}`);
});

View File

@ -0,0 +1,274 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
add_task(function isValidNumber() {
function testValid(number, shouldPass) {
if (shouldPass) {
ok(CreditCard.isValidNumber(number), `${number} should be considered valid`);
} else {
ok(!CreditCard.isValidNumber(number), `${number} should not be considered valid`);
}
}
testValid("0000000000000000", true);
testValid("4929001587121045", true);
testValid("5103059495477870", true);
testValid("6011029476355493", true);
testValid("3589993783099582", true);
testValid("5415425865751454", true);
if (CreditCard.isValidNumber("30190729470495")) {
ok(false, "todo: 14-digit numbers (Diners Club) aren't supported by isValidNumber yet");
}
if (CreditCard.isValidNumber("36333851788250")) {
ok(false, "todo: 14-digit numbers (Diners Club) aren't supported by isValidNumber yet");
}
if (CreditCard.isValidNumber("3532596776688495393")) {
ok(false, "todo: 19-digit numbers (JCB, Discover, Maestro) could have 16-19 digits");
}
testValid("5038146897157463", true);
testValid("4026313395502338", true);
testValid("6387060366272981", true);
testValid("474915027480942", true);
testValid("924894781317325", true);
testValid("714816113937185", true);
testValid("790466087343106", true);
testValid("474320195408363", true);
testValid("219211148122351", true);
testValid("633038472250799", true);
testValid("354236732906484", true);
testValid("095347810189325", true);
testValid("930771457288760", true);
testValid("3091269135815020", true);
testValid("5471839082338112", true);
testValid("0580828863575793", true);
testValid("5015290610002932", true);
testValid("9465714503078607", true);
testValid("4302068493801686", true);
testValid("2721398408985465", true);
testValid("6160334316984331", true);
testValid("8643619970075142", true);
testValid("0218246069710785", true);
testValid("0000-0000-0080-4609", true);
testValid("0000 0000 0222 331", true);
testValid("344060747836806", true);
testValid("001064088", true);
testValid("4929001587121046", false);
testValid("5103059495477876", false);
testValid("6011029476355494", false);
testValid("3589993783099581", false);
testValid("5415425865751455", false);
testValid("5038146897157462", false);
testValid("4026313395502336", false);
testValid("6387060366272980", false);
testValid("344060747836804", false);
testValid("30190729470496", false);
testValid("36333851788255", false);
testValid("526931005800649", false);
testValid("724952425140686", false);
testValid("379761391174135", false);
testValid("030551436468583", false);
testValid("947377014076746", false);
testValid("254848023655752", false);
testValid("226871580283345", false);
testValid("708025346034339", false);
testValid("917585839076788", false);
testValid("918632588027666", false);
testValid("9946177098017064", false);
testValid("4081194386488872", false);
testValid("3095975979578034", false);
testValid("3662215692222536", false);
testValid("6723210018630429", false);
testValid("4411962856225025", false);
testValid("8276996369036686", false);
testValid("4449796938248871", false);
testValid("3350852696538147", false);
testValid("5011802870046957", false);
testValid("0000", false);
});
add_task(function test_formatMaskedNumber() {
function testFormat(number) {
let format = CreditCard.formatMaskedNumber(number);
Assert.equal(format.affix, "****", "Affix should always be four asterisks");
Assert.equal(format.label, number.substr(-4),
"The label should always be the last four digits of the card number");
}
testFormat("************0000");
testFormat("************1045");
testFormat("***********6806");
testFormat("**********0495");
testFormat("**********8250");
});
add_task(function test_maskNumber() {
function testMask(number, expected) {
let card = new CreditCard({number});
Assert.equal(card.maskedNumber, expected,
"Masked number should only show the last four digits");
}
testMask("0000000000000000", "**** 0000");
testMask("4929001587121045", "**** 1045");
testMask("5103059495477870", "**** 7870");
testMask("6011029476355493", "**** 5493");
testMask("3589993783099582", "**** 9582");
testMask("5415425865751454", "**** 1454");
testMask("344060747836806", "**** 6806");
Assert.throws(() => (new CreditCard({number: "1234"})).maskedNumber,
/Invalid credit card number/,
"Four or less numbers should throw when retrieving the maskedNumber");
});
add_task(function test_longMaskedNumber() {
function testMask(number, expected) {
let card = new CreditCard({number});
Assert.equal(card.longMaskedNumber, expected,
"Long masked number should show asterisks for all digits but last four");
}
testMask("0000000000000000", "************0000");
testMask("4929001587121045", "************1045");
testMask("5103059495477870", "************7870");
testMask("6011029476355493", "************5493");
testMask("3589993783099582", "************9582");
testMask("5415425865751454", "************1454");
testMask("344060747836806", "***********6806");
Assert.throws(() => (new CreditCard({number: "1234"})).longMaskedNumber,
/Invalid credit card number/,
"Four or less numbers should throw when retrieving the maskedNumber");
});
add_task(function test_isValid() {
function testValid(number, expirationMonth, expirationYear, shouldPass, message) {
let card = new CreditCard({
number,
expirationMonth,
expirationYear,
});
if (shouldPass) {
ok(card.isValid(), message);
} else {
ok(!card.isValid(), message);
}
}
let year = (new Date()).getFullYear();
let month = (new Date()).getMonth() + 1;
testValid("0000000000000000", month, year + 2, true,
"Valid number and future expiry date (two years) should pass");
testValid("0000000000000000", month + 2, year, true,
"Valid number and future expiry date (two months) should pass");
testValid("0000000000000000", month, year, true,
"Valid number and expiry date equal to this month should pass");
testValid("0000000000000000", month - 1, year, false,
"Valid number but overdue expiry date should fail");
testValid("0000000000000000", month, year - 1, false,
"Valid number but overdue expiry date (by a year) should fail");
testValid("0000000000000001", month, year + 2, false,
"Invalid number but future expiry date should fail");
});
add_task(function test_normalize() {
Assert.equal((new CreditCard({number: "0000 0000 0000 0000"})).number, "0000000000000000",
"Spaces should be removed from card number after it is normalized");
Assert.equal((new CreditCard({number: "0000 0000\t 0000\t0000"})).number, "0000000000000000",
"Spaces should be removed from card number after it is normalized");
Assert.equal((new CreditCard({number: "0000-0000-0000-0000"})).number, "0000000000000000",
"Hyphens should be removed from card number after it is normalized");
Assert.equal((new CreditCard({number: "0000-0000 0000-0000"})).number, "0000000000000000",
"Spaces and hyphens should be removed from card number after it is normalized");
Assert.equal((new CreditCard({number: "0000000000000000"})).number, "0000000000000000",
"Normalized numbers should not get changed");
Assert.equal((new CreditCard({number: "0000"})).number, null,
"Card numbers that are too short get set to null");
let card = new CreditCard({number: "0000000000000000"});
card.expirationYear = "22";
card.expirationMonth = "11";
Assert.equal(card.expirationYear, 2022, "Years less than four digits are in the third millenium");
card.expirationYear = "-200";
ok(isNaN(card.expirationYear), "Negative years are blocked");
card.expirationYear = "1998";
Assert.equal(card.expirationYear, 1998, "Years with four digits are not changed");
card.expirationYear = "test";
ok(isNaN(card.expirationYear), "non-number years are returned as NaN");
card.expirationMonth = "02";
Assert.equal(card.expirationMonth, 2, "Zero-leading months are converted properly (not octal)");
card.expirationMonth = "test";
ok(isNaN(card.expirationMonth), "non-number months are returned as NaN");
card.expirationMonth = "12";
Assert.equal(card.expirationMonth, 12, "Months formatted correctly are unchanged");
card.expirationMonth = "13";
ok(isNaN(card.expirationMonth), "Months above 12 are blocked");
card.expirationMonth = "7";
Assert.equal(card.expirationMonth, 7, "Changing back to a valid number passes");
card.expirationMonth = "0";
ok(isNaN(card.expirationMonth), "Months below 1 are blocked");
card.expirationMonth = card.expirationYear = undefined;
card.expirationString = "2022/01";
Assert.equal(card.expirationMonth, 1, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2022, "Year should be parsed correctly");
card.expirationString = "2023-02";
Assert.equal(card.expirationMonth, 2, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2023, "Year should be parsed correctly");
card.expirationString = "03-2024";
Assert.equal(card.expirationMonth, 3, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2024, "Year should be parsed correctly");
card.expirationString = "04/2025";
Assert.equal(card.expirationMonth, 4, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2025, "Year should be parsed correctly");
card.expirationString = "05/26";
Assert.equal(card.expirationMonth, 5, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2026, "Year should be parsed correctly");
card.expirationString = "27-6";
Assert.equal(card.expirationMonth, 6, "Month should be parsed correctly");
Assert.equal(card.expirationYear, 2027, "Year should be parsed correctly");
card.expirationString = "07/11";
Assert.equal(card.expirationMonth, 7, "Ambiguous month should be parsed correctly");
Assert.equal(card.expirationYear, 2011, "Ambiguous year should be parsed correctly");
card = new CreditCard({
number: "0000000000000000",
expirationMonth: "02",
expirationYear: "2112",
expirationString: "06-2066",
});
Assert.equal(card.expirationMonth, 2, "expirationString is takes lower precendence than explicit month");
Assert.equal(card.expirationYear, 2112, "expirationString is takes lower precendence than explicit year");
});
add_task(async function test_label() {
let testCases = [{
number: "0000000000000000",
name: "Rudy Badoody",
expectedLabel: "0000000000000000, Rudy Badoody",
expectedMaskedLabel: "**** 0000, Rudy Badoody",
}, {
number: "3589993783099582",
name: "Jimmy Babimmy",
expectedLabel: "3589993783099582, Jimmy Babimmy",
expectedMaskedLabel: "**** 9582, Jimmy Babimmy",
}, {
number: "************9582",
name: "Jimmy Babimmy",
expectedLabel: "**** 9582, Jimmy Babimmy",
expectedMaskedLabel: "**** 9582, Jimmy Babimmy",
}, {
name: "Ricky Bobby",
expectedLabel: "Ricky Bobby",
expectedMaskedLabel: "Ricky Bobby",
}];
for (let testCase of testCases) {
let {number, name} = testCase;
let card = new CreditCard({number, name});
Assert.equal(await card.getLabel({showNumbers: true}), testCase.expectedLabel,
"The expectedLabel should be shown when showNumbers is true");
Assert.equal(await card.getLabel({showNumbers: false}), testCase.expectedMaskedLabel,
"The expectedMaskedLabel should be shown when showNumbers is false");
}
});

View File

@ -13,6 +13,7 @@ skip-if = toolkit == 'android'
[test_client_id.js]
skip-if = toolkit == 'android'
[test_Color.js]
[test_CreditCard.js]
[test_DeferredTask.js]
skip-if = toolkit == 'android'
[test_FileUtils.js]