--- a/browser/components/payments/test/browser/head.js
+++ b/browser/components/payments/test/browser/head.js
@@ -190,19 +190,17 @@ function checkPaymentAddressMatchesStora
* Checks that a card from autofill storage matches a Payment Request MethodDetails response.
* @param {MethodDetails} methodDetails
* @param {object} card
* @param {string} msg to describe the check
*/
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");
is(methodDetails.expiryYear, card["cc-exp-year"], "Check expiryYear");
}
/**
* Create a PaymentRequest object with the given parameters, then
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -33,16 +33,17 @@ var EXPORTED_SYMBOLS = ["formAutofillPar
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
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",
});
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
@@ -514,17 +515,22 @@ FormAutofillParent.prototype = {
setUsedStatus(2);
return async () => {
// Suppress the pending doorhanger from showing up if user disabled credit card in previous doorhanger.
if (!FormAutofillUtils.isAutofillCreditCardsEnabled) {
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);
if (state == "cancel") {
return;
}
if (state == "disable") {
--- a/browser/extensions/formautofill/FormAutofillStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -127,16 +127,18 @@
this.EXPORTED_SYMBOLS = ["formAutofillStorage"];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
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",
"resource://formautofill/FormAutofillNameUtils.jsm");
ChromeUtils.defineModuleGetter(this, "MasterPassword",
"resource://formautofill/MasterPassword.jsm");
ChromeUtils.defineModuleGetter(this, "PhoneNumber",
"resource://formautofill/phonenumberutils/PhoneNumber.jsm");
@@ -1156,17 +1158,17 @@ class AutofillRecords {
this._normalizeFields(record);
for (let key in record) {
if (!this.VALID_FIELDS.includes(key)) {
throw new Error(`"${key}" is not a valid field.`);
}
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];
}
}
if (!Object.keys(record).length) {
throw new Error("Record contains no valid field.");
@@ -1489,23 +1491,16 @@ class Addresses extends AutofillRecords
}
}
class CreditCards extends AutofillRecords {
constructor(store) {
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
// computed fields.)
// NOTE: Computed fields should be always present in the storage no matter
// it's empty or not.
@@ -1533,17 +1528,17 @@ class CreditCards extends AutofillRecord
}
hasNewComputedFields = true;
}
// Encrypt credit card number
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"] = "";
}
}
return hasNewComputedFields;
}
@@ -1573,102 +1568,40 @@ class CreditCards extends AutofillRecord
}
delete creditCard["cc-given-name"];
delete creditCard["cc-additional-name"];
delete creditCard["cc-family-name"];
}
_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"];
}
}
}
_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;
- }
- }
-
- 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;
- }
+ 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"] && (!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;
- }
+ if (card.expirationYear) {
+ creditCard["cc-exp-year"] = card.expirationYear;
+ } else {
+ delete creditCard["cc-exp-year"];
}
-
delete creditCard["cc-exp"];
}
/**
* Normalize the given record and return the first matched guid if storage has the same record.
* @param {Object} targetCreditCard
* The credit card for duplication checking.
* @returns {string|null}
@@ -1681,17 +1614,17 @@ class CreditCards extends AutofillRecord
let isDuplicate = this.VALID_FIELDS.every(field => {
if (!clonedTargetCreditCard[field]) {
return !creditCard[field];
}
if (field == "cc-number" && creditCard[field]) {
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"]);
}
return clonedTargetCreditCard[field] == creditCard[field];
});
if (isDuplicate) {
return creditCard.guid;
}
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -37,16 +37,18 @@ const SECTION_TYPES = {
};
// The maximum length of data to be saved in a single field for preventing DoS
// attacks that fill the user's hard drive(s).
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
// information while requesting conutry information, and set country to true.
// Level 1 Set is for recording which country's level 1/level 2 data is loaded,
// since we only load this when getCountryAddressData called with level 1 parameter.
_dataLoaded: {
country: false,
@@ -224,27 +226,19 @@ this.FormAutofillUtils = {
isAddressField(fieldName) {
return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
},
isCreditCardField(fieldName) {
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) {
return this._fieldNameInfo[fieldName];
},
getCategoriesFromFieldNames(fieldNames) {
let categories = new Set();
@@ -259,52 +253,16 @@ this.FormAutofillUtils = {
getAddressSeparator() {
// The separator should be based on the L10N address format, and using a
// white space is a temporary solution.
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.
*
* @param {object} address
* @returns {string}
*/
getAddressLabel(address) {
// TODO: Implement a smarter way for deciding what to display
@@ -374,23 +332,16 @@ this.FormAutofillUtils = {
for (let field in address) {
if (field != "tel" && this.getCategoryFromFieldName(field) == "tel") {
delete address[field];
}
}
},
- fmtMaskedCreditCardLabel(maskedCCNum = "") {
- return {
- affix: "****",
- label: maskedCCNum.replace(/^\**/, ""),
- };
- },
-
defineLazyLogGetter(scope, logPrefix) {
XPCOMUtils.defineLazyGetter(scope, "log", () => {
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
return new ConsoleAPI({
maxLogLevelPref: "extensions.formautofill.loglevel",
prefix: logPrefix,
});
});
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -6,16 +6,18 @@
"use strict";
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");
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
class ProfileAutoCompleteResult {
constructor(searchString, focusedFieldName, allFieldNames, matchingProfiles, {
@@ -334,17 +336,17 @@ class CreditCardResult extends ProfileAu
}
let matching = GROUP_FIELDS[currentFieldName] ?
allFieldNames.some(fieldName => GROUP_FIELDS[currentFieldName].includes(fieldName)) :
allFieldNames.includes(currentFieldName);
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];
}
}
return ""; // Nothing matched.
}
@@ -369,17 +371,17 @@ class CreditCardResult extends ProfileAu
// Skip results without a primary label.
let labels = profiles.filter(profile => {
return !!profile[focusedFieldName];
}).map(profile => {
let primaryAffix;
let primary = profile[focusedFieldName];
if (focusedFieldName == "cc-number") {
- let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(primary);
+ let {affix, label} = CreditCard.formatMaskedNumber(primary);
primaryAffix = affix;
primary = label;
}
return {
primaryAffix,
primary,
secondary: this._getSecondaryLabel(focusedFieldName,
allFieldNames,
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -8,16 +8,18 @@
const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
const EDIT_CREDIT_CARD_URL = "chrome://formautofill/content/editCreditCard.xhtml";
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",
"resource://formautofill/MasterPassword.jsm");
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
@@ -329,27 +331,27 @@ class ManageCreditCards extends ManageRe
}
}
/**
* 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]
+ * @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) {
this._isDecrypted = !this._isDecrypted;
this.updateShowHideButtonState();
await this.updateLabels(options, this._isDecrypted);
}
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -13,17 +13,17 @@ add_task(async function test_submit_cred
"popupshown");
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
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));
form.querySelector("input[type=submit]").click();
});
ok(!SpecialPowers.Services.prefs.prefHasUserValue(SYNC_USERNAME_PREF),
"Sync account should not exist by default");
@@ -52,17 +52,17 @@ add_task(async function test_submit_cred
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown");
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
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");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
@@ -130,17 +130,17 @@ add_task(async function test_submit_chan
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
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
await new Promise(resolve => setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
@@ -168,17 +168,17 @@ add_task(async function test_submit_dupl
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
async function(browser) {
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
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());
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
@@ -204,17 +204,17 @@ add_task(async function test_submit_unno
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
async function(browser) {
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
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));
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
@@ -243,17 +243,17 @@ add_task(async function test_submit_cred
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
await new Promise(resolve => setTimeout(resolve, 1000));
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));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
await clickDoorhangerButton(MENU_BUTTON, 0);
@@ -281,34 +281,34 @@ add_task(async function test_submit_cred
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
await new Promise(resolve => setTimeout(resolve, 1000));
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));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
await masterPasswordDialogShown;
await TestUtils.topicObserved("formautofill-storage-changed");
}
);
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();
});
add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
LoginTestUtils.masterPassword.enable();
let masterPasswordDialogShown = waitForMasterPasswordDialog();
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
@@ -318,17 +318,17 @@ add_task(async function test_submit_cred
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
await new Promise(resolve => setTimeout(resolve, 1000));
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));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
@@ -356,17 +356,17 @@ add_task(async function test_submit_cred
"popupshown");
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
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));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
let cb = getDoorhangerCheckbox();
@@ -412,17 +412,17 @@ add_task(async function test_submit_cred
"popupshown");
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
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));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
let cb = getDoorhangerCheckbox();
@@ -446,17 +446,17 @@ add_task(async function test_submit_manu
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown");
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
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");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
form.querySelector("input[type=submit]").click();
});
await promiseShown;
@@ -501,17 +501,17 @@ add_task(async function test_update_auto
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
}
);
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();
});
add_task(async function test_update_autofill_form_exp_date() {
await SpecialPowers.pushPrefEnv({
"set": [
@@ -541,17 +541,17 @@ add_task(async function test_update_auto
await promiseShown;
await clickDoorhangerButton(MAIN_BUTTON);
}
);
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();
});
add_task(async function test_create_new_autofill_form() {
await SpecialPowers.pushPrefEnv({
"set": [
@@ -595,34 +595,34 @@ add_task(async function test_create_new_
add_task(async function test_update_duplicate_autofill_form() {
await SpecialPowers.pushPrefEnv({
"set": [
[CREDITCARDS_USED_STATUS_PREF, 0],
],
});
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");
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
async function(browser) {
await openPopupOn(browser, "form #cc-number");
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
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));
form.querySelector("input[type=submit]").click();
});
await sleep(1000);
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
--- a/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
@@ -46,19 +46,19 @@ add_task(async function test_removingSin
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
await waitForFocusAndFormReady(win);
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
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");
is(btnEdit.disabled, false, "Edit button enabled");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
is(selRecords.length, 2, "Two credit cards left");
@@ -120,39 +120,39 @@ add_task(async function test_showCreditC
let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
// 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]);
await removeCreditCards([selRecords.options[0].value]);
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
win.close();
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -95,30 +95,30 @@ const TEST_ADDRESS_DE_1 = {
"postal-code": "10997",
country: "DE",
tel: "+4930983333000",
email: "timbl@w3.org",
};
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,
};
const MAIN_BUTTON = "button";
const SECONDARY_BUTTON = "secondaryButton";
const MENU_BUTTON = "menubutton";
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -197,19 +197,21 @@ async function cleanUpCreditCards() {
}
async function cleanUpStorage() {
await cleanUpAddresses();
await cleanUpCreditCards();
}
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});
}
// Utils for registerPopupShownListener(in satchel_common.js) that handles dropdown popup
// Please call "initPopupListener()" in your test and "await expectPopup()"
// if you want to wait for dropdown menu displayed.
function expectPopup() {
--- a/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
@@ -17,29 +17,29 @@ Form autofill test: simple form credit c
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/AddTask.js */
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
/* import-globals-from formautofill_common.js */
"use strict";
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() {
await addCreditCard(MOCK_STORAGE[0]);
await addCreditCard(MOCK_STORAGE[1]);
}
async function setupFormHistory() {
--- a/browser/extensions/formautofill/test/mochitest/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_clear_form.html
@@ -28,22 +28,22 @@ const MOCK_ADDR_STORAGE = [{
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
}, {
organization: "Tel org",
tel: "+12223334444",
}];
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,
}];
initPopupListener();
add_task(async function setup_storage() {
await addAddress(MOCK_ADDR_STORAGE[0]);
--- a/browser/extensions/formautofill/test/mochitest/test_creditcard_autocomplete_off.html
+++ b/browser/extensions/formautofill/test/mochitest/test_creditcard_autocomplete_off.html
@@ -17,48 +17,48 @@ Form autofill test: simple form credit c
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/AddTask.js */
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
/* import-globals-from formautofill_common.js */
"use strict";
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,
}];
async function setupCreditCardStorage() {
await addCreditCard(MOCK_STORAGE[0]);
await addCreditCard(MOCK_STORAGE[1]);
}
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"},
]);
}
initPopupListener();
// Show Form History popup for non-autocomplete="off" field only
add_task(async function history_only_menu_checking() {
await setupFormHistory();
await setInput("#cc-number", "");
synthesizeKey("KEY_ArrowDown");
await expectPopup();
- checkMenuEntries(["1234000056780000"], false);
+ checkMenuEntries(["6011029476355493"], false);
await setInput("#cc-name", "");
synthesizeKey("KEY_ArrowDown");
await notExpectPopup();
});
// Show Form Autofill popup for the credit card fields.
add_task(async function check_menu_when_both_with_autocomplete_off() {
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -2,88 +2,89 @@
* Tests FormAutofillStorage object with creditCards records.
*/
"use strict";
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 = {
"cc-name": "J. Smith",
"cc-number": "4111111111111111",
billingAddressGUID: "9m6hf4gfr6ge",
};
const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
billingAddressGUID: "",
"cc-name": "",
- "cc-number": "1234123412341234",
+ "cc-number": "344060747836806",
"cc-exp-month": 1,
};
const TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD = {
"cc-given-name": "",
"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 = {
"cc-exp-month": 13,
};
const TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1 = {
"cc-name": "",
@@ -91,97 +92,97 @@ const TEST_CREDIT_CARD_EMPTY_AFTER_UPDAT
"cc-exp-month": 13,
"cc-exp-year": "",
};
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,
},
},
{
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",
},
},
{
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,
},
noNeedToUpdate: true,
},
{
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,
},
{
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,
},
},
];
let prepareTestCreditCards = async function(path) {
let profileStorage = new FormAutofillStorage(path);
@@ -312,17 +313,17 @@ add_task(async function test_add() {
Assert.equal(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
Assert.equal(creditCard["cc-name"], undefined);
Assert.equal(creditCard.billingAddressGUID, undefined);
// Empty computed fields shouldn't cause any problem.
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\./);
Assert.throws(() => profileStorage.creditCards.add({}),
/Record contains no valid field\./);
Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE),
@@ -394,21 +395,21 @@ add_task(async function test_update() {
Assert.equal(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
Assert.equal(creditCard["cc-name"], undefined);
Assert.equal(creditCard.billingAddressGUID, undefined);
// Empty computed fields shouldn't cause any problem.
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),
/No matching record\./
);
Assert.throws(
() => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
@@ -630,17 +631,19 @@ add_task(async function test_getDuplicat
// Superset shouldn't be treated as a duplicate.
record = Object.assign({}, TEST_CREDIT_CARD_3);
record["cc-name"] = "John Doe";
Assert.equal(profileStorage.creditCards.getDuplicateGuid(record), null);
// 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
// the master password is enabled.
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
let token = tokendb.getInternalKeyToken();
token.reset();
token.initPassword("password");
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -2,16 +2,17 @@
* Test for make sure getRecords can retrieve right collection from storage.
*/
"use strict";
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",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
@@ -24,24 +25,24 @@ const TEST_ADDRESS_1 = {
const TEST_ADDRESS_2 = {
"street-address": "Some Address",
country: "US",
};
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,
};
let target = {
sendAsyncMessage: function sendAsyncMessage(msg, payload) {},
};
@@ -54,17 +55,17 @@ add_task(async function test_getRecords(
addresses: [{
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
"organization": "World Wide Web Consortium",
}],
creditCards: [{
"cc-name": "John Doe",
- "cc-number": "1234567812345678",
+ "cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}],
};
for (let collectionName of ["addresses", "creditCards", "nonExisting"]) {
let collection = formAutofillParent.formAutofillStorage[collectionName];
let expectedResult = fakeResult[collectionName] || [];
@@ -164,17 +165,17 @@ add_task(async function test_getRecords_
add_task(async function test_getRecords_creditCards() {
let formAutofillParent = new FormAutofillParent();
await formAutofillParent.init();
await formAutofillParent.formAutofillStorage.initialize();
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;
});
sinon.stub(collection, "getAll", () => [Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]);
let CreditCardsWithDecryptedNumber = [
Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
];
@@ -207,26 +208,26 @@ add_task(async function test_getRecords_
},
expectedResult: [],
},
{
description: "If the search number string could match 1 creditCard (without masterpassword)",
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
- searchString: "123",
+ searchString: "411",
},
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
},
{
description: "If the search string could match multiple creditCards (without masterpassword)",
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
- searchString: "1",
+ searchString: "4",
},
expectedResult: CreditCardsWithDecryptedNumber,
},
{
description: "If the search string could match 1 creditCard (with masterpassword)",
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-name"},
@@ -235,17 +236,17 @@ add_task(async function test_getRecords_
mpEnabled: true,
expectedResult: encryptedCCRecords.slice(0, 1),
},
{
description: "Return all creditCards if focused field is cc number (with masterpassword)",
filter: {
collectionName: "creditCards",
info: {fieldName: "cc-number"},
- searchString: "123",
+ searchString: "411",
},
mpEnabled: true,
expectedResult: encryptedCCRecords,
},
];
for (let testCase of testCases) {
info("Starting testcase: " + testCase.description);
--- a/browser/extensions/formautofill/test/unit/test_reconcile.js
+++ b/browser/extensions/formautofill/test/unit/test_reconcile.js
@@ -467,453 +467,453 @@ const ADDRESS_RECONCILE_TESTCASES = [
const CREDIT_CARD_RECONCILE_TESTCASES = [
{
description: "Local change",
parent: {
// So when we last wrote the record to the server, it had these values.
"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
// we can deduce the record hasn't actually been changed remotely so we
// can safely ignore the incoming record and write our local changes.
"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",
},
},
{
description: "Remote change",
parent: {
"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",
},
},
{
description: "New local field",
parent: {
"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,
},
},
{
description: "New remote field",
parent: {
"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,
},
},
{
description: "Deleted field locally",
parent: {
"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",
},
},
{
description: "Deleted field remotely",
parent: {
"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",
},
},
{
description: "Local and remote changes to unrelated fields",
parent: {
// The last time we wrote this to the server, "cc-exp-month" was 12.
"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: {
// Remotely, we've changed "cc-exp-month" to 1.
"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,
},
},
{
description: "Multiple local changes",
parent: {
"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,
},
},
{
// Local and remote diverged from the shared parent, but the values are the
// same, so we shouldn't fork.
description: "Same change to local and remote",
parent: {
"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",
},
},
{
description: "Conflicting changes to single field",
parent: {
// This is what we last wrote to the sync server.
"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",
},
},
{
description: "Conflicting changes to multiple fields",
parent: {
"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,
},
},
{
description: "Field deleted locally, changed remotely",
parent: {
"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,
},
},
{
description: "Field changed locally, deleted remotely",
parent: {
"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",
},
},
{
// Created, last modified should be synced; last used and times used should
// be local. Remote created time older than local, remote modified time
// newer than local.
description: "Created, last modified time reconciliation without local changes",
parent: {
"guid": "5113f329c42f",
"version": 1,
"cc-name": "John Doe",
- "cc-number": "1111222233334444",
+ "cc-number": "4111111111111111",
"timeCreated": 1234,
"timeLastModified": 5678,
"timeLastUsed": 5678,
"timesUsed": 6,
},
local: [],
remote: {
"guid": "5113f329c42f",
"version": 1,
"cc-name": "John Doe",
- "cc-number": "1111222233334444",
+ "cc-number": "4111111111111111",
"timeCreated": 1200,
"timeLastModified": 5700,
"timeLastUsed": 5700,
"timesUsed": 3,
},
reconciled: {
"guid": "5113f329c42f",
"cc-name": "John Doe",
- "cc-number": "1111222233334444",
+ "cc-number": "4111111111111111",
"timeCreated": 1200,
"timeLastModified": 5700,
"timeLastUsed": 5678,
"timesUsed": 6,
},
},
{
// Local changes, remote created time newer than local, remote modified time
// older than local.
description: "Created, last modified time reconciliation with local changes",
parent: {
"guid": "791e5608b80a",
"version": 1,
"cc-name": "John Doe",
- "cc-number": "1111222233334444",
+ "cc-number": "4111111111111111",
"timeCreated": 1234,
"timeLastModified": 5678,
"timeLastUsed": 5678,
"timesUsed": 6,
},
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,
"timesUsed": 3,
},
reconciled: {
"guid": "791e5608b80a",
"cc-name": "John Doe",
- "cc-number": "4444333322221111",
+ "cc-number": "4929001587121045",
"timeCreated": 1234,
"timeLastUsed": 5678,
"timesUsed": 6,
},
},
];
add_task(async function test_reconcile_unknown_version() {
--- a/browser/extensions/formautofill/test/unit/test_storage_remove.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_remove.js
@@ -24,24 +24,24 @@ const TEST_ADDRESS_1 = {
const TEST_ADDRESS_2 = {
"street-address": "Some Address",
country: "US",
};
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,
};
// Like add_task, but actually adds 2 - one for addresses and one for cards.
function add_storage_task(test_function) {
add_task(async function() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -19,17 +19,17 @@ const TEST_ADDRESS_1 = {
"postal-code": "02139",
country: "US",
tel: "+1 617 253 5702",
email: "timbl@w3.org",
};
const TEST_CC_1 = {
"cc-name": "John Doe",
- "cc-number": "1234567812345678",
+ "cc-number": "4111111111111111",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
let do_check_tombstone_record = (profile) => {
Assert.ok(profile.deleted);
Assert.deepEqual(Object.keys(profile).sort(),
["guid", "timeLastModified", "deleted"].sort());
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -534,20 +534,20 @@ const CREDIT_CARD_COMPUTE_TESTCASES = [
"cc-family-name": "Berners-Lee",
},
},
// Card Number
{
description: "Number should be encrypted and masked",
creditCard: {
- "cc-number": "1234123412341234",
+ "cc-number": "4929001587121045",
},
expectedResult: {
- "cc-number": "************1234",
+ "cc-number": "************1045",
},
},
// Expiration Date
{
description: "Has \"cc-exp-year\" and \"cc-exp-month\"",
creditCard: {
"cc-exp-month": 12,
@@ -604,38 +604,38 @@ const CREDIT_CARD_NORMALIZE_TESTCASES =
"cc-name": "John Doe",
},
},
// Card Number
{
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",
},
},
// Expiration Date
{
description: "Has \"cc-exp\" formatted \"yyyy-mm\"",
creditCard: {
"cc-exp": "2022-12",
@@ -783,17 +783,17 @@ const CREDIT_CARD_NORMALIZE_TESTCASES =
expectedResult: {
"cc-exp-month": 11,
"cc-exp-year": 2033,
},
},
{
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: {
"cc-exp-month": undefined,
"cc-exp-year": undefined,
},
},
{
--- a/toolkit/components/satchel/formSubmitListener.js
+++ b/toolkit/components/satchel/formSubmitListener.js
@@ -1,14 +1,17 @@
/* 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/. */
/* 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");
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
let satchelFormListener = {
QueryInterface: ChromeUtils.generateQI([
Ci.nsIFormSubmitObserver,
@@ -26,46 +29,16 @@ let satchelFormListener = {
this.updatePrefs();
},
updatePrefs() {
this.debug = Services.prefs.getBoolPref("browser.formfill.debug");
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;
}
dump("satchelFormListener: " + message + "\n");
Services.console.logStringMessage("satchelFormListener: " + message);
},
@@ -122,17 +95,17 @@ let satchelFormListener = {
let value = input.value.trim();
// Don't save empty or unchanged values.
if (!value || value == input.defaultValue.trim()) {
continue;
}
// Don't save credit card numbers.
- if (this.isValidCCNumber(value)) {
+ if (CreditCard.isValidNumber(value)) {
this.log("skipping saving a credit card number");
continue;
}
let name = input.name || input.id;
if (!name) {
continue;
}
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/CreditCard.jsm
@@ -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();
+ }
+}
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -184,16 +184,17 @@ EXTRA_JS_MODULES += [
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CanonicalJSON.jsm',
'CertUtils.jsm',
'CharsetMenu.jsm',
'ClientID.jsm',
'Color.jsm',
'Console.jsm',
+ 'CreditCard.jsm',
'css-selector.js',
'DateTimePickerHelper.jsm',
'DeferredTask.jsm',
'Deprecated.jsm',
'E10SUtils.jsm',
'EventEmitter.jsm',
'FileUtils.jsm',
'Finder.jsm',
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -2,16 +2,19 @@
* 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 = ["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.
*/
function isRestorationPage(url) {
return url == "about:sessionrestore" || url == "about:welcomeback";
}
@@ -30,54 +33,16 @@ function hasRestorationData(data) {
/**
* Returns the given document's current URI and strips
* off the URI's anchor part, if any.
*/
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 = [
["type", new Set(["password", "hidden", "button", "image", "submit", "reset"])],
["autocomplete", new Set(["off"])]
];
function shouldIgnoreNode(node) {
for (let i = 0; i < IGNORE_PROPERTIES.length; ++i) {
@@ -193,19 +158,20 @@ var FormDataInternal = {
// Only generate a limited number of XPath expressions for perf reasons
// (cf. bug 477564)
if (!node.id && generatedCount > MAX_TRAVERSED_XPATHS) {
continue;
}
// 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" ||
ChromeUtils.getClassName(node) === "HTMLTextAreaElement" ||
(node.namespaceURI == this.namespaceURIs.xul && node.localName == "textbox")) {
switch (node.type) {
case "checkbox":
case "radio":
--- a/toolkit/modules/tests/browser/browser.ini
+++ b/toolkit/modules/tests/browser/browser.ini
@@ -24,16 +24,17 @@ support-files =
file_script_xhr.js
head.js
WebRequest_dynamic.sjs
WebRequest_redirection.sjs
[browser_AsyncPrefs.js]
[browser_Battery.js]
[browser_BrowserUtils.js]
+[browser_CreditCard.js]
[browser_Deprecated.js]
[browser_Finder.js]
[browser_Finder_hidden_textarea.js]
[browser_Finder_offscreen_text.js]
[browser_Finder_overflowed_onscreen.js]
[browser_Finder_overflowed_textarea.js]
[browser_Finder_pointer_events_none.js]
[browser_Finder_vertical_text.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/browser/browser_CreditCard.js
@@ -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}`);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_CreditCard.js
@@ -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");
+ }
+});
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -8,16 +8,17 @@ support-files =
zips/zen.zip
[test_BinarySearch.js]
skip-if = toolkit == 'android'
[test_CanonicalJSON.js]
[test_client_id.js]
skip-if = toolkit == 'android'
[test_Color.js]
+[test_CreditCard.js]
[test_DeferredTask.js]
skip-if = toolkit == 'android'
[test_FileUtils.js]
skip-if = toolkit == 'android'
[test_FinderIterator.js]
[test_GMPInstallManager.js]
skip-if = toolkit == 'android'
[test_Http.js]